Skip to content

Commit 3e40d81

Browse files
authoredSep 21, 2022
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`.
1 parent ee3d0df commit 3e40d81

File tree

11 files changed

+1358
-7
lines changed

11 files changed

+1358
-7
lines changed
 

‎tonic-types/Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ version = "0.6.0"
1717
[dependencies]
1818
prost = "0.11"
1919
prost-types = "0.11"
20+
tonic = {version = "0.8", path = "../tonic"}
2021

21-
[build-dependencies]
22-
prost-build = "0.11"
22+
[dev-dependencies]
23+
tonic-build = {version = "0.8", path = "../tonic-build", features = ["prost"]}

‎tonic-types/build.rs

-3
This file was deleted.

‎tonic-types/proto/error_details.proto

+250
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
package google.rpc;
18+
19+
import "google/protobuf/duration.proto";
20+
21+
option go_package = "google.golang.org/genproto/googleapis/rpc/errdetails;errdetails";
22+
option java_multiple_files = true;
23+
option java_outer_classname = "ErrorDetailsProto";
24+
option java_package = "com.google.rpc";
25+
option objc_class_prefix = "RPC";
26+
27+
// Describes when the clients can retry a failed request. Clients could ignore
28+
// the recommendation here or retry when this information is missing from error
29+
// responses.
30+
//
31+
// It's always recommended that clients should use exponential backoff when
32+
// retrying.
33+
//
34+
// Clients should wait until `retry_delay` amount of time has passed since
35+
// receiving the error response before retrying. If retrying requests also
36+
// fail, clients should use an exponential backoff scheme to gradually increase
37+
// the delay between retries based on `retry_delay`, until either a maximum
38+
// number of retries have been reached or a maximum retry delay cap has been
39+
// reached.
40+
message RetryInfo {
41+
// Clients should wait at least this long between retrying the same request.
42+
google.protobuf.Duration retry_delay = 1;
43+
}
44+
45+
// Describes additional debugging info.
46+
message DebugInfo {
47+
// The stack trace entries indicating where the error occurred.
48+
repeated string stack_entries = 1;
49+
50+
// Additional debugging information provided by the server.
51+
string detail = 2;
52+
}
53+
54+
// Describes how a quota check failed.
55+
//
56+
// For example if a daily limit was exceeded for the calling project,
57+
// a service could respond with a QuotaFailure detail containing the project
58+
// id and the description of the quota limit that was exceeded. If the
59+
// calling project hasn't enabled the service in the developer console, then
60+
// a service could respond with the project id and set `service_disabled`
61+
// to true.
62+
//
63+
// Also see RetryInfo and Help types for other details about handling a
64+
// quota failure.
65+
message QuotaFailure {
66+
// A message type used to describe a single quota violation. For example, a
67+
// daily quota or a custom quota that was exceeded.
68+
message Violation {
69+
// The subject on which the quota check failed.
70+
// For example, "clientip:<ip address of client>" or "project:<Google
71+
// developer project id>".
72+
string subject = 1;
73+
74+
// A description of how the quota check failed. Clients can use this
75+
// description to find more about the quota configuration in the service's
76+
// public documentation, or find the relevant quota limit to adjust through
77+
// developer console.
78+
//
79+
// For example: "Service disabled" or "Daily Limit for read operations
80+
// exceeded".
81+
string description = 2;
82+
}
83+
84+
// Describes all quota violations.
85+
repeated Violation violations = 1;
86+
}
87+
88+
// Describes the cause of the error with structured details.
89+
//
90+
// Example of an error when contacting the "pubsub.googleapis.com" API when it
91+
// is not enabled:
92+
// ```json
93+
// { "reason": "API_DISABLED"
94+
// "domain": "googleapis.com"
95+
// "metadata": {
96+
// "resource": "projects/123",
97+
// "service": "pubsub.googleapis.com"
98+
// }
99+
// }
100+
// ```
101+
// This response indicates that the pubsub.googleapis.com API is not enabled.
102+
//
103+
// Example of an error that is returned when attempting to create a Spanner
104+
// instance in a region that is out of stock:
105+
// ```json
106+
// { "reason": "STOCKOUT"
107+
// "domain": "spanner.googleapis.com",
108+
// "metadata": {
109+
// "availableRegions": "us-central1,us-east2"
110+
// }
111+
// }
112+
// ```
113+
message ErrorInfo {
114+
// The reason of the error. This is a constant value that identifies the
115+
// proximate cause of the error. Error reasons are unique within a particular
116+
// domain of errors. This should be at most 63 characters and match
117+
// /[A-Z0-9_]+/.
118+
string reason = 1;
119+
120+
// The logical grouping to which the "reason" belongs. The error domain
121+
// is typically the registered service name of the tool or product that
122+
// generates the error. Example: "pubsub.googleapis.com". If the error is
123+
// generated by some common infrastructure, the error domain must be a
124+
// globally unique value that identifies the infrastructure. For Google API
125+
// infrastructure, the error domain is "googleapis.com".
126+
string domain = 2;
127+
128+
// Additional structured details about this error.
129+
//
130+
// Keys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in
131+
// length. When identifying the current value of an exceeded limit, the units
132+
// should be contained in the key, not the value. For example, rather than
133+
// {"instanceLimit": "100/request"}, should be returned as,
134+
// {"instanceLimitPerRequest": "100"}, if the client exceeds the number of
135+
// instances that can be created in a single (batch) request.
136+
map<string, string> metadata = 3;
137+
}
138+
139+
// Describes what preconditions have failed.
140+
//
141+
// For example, if an RPC failed because it required the Terms of Service to be
142+
// acknowledged, it could list the terms of service violation in the
143+
// PreconditionFailure message.
144+
message PreconditionFailure {
145+
// A message type used to describe a single precondition failure.
146+
message Violation {
147+
// The type of PreconditionFailure. We recommend using a service-specific
148+
// enum type to define the supported precondition violation subjects. For
149+
// example, "TOS" for "Terms of Service violation".
150+
string type = 1;
151+
152+
// The subject, relative to the type, that failed.
153+
// For example, "google.com/cloud" relative to the "TOS" type would indicate
154+
// which terms of service is being referenced.
155+
string subject = 2;
156+
157+
// A description of how the precondition failed. Developers can use this
158+
// description to understand how to fix the failure.
159+
//
160+
// For example: "Terms of service not accepted".
161+
string description = 3;
162+
}
163+
164+
// Describes all precondition violations.
165+
repeated Violation violations = 1;
166+
}
167+
168+
// Describes violations in a client request. This error type focuses on the
169+
// syntactic aspects of the request.
170+
message BadRequest {
171+
// A message type used to describe a single bad request field.
172+
message FieldViolation {
173+
// A path leading to a field in the request body. The value will be a
174+
// sequence of dot-separated identifiers that identify a protocol buffer
175+
// field. E.g., "field_violations.field" would identify this field.
176+
string field = 1;
177+
178+
// A description of why the request element is bad.
179+
string description = 2;
180+
}
181+
182+
// Describes all violations in a client request.
183+
repeated FieldViolation field_violations = 1;
184+
}
185+
186+
// Contains metadata about the request that clients can attach when filing a bug
187+
// or providing other forms of feedback.
188+
message RequestInfo {
189+
// An opaque string that should only be interpreted by the service generating
190+
// it. For example, it can be used to identify requests in the service's logs.
191+
string request_id = 1;
192+
193+
// Any data that was used to serve this request. For example, an encrypted
194+
// stack trace that can be sent back to the service provider for debugging.
195+
string serving_data = 2;
196+
}
197+
198+
// Describes the resource that is being accessed.
199+
message ResourceInfo {
200+
// A name for the type of resource being accessed, e.g. "sql table",
201+
// "cloud storage bucket", "file", "Google calendar"; or the type URL
202+
// of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic".
203+
string resource_type = 1;
204+
205+
// The name of the resource being accessed. For example, a shared calendar
206+
// name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current
207+
// error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED].
208+
string resource_name = 2;
209+
210+
// The owner of the resource (optional).
211+
// For example, "user:<owner email>" or "project:<Google developer project
212+
// id>".
213+
string owner = 3;
214+
215+
// Describes what error is encountered when accessing this resource.
216+
// For example, updating a cloud project may require the `writer` permission
217+
// on the developer console project.
218+
string description = 4;
219+
}
220+
221+
// Provides links to documentation or for performing an out of band action.
222+
//
223+
// For example, if a quota check failed with an error indicating the calling
224+
// project hasn't enabled the accessed service, this can contain a URL pointing
225+
// directly to the right place in the developer console to flip the bit.
226+
message Help {
227+
// Describes a URL link.
228+
message Link {
229+
// Describes what the link offers.
230+
string description = 1;
231+
232+
// The URL of the link.
233+
string url = 2;
234+
}
235+
236+
// URL(s) pointing to additional information on handling the current error.
237+
repeated Link links = 1;
238+
}
239+
240+
// Provides a localized error message that is safe to return to the user
241+
// which can be attached to an RPC error.
242+
message LocalizedMessage {
243+
// The locale used following the specification defined at
244+
// http://www.rfc-editor.org/rfc/bcp/bcp47.txt.
245+
// Examples are: "en-US", "fr-CH", "es-MX"
246+
string locale = 1;
247+
248+
// The localized error message in the above locale.
249+
string message = 2;
250+
}

‎tonic-types/src/error_details.rs

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use super::std_messages::{BadRequest, FieldViolation};
2+
3+
pub(crate) mod vec;
4+
5+
/// Groups the standard error messages structs. Provides associated
6+
/// functions and methods to setup and edit each error message independently.
7+
/// Used when extracting error details from `tonic::Status`, and when
8+
/// creating a `tonic::Status` with error details.
9+
#[non_exhaustive]
10+
#[derive(Clone, Debug)]
11+
pub struct ErrorDetails {
12+
/// This field stores [`BadRequest`] data, if any.
13+
pub(crate) bad_request: Option<BadRequest>,
14+
}
15+
16+
impl ErrorDetails {
17+
/// Generates an [`ErrorDetails`] struct with all fields set to `None`.
18+
///
19+
/// # Examples
20+
///
21+
/// ```
22+
/// use tonic_types::{ErrorDetails};
23+
///
24+
/// let err_details = ErrorDetails::new();
25+
/// ```
26+
pub fn new() -> Self {
27+
ErrorDetails { bad_request: None }
28+
}
29+
30+
/// Generates an [`ErrorDetails`] struct with [`BadRequest`] details and
31+
/// remaining fields set to `None`.
32+
///
33+
/// # Examples
34+
///
35+
/// ```
36+
/// use tonic_types::{ErrorDetails, FieldViolation};
37+
///
38+
/// let err_details = ErrorDetails::with_bad_request(vec![
39+
/// FieldViolation::new("field_1", "description 1"),
40+
/// FieldViolation::new("field_2", "description 2"),
41+
/// ]);
42+
/// ```
43+
pub fn with_bad_request(field_violations: Vec<FieldViolation>) -> Self {
44+
ErrorDetails {
45+
bad_request: Some(BadRequest::new(field_violations)),
46+
..ErrorDetails::new()
47+
}
48+
}
49+
50+
/// Generates an [`ErrorDetails`] struct with [`BadRequest`] details (one
51+
/// [`FieldViolation`] set) and remaining fields set to `None`.
52+
///
53+
/// # Examples
54+
///
55+
/// ```
56+
/// use tonic_types::{ErrorDetails};
57+
///
58+
/// let err_details = ErrorDetails::with_bad_request_violation(
59+
/// "field",
60+
/// "description",
61+
/// );
62+
/// ```
63+
pub fn with_bad_request_violation(
64+
field: impl Into<String>,
65+
description: impl Into<String>,
66+
) -> Self {
67+
ErrorDetails {
68+
bad_request: Some(BadRequest::with_violation(field, description)),
69+
..ErrorDetails::new()
70+
}
71+
}
72+
73+
/// Get [`BadRequest`] details, if any
74+
pub fn bad_request(&self) -> Option<BadRequest> {
75+
self.bad_request.clone()
76+
}
77+
78+
/// Set [`BadRequest`] details. Can be chained with other `.set_` and
79+
/// `.add_` [`ErrorDetails`] methods.
80+
///
81+
/// # Examples
82+
///
83+
/// ```
84+
/// use tonic_types::{ErrorDetails, FieldViolation};
85+
///
86+
/// let mut err_details = ErrorDetails::new();
87+
///
88+
/// err_details.set_bad_request(vec![
89+
/// FieldViolation::new("field_1", "description 1"),
90+
/// FieldViolation::new("field_2", "description 2"),
91+
/// ]);
92+
/// ```
93+
pub fn set_bad_request(&mut self, violations: Vec<FieldViolation>) -> &mut Self {
94+
self.bad_request = Some(BadRequest::new(violations));
95+
self
96+
}
97+
98+
/// Adds a [`FieldViolation`] to [`BadRequest`] details. Sets
99+
/// [`BadRequest`] details if it is not set yet. Can be chained with other
100+
/// `.set_` and `.add_` [`ErrorDetails`] methods.
101+
///
102+
/// # Examples
103+
///
104+
/// ```
105+
/// use tonic_types::{ErrorDetails};
106+
///
107+
/// let mut err_details = ErrorDetails::new();
108+
///
109+
/// err_details.add_bad_request_violation("field", "description");
110+
/// ```
111+
pub fn add_bad_request_violation(
112+
&mut self,
113+
field: impl Into<String>,
114+
description: impl Into<String>,
115+
) -> &mut Self {
116+
match &mut self.bad_request {
117+
Some(bad_request) => {
118+
bad_request.add_violation(field, description);
119+
}
120+
None => {
121+
self.bad_request = Some(BadRequest::with_violation(field, description));
122+
}
123+
};
124+
self
125+
}
126+
127+
/// Returns `true` if [`BadRequest`] is set and its `field_violations`
128+
/// vector is not empty, otherwise returns `false`.
129+
///
130+
/// # Examples
131+
///
132+
/// ```
133+
/// use tonic_types::{ErrorDetails};
134+
///
135+
/// let mut err_details = ErrorDetails::with_bad_request(vec![]);
136+
///
137+
/// assert_eq!(err_details.has_bad_request_violations(), false);
138+
///
139+
/// err_details.add_bad_request_violation("field", "description");
140+
///
141+
/// assert_eq!(err_details.has_bad_request_violations(), true);
142+
/// ```
143+
pub fn has_bad_request_violations(&self) -> bool {
144+
if let Some(bad_request) = &self.bad_request {
145+
return !bad_request.field_violations.is_empty();
146+
}
147+
false
148+
}
149+
}

‎tonic-types/src/error_details/vec.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use super::super::std_messages::BadRequest;
2+
3+
/// Wraps the structs corresponding to the standard error messages, allowing
4+
/// the implementation and handling of vectors containing any of them.
5+
#[non_exhaustive]
6+
#[derive(Clone, Debug)]
7+
pub enum ErrorDetail {
8+
/// Wraps the [`BadRequest`] struct.
9+
BadRequest(BadRequest),
10+
}
11+
12+
impl From<BadRequest> for ErrorDetail {
13+
fn from(err_detail: BadRequest) -> Self {
14+
ErrorDetail::BadRequest(err_detail)
15+
}
16+
}

‎tonic-types/src/generated/google.protobuf.rs

Whitespace-only changes.
+273
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
/// The `Status` type defines a logical error model that is suitable for
2+
/// different programming environments, including REST APIs and RPC APIs. It is
3+
/// used by \[gRPC\](<https://github.com/grpc>). Each `Status` message contains
4+
/// three pieces of data: error code, error message, and error details.
5+
///
6+
/// You can find out more about this error model and how to work with it in the
7+
/// [API Design Guide](<https://cloud.google.com/apis/design/errors>).
8+
#[derive(Clone, PartialEq, ::prost::Message)]
9+
pub struct Status {
10+
/// The status code, which should be an enum value of \[google.rpc.Code][google.rpc.Code\].
11+
#[prost(int32, tag="1")]
12+
pub code: i32,
13+
/// A developer-facing error message, which should be in English. Any
14+
/// user-facing error message should be localized and sent in the
15+
/// \[google.rpc.Status.details][google.rpc.Status.details\] field, or localized by the client.
16+
#[prost(string, tag="2")]
17+
pub message: ::prost::alloc::string::String,
18+
/// A list of messages that carry the error details. There is a common set of
19+
/// message types for APIs to use.
20+
#[prost(message, repeated, tag="3")]
21+
pub details: ::prost::alloc::vec::Vec<::prost_types::Any>,
22+
}
23+
/// Describes when the clients can retry a failed request. Clients could ignore
24+
/// the recommendation here or retry when this information is missing from error
25+
/// responses.
26+
///
27+
/// It's always recommended that clients should use exponential backoff when
28+
/// retrying.
29+
///
30+
/// Clients should wait until `retry_delay` amount of time has passed since
31+
/// receiving the error response before retrying. If retrying requests also
32+
/// fail, clients should use an exponential backoff scheme to gradually increase
33+
/// the delay between retries based on `retry_delay`, until either a maximum
34+
/// number of retries have been reached or a maximum retry delay cap has been
35+
/// reached.
36+
#[derive(Clone, PartialEq, ::prost::Message)]
37+
pub struct RetryInfo {
38+
/// Clients should wait at least this long between retrying the same request.
39+
#[prost(message, optional, tag="1")]
40+
pub retry_delay: ::core::option::Option<::prost_types::Duration>,
41+
}
42+
/// Describes additional debugging info.
43+
#[derive(Clone, PartialEq, ::prost::Message)]
44+
pub struct DebugInfo {
45+
/// The stack trace entries indicating where the error occurred.
46+
#[prost(string, repeated, tag="1")]
47+
pub stack_entries: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
48+
/// Additional debugging information provided by the server.
49+
#[prost(string, tag="2")]
50+
pub detail: ::prost::alloc::string::String,
51+
}
52+
/// Describes how a quota check failed.
53+
///
54+
/// For example if a daily limit was exceeded for the calling project,
55+
/// a service could respond with a QuotaFailure detail containing the project
56+
/// id and the description of the quota limit that was exceeded. If the
57+
/// calling project hasn't enabled the service in the developer console, then
58+
/// a service could respond with the project id and set `service_disabled`
59+
/// to true.
60+
///
61+
/// Also see RetryInfo and Help types for other details about handling a
62+
/// quota failure.
63+
#[derive(Clone, PartialEq, ::prost::Message)]
64+
pub struct QuotaFailure {
65+
/// Describes all quota violations.
66+
#[prost(message, repeated, tag="1")]
67+
pub violations: ::prost::alloc::vec::Vec<quota_failure::Violation>,
68+
}
69+
/// Nested message and enum types in `QuotaFailure`.
70+
pub mod quota_failure {
71+
/// A message type used to describe a single quota violation. For example, a
72+
/// daily quota or a custom quota that was exceeded.
73+
#[derive(Clone, PartialEq, ::prost::Message)]
74+
pub struct Violation {
75+
/// The subject on which the quota check failed.
76+
/// For example, "clientip:<ip address of client>" or "project:<Google
77+
/// developer project id>".
78+
#[prost(string, tag="1")]
79+
pub subject: ::prost::alloc::string::String,
80+
/// A description of how the quota check failed. Clients can use this
81+
/// description to find more about the quota configuration in the service's
82+
/// public documentation, or find the relevant quota limit to adjust through
83+
/// developer console.
84+
///
85+
/// For example: "Service disabled" or "Daily Limit for read operations
86+
/// exceeded".
87+
#[prost(string, tag="2")]
88+
pub description: ::prost::alloc::string::String,
89+
}
90+
}
91+
/// Describes the cause of the error with structured details.
92+
///
93+
/// Example of an error when contacting the "pubsub.googleapis.com" API when it
94+
/// is not enabled:
95+
/// ```json
96+
/// { "reason": "API_DISABLED"
97+
/// "domain": "googleapis.com"
98+
/// "metadata": {
99+
/// "resource": "projects/123",
100+
/// "service": "pubsub.googleapis.com"
101+
/// }
102+
/// }
103+
/// ```
104+
/// This response indicates that the pubsub.googleapis.com API is not enabled.
105+
///
106+
/// Example of an error that is returned when attempting to create a Spanner
107+
/// instance in a region that is out of stock:
108+
/// ```json
109+
/// { "reason": "STOCKOUT"
110+
/// "domain": "spanner.googleapis.com",
111+
/// "metadata": {
112+
/// "availableRegions": "us-central1,us-east2"
113+
/// }
114+
/// }
115+
/// ```
116+
#[derive(Clone, PartialEq, ::prost::Message)]
117+
pub struct ErrorInfo {
118+
/// The reason of the error. This is a constant value that identifies the
119+
/// proximate cause of the error. Error reasons are unique within a particular
120+
/// domain of errors. This should be at most 63 characters and match
121+
/// /\[A-Z0-9_\]+/.
122+
#[prost(string, tag="1")]
123+
pub reason: ::prost::alloc::string::String,
124+
/// The logical grouping to which the "reason" belongs. The error domain
125+
/// is typically the registered service name of the tool or product that
126+
/// generates the error. Example: "pubsub.googleapis.com". If the error is
127+
/// generated by some common infrastructure, the error domain must be a
128+
/// globally unique value that identifies the infrastructure. For Google API
129+
/// infrastructure, the error domain is "googleapis.com".
130+
#[prost(string, tag="2")]
131+
pub domain: ::prost::alloc::string::String,
132+
/// Additional structured details about this error.
133+
///
134+
/// Keys should match /\[a-zA-Z0-9-_\]/ and be limited to 64 characters in
135+
/// length. When identifying the current value of an exceeded limit, the units
136+
/// should be contained in the key, not the value. For example, rather than
137+
/// {"instanceLimit": "100/request"}, should be returned as,
138+
/// {"instanceLimitPerRequest": "100"}, if the client exceeds the number of
139+
/// instances that can be created in a single (batch) request.
140+
#[prost(map="string, string", tag="3")]
141+
pub metadata: ::std::collections::HashMap<::prost::alloc::string::String, ::prost::alloc::string::String>,
142+
}
143+
/// Describes what preconditions have failed.
144+
///
145+
/// For example, if an RPC failed because it required the Terms of Service to be
146+
/// acknowledged, it could list the terms of service violation in the
147+
/// PreconditionFailure message.
148+
#[derive(Clone, PartialEq, ::prost::Message)]
149+
pub struct PreconditionFailure {
150+
/// Describes all precondition violations.
151+
#[prost(message, repeated, tag="1")]
152+
pub violations: ::prost::alloc::vec::Vec<precondition_failure::Violation>,
153+
}
154+
/// Nested message and enum types in `PreconditionFailure`.
155+
pub mod precondition_failure {
156+
/// A message type used to describe a single precondition failure.
157+
#[derive(Clone, PartialEq, ::prost::Message)]
158+
pub struct Violation {
159+
/// The type of PreconditionFailure. We recommend using a service-specific
160+
/// enum type to define the supported precondition violation subjects. For
161+
/// example, "TOS" for "Terms of Service violation".
162+
#[prost(string, tag="1")]
163+
pub r#type: ::prost::alloc::string::String,
164+
/// The subject, relative to the type, that failed.
165+
/// For example, "google.com/cloud" relative to the "TOS" type would indicate
166+
/// which terms of service is being referenced.
167+
#[prost(string, tag="2")]
168+
pub subject: ::prost::alloc::string::String,
169+
/// A description of how the precondition failed. Developers can use this
170+
/// description to understand how to fix the failure.
171+
///
172+
/// For example: "Terms of service not accepted".
173+
#[prost(string, tag="3")]
174+
pub description: ::prost::alloc::string::String,
175+
}
176+
}
177+
/// Describes violations in a client request. This error type focuses on the
178+
/// syntactic aspects of the request.
179+
#[derive(Clone, PartialEq, ::prost::Message)]
180+
pub struct BadRequest {
181+
/// Describes all violations in a client request.
182+
#[prost(message, repeated, tag="1")]
183+
pub field_violations: ::prost::alloc::vec::Vec<bad_request::FieldViolation>,
184+
}
185+
/// Nested message and enum types in `BadRequest`.
186+
pub mod bad_request {
187+
/// A message type used to describe a single bad request field.
188+
#[derive(Clone, PartialEq, ::prost::Message)]
189+
pub struct FieldViolation {
190+
/// A path leading to a field in the request body. The value will be a
191+
/// sequence of dot-separated identifiers that identify a protocol buffer
192+
/// field. E.g., "field_violations.field" would identify this field.
193+
#[prost(string, tag="1")]
194+
pub field: ::prost::alloc::string::String,
195+
/// A description of why the request element is bad.
196+
#[prost(string, tag="2")]
197+
pub description: ::prost::alloc::string::String,
198+
}
199+
}
200+
/// Contains metadata about the request that clients can attach when filing a bug
201+
/// or providing other forms of feedback.
202+
#[derive(Clone, PartialEq, ::prost::Message)]
203+
pub struct RequestInfo {
204+
/// An opaque string that should only be interpreted by the service generating
205+
/// it. For example, it can be used to identify requests in the service's logs.
206+
#[prost(string, tag="1")]
207+
pub request_id: ::prost::alloc::string::String,
208+
/// Any data that was used to serve this request. For example, an encrypted
209+
/// stack trace that can be sent back to the service provider for debugging.
210+
#[prost(string, tag="2")]
211+
pub serving_data: ::prost::alloc::string::String,
212+
}
213+
/// Describes the resource that is being accessed.
214+
#[derive(Clone, PartialEq, ::prost::Message)]
215+
pub struct ResourceInfo {
216+
/// A name for the type of resource being accessed, e.g. "sql table",
217+
/// "cloud storage bucket", "file", "Google calendar"; or the type URL
218+
/// of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic".
219+
#[prost(string, tag="1")]
220+
pub resource_type: ::prost::alloc::string::String,
221+
/// The name of the resource being accessed. For example, a shared calendar
222+
/// name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current
223+
/// error is \[google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED\].
224+
#[prost(string, tag="2")]
225+
pub resource_name: ::prost::alloc::string::String,
226+
/// The owner of the resource (optional).
227+
/// For example, "user:<owner email>" or "project:<Google developer project
228+
/// id>".
229+
#[prost(string, tag="3")]
230+
pub owner: ::prost::alloc::string::String,
231+
/// Describes what error is encountered when accessing this resource.
232+
/// For example, updating a cloud project may require the `writer` permission
233+
/// on the developer console project.
234+
#[prost(string, tag="4")]
235+
pub description: ::prost::alloc::string::String,
236+
}
237+
/// Provides links to documentation or for performing an out of band action.
238+
///
239+
/// For example, if a quota check failed with an error indicating the calling
240+
/// project hasn't enabled the accessed service, this can contain a URL pointing
241+
/// directly to the right place in the developer console to flip the bit.
242+
#[derive(Clone, PartialEq, ::prost::Message)]
243+
pub struct Help {
244+
/// URL(s) pointing to additional information on handling the current error.
245+
#[prost(message, repeated, tag="1")]
246+
pub links: ::prost::alloc::vec::Vec<help::Link>,
247+
}
248+
/// Nested message and enum types in `Help`.
249+
pub mod help {
250+
/// Describes a URL link.
251+
#[derive(Clone, PartialEq, ::prost::Message)]
252+
pub struct Link {
253+
/// Describes what the link offers.
254+
#[prost(string, tag="1")]
255+
pub description: ::prost::alloc::string::String,
256+
/// The URL of the link.
257+
#[prost(string, tag="2")]
258+
pub url: ::prost::alloc::string::String,
259+
}
260+
}
261+
/// Provides a localized error message that is safe to return to the user
262+
/// which can be attached to an RPC error.
263+
#[derive(Clone, PartialEq, ::prost::Message)]
264+
pub struct LocalizedMessage {
265+
/// The locale used following the specification defined at
266+
/// <http://www.rfc-editor.org/rfc/bcp/bcp47.txt.>
267+
/// Examples are: "en-US", "fr-CH", "es-MX"
268+
#[prost(string, tag="1")]
269+
pub locale: ::prost::alloc::string::String,
270+
/// The localized error message in the above locale.
271+
#[prost(string, tag="2")]
272+
pub message: ::prost::alloc::string::String,
273+
}

‎tonic-types/src/lib.rs

+453-2
Large diffs are not rendered by default.

‎tonic-types/src/std_messages.rs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod bad_request;
2+
3+
pub use bad_request::{BadRequest, FieldViolation};
+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
use prost::{DecodeError, Message};
2+
use prost_types::Any;
3+
4+
use super::super::pb;
5+
use super::super::{FromAny, IntoAny};
6+
7+
/// Used at the `field_violations` field of the [`BadRequest`] struct.
8+
/// Describes a single bad request field.
9+
#[derive(Clone, Debug)]
10+
pub struct FieldViolation {
11+
/// Path leading to a field in the request body. Value should be a
12+
/// sequence of dot-separated identifiers that identify a protocol buffer
13+
/// field.
14+
pub field: String,
15+
16+
/// Description of why the field is bad.
17+
pub description: String,
18+
}
19+
20+
impl FieldViolation {
21+
/// Creates a new [`FieldViolation`] struct.
22+
pub fn new(field: impl Into<String>, description: impl Into<String>) -> Self {
23+
FieldViolation {
24+
field: field.into(),
25+
description: description.into(),
26+
}
27+
}
28+
}
29+
30+
/// Used to encode/decode the `BadRequest` standard error message described in
31+
/// [error_details.proto]. Describes violations in a client request. Focuses
32+
/// on the syntactic aspects of the request.
33+
///
34+
/// [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
35+
#[derive(Clone, Debug)]
36+
pub struct BadRequest {
37+
/// Describes all field violations of the request.
38+
pub field_violations: Vec<FieldViolation>,
39+
}
40+
41+
impl BadRequest {
42+
/// Type URL of the `BadRequest` standard error message type.
43+
pub const TYPE_URL: &'static str = "type.googleapis.com/google.rpc.BadRequest";
44+
45+
/// Creates a new [`BadRequest`] struct.
46+
pub fn new(field_violations: Vec<FieldViolation>) -> Self {
47+
BadRequest { field_violations }
48+
}
49+
50+
/// Creates a new [`BadRequest`] struct with a single [`FieldViolation`] in
51+
/// `field_violations`.
52+
pub fn with_violation(field: impl Into<String>, description: impl Into<String>) -> Self {
53+
BadRequest {
54+
field_violations: vec![FieldViolation {
55+
field: field.into(),
56+
description: description.into(),
57+
}],
58+
}
59+
}
60+
61+
/// Adds a [`FieldViolation`] to [`BadRequest`]'s `field_violations`.
62+
pub fn add_violation(
63+
&mut self,
64+
field: impl Into<String>,
65+
description: impl Into<String>,
66+
) -> &mut Self {
67+
self.field_violations.append(&mut vec![FieldViolation {
68+
field: field.into(),
69+
description: description.into(),
70+
}]);
71+
self
72+
}
73+
74+
/// Returns `true` if [`BadRequest`]'s `field_violations` vector is empty,
75+
/// and `false` if it is not.
76+
pub fn is_empty(&self) -> bool {
77+
self.field_violations.is_empty()
78+
}
79+
}
80+
81+
impl IntoAny for BadRequest {
82+
fn into_any(self) -> Any {
83+
let detail_data = pb::BadRequest {
84+
field_violations: self
85+
.field_violations
86+
.into_iter()
87+
.map(|v| pb::bad_request::FieldViolation {
88+
field: v.field,
89+
description: v.description,
90+
})
91+
.collect(),
92+
};
93+
94+
Any {
95+
type_url: BadRequest::TYPE_URL.to_string(),
96+
value: detail_data.encode_to_vec(),
97+
}
98+
}
99+
}
100+
101+
impl FromAny for BadRequest {
102+
fn from_any(any: Any) -> Result<Self, DecodeError> {
103+
let buf: &[u8] = &any.value;
104+
let bad_req = pb::BadRequest::decode(buf)?;
105+
106+
let bad_req = BadRequest {
107+
field_violations: bad_req
108+
.field_violations
109+
.into_iter()
110+
.map(|v| FieldViolation {
111+
field: v.field,
112+
description: v.description,
113+
})
114+
.collect(),
115+
};
116+
117+
Ok(bad_req)
118+
}
119+
}
120+
121+
#[cfg(test)]
122+
mod tests {
123+
use super::super::super::{FromAny, IntoAny};
124+
use super::BadRequest;
125+
126+
#[test]
127+
fn gen_bad_request() {
128+
let mut br_details = BadRequest::new(Vec::new());
129+
let formatted = format!("{:?}", br_details);
130+
131+
let expected = "BadRequest { field_violations: [] }";
132+
133+
assert!(
134+
formatted.eq(expected),
135+
"empty BadRequest differs from expected result"
136+
);
137+
138+
assert!(
139+
br_details.is_empty(),
140+
"empty BadRequest returns 'false' from .is_empty()"
141+
);
142+
143+
br_details
144+
.add_violation("field_a", "description_a")
145+
.add_violation("field_b", "description_b");
146+
147+
let formatted = format!("{:?}", br_details);
148+
149+
let expected_filled = "BadRequest { field_violations: [FieldViolation { field: \"field_a\", description: \"description_a\" }, FieldViolation { field: \"field_b\", description: \"description_b\" }] }";
150+
151+
assert!(
152+
formatted.eq(expected_filled),
153+
"filled BadRequest differs from expected result"
154+
);
155+
156+
assert!(
157+
br_details.is_empty() == false,
158+
"filled BadRequest returns 'true' from .is_empty()"
159+
);
160+
161+
let gen_any = br_details.into_any();
162+
let formatted = format!("{:?}", gen_any);
163+
164+
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] }";
165+
166+
assert!(
167+
formatted.eq(expected),
168+
"Any from filled BadRequest differs from expected result"
169+
);
170+
171+
let br_details = match BadRequest::from_any(gen_any) {
172+
Err(error) => panic!("Error generating BadRequest from Any: {:?}", error),
173+
Ok(from_any) => from_any,
174+
};
175+
176+
let formatted = format!("{:?}", br_details);
177+
178+
assert!(
179+
formatted.eq(expected_filled),
180+
"BadRequest from Any differs from expected result"
181+
);
182+
}
183+
}

‎tonic-types/tests/bootstrap.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use std::{path::PathBuf, process::Command};
2+
3+
#[test]
4+
fn bootstrap() {
5+
let iface_files = &["proto/status.proto", "proto/error_details.proto"];
6+
let dirs = &["proto"];
7+
8+
let out_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR"))
9+
.join("src")
10+
.join("generated");
11+
12+
tonic_build::configure()
13+
.out_dir(format!("{}", out_dir.display()))
14+
.compile(iface_files, dirs)
15+
.unwrap();
16+
17+
let status = Command::new("git")
18+
.arg("diff")
19+
.arg("--exit-code")
20+
.arg("--")
21+
.arg(format!("{}", out_dir.display()))
22+
.status()
23+
.unwrap();
24+
25+
if !status.success() {
26+
panic!("You should commit the protobuf files");
27+
}
28+
}

0 commit comments

Comments
 (0)
Please sign in to comment.