Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: hyperium/tonic
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.8.1
Choose a base ref
...
head repository: hyperium/tonic
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.8.2
Choose a head ref
  • 7 commits
  • 20 files changed
  • 5 contributors

Commits on Sep 7, 2022

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    5723681 View commit details

Commits on Sep 12, 2022

  1. Verified

    This commit was signed with the committer’s verified signature.
    darinpope Darin Pope
    Copy the full SHA
    ee3d0df View commit details

Commits on Sep 21, 2022

  1. feat(types): Add gRPC Richer Error Model support (BadRequest) (#1068)

    * types: add tonic as dependency, add error_details.proto
    
    * types: add BadRequest support from flemosr/tonic-richer-error
    
    * types: adjust code following suggestions
    
    Adjustments following suggestions by @LucioFranco in #1068.
    
    Adjust style, remove unecessary prints, avoid glob imports, apply
    `non_exhaustive` to `ErrorDetails` and `ErrorDetail`, avoid pub
    fields in `ErrorDetails`, adjust
    `WithErrorDetails::with_error_details_vec` args, add
    `gen_details_bytes`.
    
    * types: add generated protobuf code
    
    As suggested by @LucioFranco in #1068 (comment).
    
    This avoids the need for consumers to have `protoc` in their path.
    Implemented following changes in #1065.
    
    * types: add custom metadata support
    
    This allows consumers to provide custom metadata when creating a
    `Status` with error details.
    
    * types: adjust code following suggestions
    
    Adjustments following suggestions by @LucioFranco in #1068.
    
    Move `error_details_vec` mod into `error_details` mod, adjust doc
    comments, rename `WithErrorDetails` trait to `StatusExt`.
    flemosr authored Sep 21, 2022
    Copy the full SHA
    3e40d81 View commit details

Commits on Sep 26, 2022

  1. feat: add Result<T> type alias for `std::result::Result<T, tonic::S…

    …tatus>` (#1085)
    
    * add `Result<T>` type alias for `std::result::Result<T, tonic::Status>`
    
    * Allow user to override the error type
    
    Co-authored-by: David Pedersen <david.pdrsn@gmail.com>
    
    * add rustdoc for `tonic::Result`
    
    * cargo fmt
    
    Co-authored-by: David Pedersen <david.pdrsn@gmail.com>
    stphnsmpsn and davidpdrsn authored Sep 26, 2022
    Copy the full SHA
    56ff45d View commit details

Commits on Sep 28, 2022

  1. Copy the full SHA
    c1b08df View commit details
  2. Copy the full SHA
    cddd992 View commit details
  3. Copy the full SHA
    a7e29c9 View commit details
30 changes: 28 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,30 @@
# [v0.8.0](https://github.com/hyperium/tonic/compare/v0.7.2...v0.8.0) (2022-07-29)
# [0.8.2](https://github.com/hyperium/tonic/compare/v0.8.1...v0.8.2) (2022-09-28)


### Bug Fixes

* **transport:** Bump axum for CVE-2022-3212 ([#1088](https://github.com/hyperium/tonic/issues/1088)) ([cddd992](https://github.com/hyperium/tonic/commit/cddd99266682127a3fa0e5d601f56a6346369814))


### Features

* **tonic:** add `Result<T>` type alias for `std::result::Result<T, tonic::Status>` ([#1085](https://github.com/hyperium/tonic/issues/1085)) ([56ff45d](https://github.com/hyperium/tonic/commit/56ff45d9a36040c429753d0d118ad980fbfe3eb8))
* **build:** add `cleanup-markdown` feature flag ([#1086](https://github.com/hyperium/tonic/issues/1086)) ([c1b08df](https://github.com/hyperium/tonic/commit/c1b08dffacb67e13ce7e94a002eee8999ca7c0e5))
* **tonic:** impl `Clone` for `Status` using `Arc` ([#1076](https://github.com/hyperium/tonic/issues/1076)) ([ee3d0df](https://github.com/hyperium/tonic/commit/ee3d0dfe7556fc7e996764f650ee3351097e7309))
* **transport:** Expose hyper's H2 adaptive window on server ([#1071](https://github.com/hyperium/tonic/issues/1071)) ([919d28b](https://github.com/hyperium/tonic/commit/919d28b2b96c7c803cec131a9e36e80d2b071701))


# [0.8.1](https://github.com/hyperium/tonic/compare/v0.8.0...v0.8.1) (2022-09-07)


### Features

* **transport:** Expose hyper's H2 adaptive window on server ([#1071](https://github.com/hyperium/tonic/issues/1071)) ([919d28b](https://github.com/hyperium/tonic/commit/919d28b2b96c7c803cec131a9e36e80d2b071701))
* Reduce the amount of monomorphized code.
* Expose `Extensions::into_http` and `Status::from_error`.
* **health:** Remove `build.rs` and commit generated code.

# [0.8.0](https://github.com/hyperium/tonic/compare/v0.7.2...v0.8.0) (2022-07-29)


### Features
@@ -16,7 +42,7 @@
* **tonic** Remove codegen depedency on `compression` feature.
* **tonic** Remove `compression` feature in favor of `gzip` feature.

# [v0.7.2](https://github.com/hyperium/tonic/compare/v0.7.1...v0.7.2) (2022-05-04)
# [0.7.2](https://github.com/hyperium/tonic/compare/v0.7.1...v0.7.2) (2022-05-04)


### Bug Fixes
9 changes: 5 additions & 4 deletions tonic-build/Cargo.toml
Original file line number Diff line number Diff line change
@@ -4,25 +4,26 @@ categories = ["network-programming", "asynchronous"]
description = """
Codegen module of `tonic` gRPC implementation.
"""
documentation = "https://docs.rs/tonic-build/0.7.2/tonic_build/"
documentation = "https://docs.rs/tonic-build/0.8.2/tonic_build/"
edition = "2018"
homepage = "https://github.com/hyperium/tonic"
keywords = ["rpc", "grpc", "async", "codegen", "protobuf"]
license = "MIT"
name = "tonic-build"
readme = "README.md"
repository = "https://github.com/hyperium/tonic"
version = "0.8.0"
version = "0.8.2"

[dependencies]
prettyplease = {version = "0.1"}
prettyplease = { version = "0.1" }
proc-macro2 = "1.0"
prost-build = {version = "0.11", optional = true}
prost-build = { version = "0.11", optional = true }
quote = "1.0"
syn = "1.0"

[features]
default = ["transport", "prost"]
cleanup-markdown = ["prost-build/cleanup-markdown"]
prost = ["prost-build"]
transport = []

10 changes: 9 additions & 1 deletion tonic-build/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
//! `tonic-build` compiles `proto` files via `prost` and generates service stubs
//! and proto definitiones for use with `tonic`.
//!
//! # Feature flags
//!
//! - `cleanup-markdown`: Enables cleaning up documentation from the generated code. Useful
//! when documentation of the generated code fails `cargo test --doc` for example.
//! - `prost`: Enables usage of prost generator (enabled by default).
//! - `transport`: Enables generation of `connect` method using `tonic::transport::Channel`
//! (enabled by default).
//!
//! # Required dependencies
//!
//! ```toml
@@ -62,7 +70,7 @@
html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg"
)]
#![deny(rustdoc::broken_intra_doc_links)]
#![doc(html_root_url = "https://docs.rs/tonic-build/0.8.0")]
#![doc(html_root_url = "https://docs.rs/tonic-build/0.8.2")]
#![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
2 changes: 1 addition & 1 deletion tonic-health/Cargo.toml
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ license = "MIT"
name = "tonic-health"
readme = "README.md"
repository = "https://github.com/hyperium/tonic"
version = "0.7.0"
version = "0.7.1"

[features]
default = ["transport"]
60 changes: 30 additions & 30 deletions tonic-health/src/generated/grpc.health.v1.rs
Original file line number Diff line number Diff line change
@@ -103,8 +103,8 @@ pub mod health_client {
self.inner = self.inner.accept_compressed(encoding);
self
}
/// If the requested service is unknown, the call will fail with status
/// NOT_FOUND.
///If the requested service is unknown, the call will fail with status
///NOT_FOUND.
pub async fn check(
&mut self,
request: impl tonic::IntoRequest<super::HealthCheckRequest>,
@@ -124,21 +124,21 @@ pub mod health_client {
);
self.inner.unary(request.into_request(), path, codec).await
}
/// Performs a watch for the serving status of the requested service.
/// The server will immediately send back a message indicating the current
/// serving status. It will then subsequently send a new message whenever
/// the service's serving status changes.
///Performs a watch for the serving status of the requested service.
///The server will immediately send back a message indicating the current
///serving status. It will then subsequently send a new message whenever
///the service's serving status changes.
///
/// If the requested service is unknown when the call is received, the
/// server will send a message setting the serving status to
/// SERVICE_UNKNOWN but will *not* terminate the call. If at some
/// future point, the serving status of the service becomes known, the
/// server will send a new message with the service's serving status.
///If the requested service is unknown when the call is received, the
///server will send a message setting the serving status to
///SERVICE_UNKNOWN but will *not* terminate the call. If at some
///future point, the serving status of the service becomes known, the
///server will send a new message with the service's serving status.
///
/// If the call terminates with status UNIMPLEMENTED, then clients
/// should assume this method is not supported and should not retry the
/// call. If the call terminates with any other status (including OK),
/// clients should retry the call with appropriate exponential backoff.
///If the call terminates with status UNIMPLEMENTED, then clients
///should assume this method is not supported and should not retry the
///call. If the call terminates with any other status (including OK),
///clients should retry the call with appropriate exponential backoff.
pub async fn watch(
&mut self,
request: impl tonic::IntoRequest<super::HealthCheckRequest>,
@@ -170,8 +170,8 @@ pub mod health_server {
///Generated trait containing gRPC methods that should be implemented for use with HealthServer.
#[async_trait]
pub trait Health: Send + Sync + 'static {
/// If the requested service is unknown, the call will fail with status
/// NOT_FOUND.
///If the requested service is unknown, the call will fail with status
///NOT_FOUND.
async fn check(
&self,
request: tonic::Request<super::HealthCheckRequest>,
@@ -182,21 +182,21 @@ pub mod health_server {
>
+ Send
+ 'static;
/// Performs a watch for the serving status of the requested service.
/// The server will immediately send back a message indicating the current
/// serving status. It will then subsequently send a new message whenever
/// the service's serving status changes.
///Performs a watch for the serving status of the requested service.
///The server will immediately send back a message indicating the current
///serving status. It will then subsequently send a new message whenever
///the service's serving status changes.
///
/// If the requested service is unknown when the call is received, the
/// server will send a message setting the serving status to
/// SERVICE_UNKNOWN but will *not* terminate the call. If at some
/// future point, the serving status of the service becomes known, the
/// server will send a new message with the service's serving status.
///If the requested service is unknown when the call is received, the
///server will send a message setting the serving status to
///SERVICE_UNKNOWN but will *not* terminate the call. If at some
///future point, the serving status of the service becomes known, the
///server will send a new message with the service's serving status.
///
/// If the call terminates with status UNIMPLEMENTED, then clients
/// should assume this method is not supported and should not retry the
/// call. If the call terminates with any other status (including OK),
/// clients should retry the call with appropriate exponential backoff.
///If the call terminates with status UNIMPLEMENTED, then clients
///should assume this method is not supported and should not retry the
///call. If the call terminates with any other status (including OK),
///clients should retry the call with appropriate exponential backoff.
async fn watch(
&self,
request: tonic::Request<super::HealthCheckRequest>,
2 changes: 1 addition & 1 deletion tonic-health/src/lib.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@
html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg"
)]
#![deny(rustdoc::broken_intra_doc_links)]
#![doc(html_root_url = "https://docs.rs/tonic-health/0.6.0")]
#![doc(html_root_url = "https://docs.rs/tonic-health/0.7.1")]
#![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
5 changes: 3 additions & 2 deletions tonic-types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ version = "0.6.0"
[dependencies]
prost = "0.11"
prost-types = "0.11"
tonic = {version = "0.8", path = "../tonic"}

[build-dependencies]
prost-build = "0.11"
[dev-dependencies]
tonic-build = {version = "0.8", path = "../tonic-build", features = ["prost"]}
3 changes: 0 additions & 3 deletions tonic-types/build.rs

This file was deleted.

250 changes: 250 additions & 0 deletions tonic-types/proto/error_details.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package google.rpc;

import "google/protobuf/duration.proto";

option go_package = "google.golang.org/genproto/googleapis/rpc/errdetails;errdetails";
option java_multiple_files = true;
option java_outer_classname = "ErrorDetailsProto";
option java_package = "com.google.rpc";
option objc_class_prefix = "RPC";

// Describes when the clients can retry a failed request. Clients could ignore
// the recommendation here or retry when this information is missing from error
// responses.
//
// It's always recommended that clients should use exponential backoff when
// retrying.
//
// Clients should wait until `retry_delay` amount of time has passed since
// receiving the error response before retrying. If retrying requests also
// fail, clients should use an exponential backoff scheme to gradually increase
// the delay between retries based on `retry_delay`, until either a maximum
// number of retries have been reached or a maximum retry delay cap has been
// reached.
message RetryInfo {
// Clients should wait at least this long between retrying the same request.
google.protobuf.Duration retry_delay = 1;
}

// Describes additional debugging info.
message DebugInfo {
// The stack trace entries indicating where the error occurred.
repeated string stack_entries = 1;

// Additional debugging information provided by the server.
string detail = 2;
}

// Describes how a quota check failed.
//
// For example if a daily limit was exceeded for the calling project,
// a service could respond with a QuotaFailure detail containing the project
// id and the description of the quota limit that was exceeded. If the
// calling project hasn't enabled the service in the developer console, then
// a service could respond with the project id and set `service_disabled`
// to true.
//
// Also see RetryInfo and Help types for other details about handling a
// quota failure.
message QuotaFailure {
// A message type used to describe a single quota violation. For example, a
// daily quota or a custom quota that was exceeded.
message Violation {
// The subject on which the quota check failed.
// For example, "clientip:<ip address of client>" or "project:<Google
// developer project id>".
string subject = 1;

// A description of how the quota check failed. Clients can use this
// description to find more about the quota configuration in the service's
// public documentation, or find the relevant quota limit to adjust through
// developer console.
//
// For example: "Service disabled" or "Daily Limit for read operations
// exceeded".
string description = 2;
}

// Describes all quota violations.
repeated Violation violations = 1;
}

// Describes the cause of the error with structured details.
//
// Example of an error when contacting the "pubsub.googleapis.com" API when it
// is not enabled:
// ```json
// { "reason": "API_DISABLED"
// "domain": "googleapis.com"
// "metadata": {
// "resource": "projects/123",
// "service": "pubsub.googleapis.com"
// }
// }
// ```
// This response indicates that the pubsub.googleapis.com API is not enabled.
//
// Example of an error that is returned when attempting to create a Spanner
// instance in a region that is out of stock:
// ```json
// { "reason": "STOCKOUT"
// "domain": "spanner.googleapis.com",
// "metadata": {
// "availableRegions": "us-central1,us-east2"
// }
// }
// ```
message ErrorInfo {
// The reason of the error. This is a constant value that identifies the
// proximate cause of the error. Error reasons are unique within a particular
// domain of errors. This should be at most 63 characters and match
// /[A-Z0-9_]+/.
string reason = 1;

// The logical grouping to which the "reason" belongs. The error domain
// is typically the registered service name of the tool or product that
// generates the error. Example: "pubsub.googleapis.com". If the error is
// generated by some common infrastructure, the error domain must be a
// globally unique value that identifies the infrastructure. For Google API
// infrastructure, the error domain is "googleapis.com".
string domain = 2;

// Additional structured details about this error.
//
// Keys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in
// length. When identifying the current value of an exceeded limit, the units
// should be contained in the key, not the value. For example, rather than
// {"instanceLimit": "100/request"}, should be returned as,
// {"instanceLimitPerRequest": "100"}, if the client exceeds the number of
// instances that can be created in a single (batch) request.
map<string, string> metadata = 3;
}

// Describes what preconditions have failed.
//
// For example, if an RPC failed because it required the Terms of Service to be
// acknowledged, it could list the terms of service violation in the
// PreconditionFailure message.
message PreconditionFailure {
// A message type used to describe a single precondition failure.
message Violation {
// The type of PreconditionFailure. We recommend using a service-specific
// enum type to define the supported precondition violation subjects. For
// example, "TOS" for "Terms of Service violation".
string type = 1;

// The subject, relative to the type, that failed.
// For example, "google.com/cloud" relative to the "TOS" type would indicate
// which terms of service is being referenced.
string subject = 2;

// A description of how the precondition failed. Developers can use this
// description to understand how to fix the failure.
//
// For example: "Terms of service not accepted".
string description = 3;
}

// Describes all precondition violations.
repeated Violation violations = 1;
}

// Describes violations in a client request. This error type focuses on the
// syntactic aspects of the request.
message BadRequest {
// A message type used to describe a single bad request field.
message FieldViolation {
// A path leading to a field in the request body. The value will be a
// sequence of dot-separated identifiers that identify a protocol buffer
// field. E.g., "field_violations.field" would identify this field.
string field = 1;

// A description of why the request element is bad.
string description = 2;
}

// Describes all violations in a client request.
repeated FieldViolation field_violations = 1;
}

// Contains metadata about the request that clients can attach when filing a bug
// or providing other forms of feedback.
message RequestInfo {
// An opaque string that should only be interpreted by the service generating
// it. For example, it can be used to identify requests in the service's logs.
string request_id = 1;

// Any data that was used to serve this request. For example, an encrypted
// stack trace that can be sent back to the service provider for debugging.
string serving_data = 2;
}

// Describes the resource that is being accessed.
message ResourceInfo {
// A name for the type of resource being accessed, e.g. "sql table",
// "cloud storage bucket", "file", "Google calendar"; or the type URL
// of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic".
string resource_type = 1;

// The name of the resource being accessed. For example, a shared calendar
// name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current
// error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED].
string resource_name = 2;

// The owner of the resource (optional).
// For example, "user:<owner email>" or "project:<Google developer project
// id>".
string owner = 3;

// Describes what error is encountered when accessing this resource.
// For example, updating a cloud project may require the `writer` permission
// on the developer console project.
string description = 4;
}

// Provides links to documentation or for performing an out of band action.
//
// For example, if a quota check failed with an error indicating the calling
// project hasn't enabled the accessed service, this can contain a URL pointing
// directly to the right place in the developer console to flip the bit.
message Help {
// Describes a URL link.
message Link {
// Describes what the link offers.
string description = 1;

// The URL of the link.
string url = 2;
}

// URL(s) pointing to additional information on handling the current error.
repeated Link links = 1;
}

// Provides a localized error message that is safe to return to the user
// which can be attached to an RPC error.
message LocalizedMessage {
// The locale used following the specification defined at
// http://www.rfc-editor.org/rfc/bcp/bcp47.txt.
// Examples are: "en-US", "fr-CH", "es-MX"
string locale = 1;

// The localized error message in the above locale.
string message = 2;
}
149 changes: 149 additions & 0 deletions tonic-types/src/error_details.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
use super::std_messages::{BadRequest, FieldViolation};

pub(crate) mod vec;

/// Groups the standard error messages structs. Provides associated
/// functions and methods to setup and edit each error message independently.
/// Used when extracting error details from `tonic::Status`, and when
/// creating a `tonic::Status` with error details.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct ErrorDetails {
/// This field stores [`BadRequest`] data, if any.
pub(crate) bad_request: Option<BadRequest>,
}

impl ErrorDetails {
/// Generates an [`ErrorDetails`] struct with all fields set to `None`.
///
/// # Examples
///
/// ```
/// use tonic_types::{ErrorDetails};
///
/// let err_details = ErrorDetails::new();
/// ```
pub fn new() -> Self {
ErrorDetails { bad_request: None }
}

/// Generates an [`ErrorDetails`] struct with [`BadRequest`] details and
/// remaining fields set to `None`.
///
/// # Examples
///
/// ```
/// use tonic_types::{ErrorDetails, FieldViolation};
///
/// let err_details = ErrorDetails::with_bad_request(vec![
/// FieldViolation::new("field_1", "description 1"),
/// FieldViolation::new("field_2", "description 2"),
/// ]);
/// ```
pub fn with_bad_request(field_violations: Vec<FieldViolation>) -> Self {
ErrorDetails {
bad_request: Some(BadRequest::new(field_violations)),
..ErrorDetails::new()
}
}

/// Generates an [`ErrorDetails`] struct with [`BadRequest`] details (one
/// [`FieldViolation`] set) and remaining fields set to `None`.
///
/// # Examples
///
/// ```
/// use tonic_types::{ErrorDetails};
///
/// let err_details = ErrorDetails::with_bad_request_violation(
/// "field",
/// "description",
/// );
/// ```
pub fn with_bad_request_violation(
field: impl Into<String>,
description: impl Into<String>,
) -> Self {
ErrorDetails {
bad_request: Some(BadRequest::with_violation(field, description)),
..ErrorDetails::new()
}
}

/// Get [`BadRequest`] details, if any
pub fn bad_request(&self) -> Option<BadRequest> {
self.bad_request.clone()
}

/// Set [`BadRequest`] details. Can be chained with other `.set_` and
/// `.add_` [`ErrorDetails`] methods.
///
/// # Examples
///
/// ```
/// use tonic_types::{ErrorDetails, FieldViolation};
///
/// let mut err_details = ErrorDetails::new();
///
/// err_details.set_bad_request(vec![
/// FieldViolation::new("field_1", "description 1"),
/// FieldViolation::new("field_2", "description 2"),
/// ]);
/// ```
pub fn set_bad_request(&mut self, violations: Vec<FieldViolation>) -> &mut Self {
self.bad_request = Some(BadRequest::new(violations));
self
}

/// Adds a [`FieldViolation`] to [`BadRequest`] details. Sets
/// [`BadRequest`] details if it is not set yet. Can be chained with other
/// `.set_` and `.add_` [`ErrorDetails`] methods.
///
/// # Examples
///
/// ```
/// use tonic_types::{ErrorDetails};
///
/// let mut err_details = ErrorDetails::new();
///
/// err_details.add_bad_request_violation("field", "description");
/// ```
pub fn add_bad_request_violation(
&mut self,
field: impl Into<String>,
description: impl Into<String>,
) -> &mut Self {
match &mut self.bad_request {
Some(bad_request) => {
bad_request.add_violation(field, description);
}
None => {
self.bad_request = Some(BadRequest::with_violation(field, description));
}
};
self
}

/// Returns `true` if [`BadRequest`] is set and its `field_violations`
/// vector is not empty, otherwise returns `false`.
///
/// # Examples
///
/// ```
/// use tonic_types::{ErrorDetails};
///
/// let mut err_details = ErrorDetails::with_bad_request(vec![]);
///
/// assert_eq!(err_details.has_bad_request_violations(), false);
///
/// err_details.add_bad_request_violation("field", "description");
///
/// assert_eq!(err_details.has_bad_request_violations(), true);
/// ```
pub fn has_bad_request_violations(&self) -> bool {
if let Some(bad_request) = &self.bad_request {
return !bad_request.field_violations.is_empty();
}
false
}
}
16 changes: 16 additions & 0 deletions tonic-types/src/error_details/vec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use super::super::std_messages::BadRequest;

/// Wraps the structs corresponding to the standard error messages, allowing
/// the implementation and handling of vectors containing any of them.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum ErrorDetail {
/// Wraps the [`BadRequest`] struct.
BadRequest(BadRequest),
}

impl From<BadRequest> for ErrorDetail {
fn from(err_detail: BadRequest) -> Self {
ErrorDetail::BadRequest(err_detail)
}
}
Empty file.
276 changes: 276 additions & 0 deletions tonic-types/src/generated/google.rpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
/// The `Status` type defines a logical error model that is suitable for
/// different programming environments, including REST APIs and RPC APIs. It is
/// used by \[gRPC\](<https://github.com/grpc>). Each `Status` message contains
/// three pieces of data: error code, error message, and error details.
///
/// You can find out more about this error model and how to work with it in the
/// [API Design Guide](<https://cloud.google.com/apis/design/errors>).
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Status {
/// The status code, which should be an enum value of \\[google.rpc.Code\]\[google.rpc.Code\\].
#[prost(int32, tag="1")]
pub code: i32,
/// A developer-facing error message, which should be in English. Any
/// user-facing error message should be localized and sent in the
/// \\[google.rpc.Status.details\]\[google.rpc.Status.details\\] field, or localized by the client.
#[prost(string, tag="2")]
pub message: ::prost::alloc::string::String,
/// A list of messages that carry the error details. There is a common set of
/// message types for APIs to use.
#[prost(message, repeated, tag="3")]
pub details: ::prost::alloc::vec::Vec<::prost_types::Any>,
}
/// Describes when the clients can retry a failed request. Clients could ignore
/// the recommendation here or retry when this information is missing from error
/// responses.
///
/// It's always recommended that clients should use exponential backoff when
/// retrying.
///
/// Clients should wait until `retry_delay` amount of time has passed since
/// receiving the error response before retrying. If retrying requests also
/// fail, clients should use an exponential backoff scheme to gradually increase
/// the delay between retries based on `retry_delay`, until either a maximum
/// number of retries have been reached or a maximum retry delay cap has been
/// reached.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RetryInfo {
/// Clients should wait at least this long between retrying the same request.
#[prost(message, optional, tag="1")]
pub retry_delay: ::core::option::Option<::prost_types::Duration>,
}
/// Describes additional debugging info.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct DebugInfo {
/// The stack trace entries indicating where the error occurred.
#[prost(string, repeated, tag="1")]
pub stack_entries: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
/// Additional debugging information provided by the server.
#[prost(string, tag="2")]
pub detail: ::prost::alloc::string::String,
}
/// Describes how a quota check failed.
///
/// For example if a daily limit was exceeded for the calling project,
/// a service could respond with a QuotaFailure detail containing the project
/// id and the description of the quota limit that was exceeded. If the
/// calling project hasn't enabled the service in the developer console, then
/// a service could respond with the project id and set `service_disabled`
/// to true.
///
/// Also see RetryInfo and Help types for other details about handling a
/// quota failure.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct QuotaFailure {
/// Describes all quota violations.
#[prost(message, repeated, tag="1")]
pub violations: ::prost::alloc::vec::Vec<quota_failure::Violation>,
}
/// Nested message and enum types in `QuotaFailure`.
pub mod quota_failure {
/// A message type used to describe a single quota violation. For example, a
/// daily quota or a custom quota that was exceeded.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Violation {
/// The subject on which the quota check failed.
/// For example, "clientip:<ip address of client>" or "project:<Google
/// developer project id>".
#[prost(string, tag="1")]
pub subject: ::prost::alloc::string::String,
/// A description of how the quota check failed. Clients can use this
/// description to find more about the quota configuration in the service's
/// public documentation, or find the relevant quota limit to adjust through
/// developer console.
///
/// For example: "Service disabled" or "Daily Limit for read operations
/// exceeded".
#[prost(string, tag="2")]
pub description: ::prost::alloc::string::String,
}
}
/// Describes the cause of the error with structured details.
///
/// Example of an error when contacting the "pubsub.googleapis.com" API when it
/// is not enabled:
///
/// ```text,json
/// { "reason": "API_DISABLED"
/// "domain": "googleapis.com"
/// "metadata": {
/// "resource": "projects/123",
/// "service": "pubsub.googleapis.com"
/// }
/// }
/// ```
///
/// This response indicates that the pubsub.googleapis.com API is not enabled.
///
/// Example of an error that is returned when attempting to create a Spanner
/// instance in a region that is out of stock:
///
/// ```text,json
/// { "reason": "STOCKOUT"
/// "domain": "spanner.googleapis.com",
/// "metadata": {
/// "availableRegions": "us-central1,us-east2"
/// }
/// }
/// ```
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ErrorInfo {
/// The reason of the error. This is a constant value that identifies the
/// proximate cause of the error. Error reasons are unique within a particular
/// domain of errors. This should be at most 63 characters and match
/// /\\[A-Z0-9\_\\]+/.
#[prost(string, tag="1")]
pub reason: ::prost::alloc::string::String,
/// The logical grouping to which the "reason" belongs. The error domain
/// is typically the registered service name of the tool or product that
/// generates the error. Example: "pubsub.googleapis.com". If the error is
/// generated by some common infrastructure, the error domain must be a
/// globally unique value that identifies the infrastructure. For Google API
/// infrastructure, the error domain is "googleapis.com".
#[prost(string, tag="2")]
pub domain: ::prost::alloc::string::String,
/// Additional structured details about this error.
///
/// Keys should match /\\[a-zA-Z0-9-\_\\]/ and be limited to 64 characters in
/// length. When identifying the current value of an exceeded limit, the units
/// should be contained in the key, not the value. For example, rather than
/// {"instanceLimit": "100/request"}, should be returned as,
/// {"instanceLimitPerRequest": "100"}, if the client exceeds the number of
/// instances that can be created in a single (batch) request.
#[prost(map="string, string", tag="3")]
pub metadata: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>,
}
/// Describes what preconditions have failed.
///
/// For example, if an RPC failed because it required the Terms of Service to be
/// acknowledged, it could list the terms of service violation in the
/// PreconditionFailure message.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PreconditionFailure {
/// Describes all precondition violations.
#[prost(message, repeated, tag="1")]
pub violations: ::prost::alloc::vec::Vec<precondition_failure::Violation>,
}
/// Nested message and enum types in `PreconditionFailure`.
pub mod precondition_failure {
/// A message type used to describe a single precondition failure.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Violation {
/// The type of PreconditionFailure. We recommend using a service-specific
/// enum type to define the supported precondition violation subjects. For
/// example, "TOS" for "Terms of Service violation".
#[prost(string, tag="1")]
pub r#type: ::prost::alloc::string::String,
/// The subject, relative to the type, that failed.
/// For example, "google.com/cloud" relative to the "TOS" type would indicate
/// which terms of service is being referenced.
#[prost(string, tag="2")]
pub subject: ::prost::alloc::string::String,
/// A description of how the precondition failed. Developers can use this
/// description to understand how to fix the failure.
///
/// For example: "Terms of service not accepted".
#[prost(string, tag="3")]
pub description: ::prost::alloc::string::String,
}
}
/// Describes violations in a client request. This error type focuses on the
/// syntactic aspects of the request.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BadRequest {
/// Describes all violations in a client request.
#[prost(message, repeated, tag="1")]
pub field_violations: ::prost::alloc::vec::Vec<bad_request::FieldViolation>,
}
/// Nested message and enum types in `BadRequest`.
pub mod bad_request {
/// A message type used to describe a single bad request field.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FieldViolation {
/// A path leading to a field in the request body. The value will be a
/// sequence of dot-separated identifiers that identify a protocol buffer
/// field. E.g., "field_violations.field" would identify this field.
#[prost(string, tag="1")]
pub field: ::prost::alloc::string::String,
/// A description of why the request element is bad.
#[prost(string, tag="2")]
pub description: ::prost::alloc::string::String,
}
}
/// Contains metadata about the request that clients can attach when filing a bug
/// or providing other forms of feedback.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RequestInfo {
/// An opaque string that should only be interpreted by the service generating
/// it. For example, it can be used to identify requests in the service's logs.
#[prost(string, tag="1")]
pub request_id: ::prost::alloc::string::String,
/// Any data that was used to serve this request. For example, an encrypted
/// stack trace that can be sent back to the service provider for debugging.
#[prost(string, tag="2")]
pub serving_data: ::prost::alloc::string::String,
}
/// Describes the resource that is being accessed.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ResourceInfo {
/// A name for the type of resource being accessed, e.g. "sql table",
/// "cloud storage bucket", "file", "Google calendar"; or the type URL
/// of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic".
#[prost(string, tag="1")]
pub resource_type: ::prost::alloc::string::String,
/// The name of the resource being accessed. For example, a shared calendar
/// name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current
/// error is \\[google.rpc.Code.PERMISSION_DENIED\]\[google.rpc.Code.PERMISSION_DENIED\\].
#[prost(string, tag="2")]
pub resource_name: ::prost::alloc::string::String,
/// The owner of the resource (optional).
/// For example, "user:<owner email>" or "project:<Google developer project
/// id>".
#[prost(string, tag="3")]
pub owner: ::prost::alloc::string::String,
/// Describes what error is encountered when accessing this resource.
/// For example, updating a cloud project may require the `writer` permission
/// on the developer console project.
#[prost(string, tag="4")]
pub description: ::prost::alloc::string::String,
}
/// Provides links to documentation or for performing an out of band action.
///
/// For example, if a quota check failed with an error indicating the calling
/// project hasn't enabled the accessed service, this can contain a URL pointing
/// directly to the right place in the developer console to flip the bit.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Help {
/// URL(s) pointing to additional information on handling the current error.
#[prost(message, repeated, tag="1")]
pub links: ::prost::alloc::vec::Vec<help::Link>,
}
/// Nested message and enum types in `Help`.
pub mod help {
/// Describes a URL link.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Link {
/// Describes what the link offers.
#[prost(string, tag="1")]
pub description: ::prost::alloc::string::String,
/// The URL of the link.
#[prost(string, tag="2")]
pub url: ::prost::alloc::string::String,
}
}
/// Provides a localized error message that is safe to return to the user
/// which can be attached to an RPC error.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct LocalizedMessage {
/// The locale used following the specification defined at
/// <http://www.rfc-editor.org/rfc/bcp/bcp47.txt.>
/// Examples are: "en-US", "fr-CH", "es-MX"
#[prost(string, tag="1")]
pub locale: ::prost::alloc::string::String,
/// The localized error message in the above locale.
#[prost(string, tag="2")]
pub message: ::prost::alloc::string::String,
}
455 changes: 453 additions & 2 deletions tonic-types/src/lib.rs

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions tonic-types/src/std_messages.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod bad_request;

pub use bad_request::{BadRequest, FieldViolation};
183 changes: 183 additions & 0 deletions tonic-types/src/std_messages/bad_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use prost::{DecodeError, Message};
use prost_types::Any;

use super::super::pb;
use super::super::{FromAny, IntoAny};

/// Used at the `field_violations` field of the [`BadRequest`] struct.
/// Describes a single bad request field.
#[derive(Clone, Debug)]
pub struct FieldViolation {
/// Path leading to a field in the request body. Value should be a
/// sequence of dot-separated identifiers that identify a protocol buffer
/// field.
pub field: String,

/// Description of why the field is bad.
pub description: String,
}

impl FieldViolation {
/// Creates a new [`FieldViolation`] struct.
pub fn new(field: impl Into<String>, description: impl Into<String>) -> Self {
FieldViolation {
field: field.into(),
description: description.into(),
}
}
}

/// Used to encode/decode the `BadRequest` standard error message described in
/// [error_details.proto]. Describes violations in a client request. Focuses
/// on the syntactic aspects of the request.
///
/// [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
#[derive(Clone, Debug)]
pub struct BadRequest {
/// Describes all field violations of the request.
pub field_violations: Vec<FieldViolation>,
}

impl BadRequest {
/// Type URL of the `BadRequest` standard error message type.
pub const TYPE_URL: &'static str = "type.googleapis.com/google.rpc.BadRequest";

/// Creates a new [`BadRequest`] struct.
pub fn new(field_violations: Vec<FieldViolation>) -> Self {
BadRequest { field_violations }
}

/// Creates a new [`BadRequest`] struct with a single [`FieldViolation`] in
/// `field_violations`.
pub fn with_violation(field: impl Into<String>, description: impl Into<String>) -> Self {
BadRequest {
field_violations: vec![FieldViolation {
field: field.into(),
description: description.into(),
}],
}
}

/// Adds a [`FieldViolation`] to [`BadRequest`]'s `field_violations`.
pub fn add_violation(
&mut self,
field: impl Into<String>,
description: impl Into<String>,
) -> &mut Self {
self.field_violations.append(&mut vec![FieldViolation {
field: field.into(),
description: description.into(),
}]);
self
}

/// Returns `true` if [`BadRequest`]'s `field_violations` vector is empty,
/// and `false` if it is not.
pub fn is_empty(&self) -> bool {
self.field_violations.is_empty()
}
}

impl IntoAny for BadRequest {
fn into_any(self) -> Any {
let detail_data = pb::BadRequest {
field_violations: self
.field_violations
.into_iter()
.map(|v| pb::bad_request::FieldViolation {
field: v.field,
description: v.description,
})
.collect(),
};

Any {
type_url: BadRequest::TYPE_URL.to_string(),
value: detail_data.encode_to_vec(),
}
}
}

impl FromAny for BadRequest {
fn from_any(any: Any) -> Result<Self, DecodeError> {
let buf: &[u8] = &any.value;
let bad_req = pb::BadRequest::decode(buf)?;

let bad_req = BadRequest {
field_violations: bad_req
.field_violations
.into_iter()
.map(|v| FieldViolation {
field: v.field,
description: v.description,
})
.collect(),
};

Ok(bad_req)
}
}

#[cfg(test)]
mod tests {
use super::super::super::{FromAny, IntoAny};
use super::BadRequest;

#[test]
fn gen_bad_request() {
let mut br_details = BadRequest::new(Vec::new());
let formatted = format!("{:?}", br_details);

let expected = "BadRequest { field_violations: [] }";

assert!(
formatted.eq(expected),
"empty BadRequest differs from expected result"
);

assert!(
br_details.is_empty(),
"empty BadRequest returns 'false' from .is_empty()"
);

br_details
.add_violation("field_a", "description_a")
.add_violation("field_b", "description_b");

let formatted = format!("{:?}", br_details);

let expected_filled = "BadRequest { field_violations: [FieldViolation { field: \"field_a\", description: \"description_a\" }, FieldViolation { field: \"field_b\", description: \"description_b\" }] }";

assert!(
formatted.eq(expected_filled),
"filled BadRequest differs from expected result"
);

assert!(
br_details.is_empty() == false,
"filled BadRequest returns 'true' from .is_empty()"
);

let gen_any = br_details.into_any();
let formatted = format!("{:?}", gen_any);

let expected = "Any { type_url: \"type.googleapis.com/google.rpc.BadRequest\", value: [10, 24, 10, 7, 102, 105, 101, 108, 100, 95, 97, 18, 13, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 95, 97, 10, 24, 10, 7, 102, 105, 101, 108, 100, 95, 98, 18, 13, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 95, 98] }";

assert!(
formatted.eq(expected),
"Any from filled BadRequest differs from expected result"
);

let br_details = match BadRequest::from_any(gen_any) {
Err(error) => panic!("Error generating BadRequest from Any: {:?}", error),
Ok(from_any) => from_any,
};

let formatted = format!("{:?}", br_details);

assert!(
formatted.eq(expected_filled),
"BadRequest from Any differs from expected result"
);
}
}
28 changes: 28 additions & 0 deletions tonic-types/tests/bootstrap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::{path::PathBuf, process::Command};

#[test]
fn bootstrap() {
let iface_files = &["proto/status.proto", "proto/error_details.proto"];
let dirs = &["proto"];

let out_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR"))
.join("src")
.join("generated");

tonic_build::configure()
.out_dir(format!("{}", out_dir.display()))
.compile(iface_files, dirs)
.unwrap();

let status = Command::new("git")
.arg("diff")
.arg("--exit-code")
.arg("--")
.arg(format!("{}", out_dir.display()))
.status()
.unwrap();

if !status.success() {
panic!("You should commit the protobuf files");
}
}
8 changes: 4 additions & 4 deletions tonic/Cargo.toml
Original file line number Diff line number Diff line change
@@ -7,20 +7,20 @@ name = "tonic"
# - Cargo.toml
# - README.md
# - Update CHANGELOG.md.
# - Create "v0.7.x" git tag.
# - Create "v0.8.x" git tag.
authors = ["Lucio Franco <luciofranco14@gmail.com>"]
categories = ["web-programming", "network-programming", "asynchronous"]
description = """
A gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility.
"""
documentation = "https://docs.rs/tonic/0.8.0/tonic/"
documentation = "https://docs.rs/tonic/0.8.2/tonic/"
edition = "2018"
homepage = "https://github.com/hyperium/tonic"
keywords = ["rpc", "grpc", "async", "futures", "protobuf"]
license = "MIT"
readme = "../README.md"
repository = "https://github.com/hyperium/tonic"
version = "0.8.0"
version = "0.8.2"

[features]
codegen = ["async-trait"]
@@ -81,7 +81,7 @@ tokio = {version = "1.0.1", features = ["net"], optional = true}
tokio-stream = "0.1"
tower = {version = "0.4.7", default-features = false, features = ["balance", "buffer", "discover", "limit", "load", "make", "timeout", "util"], optional = true}
tracing-futures = {version = "0.2", optional = true}
axum = {version = "0.5", default_features = false, optional = true}
axum = {version = "0.5.15", default_features = false, optional = true}

# rustls
rustls-pemfile = { version = "1.0", optional = true }
6 changes: 5 additions & 1 deletion tonic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -81,7 +81,7 @@
#![doc(
html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg"
)]
#![doc(html_root_url = "https://docs.rs/tonic/0.8.0")]
#![doc(html_root_url = "https://docs.rs/tonic/0.8.2")]
#![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
@@ -122,3 +122,7 @@ pub(crate) type Error = Box<dyn std::error::Error + Send + Sync>;
#[cfg(feature = "codegen")]
#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
pub mod codegen;

/// `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`]).
/// By default, the Err value is of type [`Status`] but this can be overridden if desired.
pub type Result<T, E = Status> = std::result::Result<T, E>;
11 changes: 6 additions & 5 deletions tonic/src/status.rs
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ use crate::metadata::MetadataMap;
use bytes::Bytes;
use http::header::{HeaderMap, HeaderValue};
use percent_encoding::{percent_decode, percent_encode, AsciiSet, CONTROLS};
use std::{borrow::Cow, error::Error, fmt};
use std::{borrow::Cow, error::Error, fmt, sync::Arc};
use tracing::{debug, trace, warn};

const ENCODING_SET: &AsciiSet = &CONTROLS
@@ -33,6 +33,7 @@ const GRPC_STATUS_DETAILS_HEADER: &str = "grpc-status-details-bin";
/// assert_eq!(status1.code(), Code::InvalidArgument);
/// assert_eq!(status1.code(), status2.code());
/// ```
#[derive(Clone)]
pub struct Status {
/// The gRPC status code, found in the `grpc-status` header.
code: Code,
@@ -45,7 +46,7 @@ pub struct Status {
/// or by `Status` fields above, they will be ignored.
metadata: MetadataMap,
/// Optional underlying error.
source: Option<Box<dyn Error + Send + Sync + 'static>>,
source: Option<Arc<dyn Error + Send + Sync + 'static>>,
}

/// gRPC status codes used by [`Status`].
@@ -318,7 +319,7 @@ impl Status {
pub fn from_error(err: Box<dyn Error + Send + Sync + 'static>) -> Status {
Status::try_from_error(err).unwrap_or_else(|err| {
let mut status = Status::new(Code::Unknown, err.to_string());
status.source = Some(err);
status.source = Some(err.into());
status
})
}
@@ -342,7 +343,7 @@ impl Status {
};

if let Some(mut status) = find_status_in_source_chain(&*err) {
status.source = Some(err);
status.source = Some(err.into());
return Ok(status);
}

@@ -370,7 +371,7 @@ impl Status {
};

let mut status = Self::new(code, format!("h2 protocol error: {}", err));
status.source = Some(err);
status.source = Some(Arc::new(*err));
status
}