Skip to content

Commit 7eeda24

Browse files
flemosrLucioFranco
andauthoredFeb 21, 2023
feat(types): Add gRPC Richer Error Model support (ResourceInfo) (#1282)
* types: add support for `ResourceInfo` error message type Following implementation at flemosr/tonic-richer-error. * types: update doc comment examples --------- Co-authored-by: Lucio Franco <luciofranco14@gmail.com>
1 parent 2888ac9 commit 7eeda24

File tree

6 files changed

+277
-18
lines changed

6 files changed

+277
-18
lines changed
 

‎tonic-types/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ mod richer_error;
4848
pub use richer_error::{
4949
BadRequest, DebugInfo, ErrorDetail, ErrorDetails, ErrorInfo, FieldViolation,
5050
PreconditionFailure, PreconditionViolation, QuotaFailure, QuotaViolation, RequestInfo,
51-
RetryInfo, StatusExt,
51+
ResourceInfo, RetryInfo, StatusExt,
5252
};
5353

5454
mod sealed {

‎tonic-types/src/richer_error/error_details/mod.rs

+69-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::{collections::HashMap, time};
22

33
use super::std_messages::{
44
BadRequest, DebugInfo, ErrorInfo, FieldViolation, PreconditionFailure, PreconditionViolation,
5-
QuotaFailure, QuotaViolation, RequestInfo, RetryInfo,
5+
QuotaFailure, QuotaViolation, RequestInfo, ResourceInfo, RetryInfo,
66
};
77

88
pub(crate) mod vec;
@@ -34,6 +34,9 @@ pub struct ErrorDetails {
3434

3535
/// This field stores [`RequestInfo`] data, if any.
3636
pub(crate) request_info: Option<RequestInfo>,
37+
38+
/// This field stores [`ResourceInfo`] data, if any.
39+
pub(crate) resource_info: Option<ResourceInfo>,
3740
}
3841

3942
impl ErrorDetails {
@@ -276,6 +279,38 @@ impl ErrorDetails {
276279
}
277280
}
278281

282+
/// Generates an [`ErrorDetails`] struct with [`ResourceInfo`] details and
283+
/// remaining fields set to `None`.
284+
///
285+
/// # Examples
286+
///
287+
/// ```
288+
/// use tonic_types::ErrorDetails;
289+
///
290+
/// let err_details = ErrorDetails::with_resource_info(
291+
/// "res_type",
292+
/// "res_name",
293+
/// "owner",
294+
/// "description",
295+
/// );
296+
/// ```
297+
pub fn with_resource_info(
298+
resource_type: impl Into<String>,
299+
resource_name: impl Into<String>,
300+
owner: impl Into<String>,
301+
description: impl Into<String>,
302+
) -> Self {
303+
ErrorDetails {
304+
resource_info: Some(ResourceInfo::new(
305+
resource_type,
306+
resource_name,
307+
owner,
308+
description,
309+
)),
310+
..ErrorDetails::new()
311+
}
312+
}
313+
279314
/// Get [`RetryInfo`] details, if any.
280315
pub fn retry_info(&self) -> Option<RetryInfo> {
281316
self.retry_info.clone()
@@ -311,6 +346,11 @@ impl ErrorDetails {
311346
self.request_info.clone()
312347
}
313348

349+
/// Get [`ResourceInfo`] details, if any.
350+
pub fn resource_info(&self) -> Option<ResourceInfo> {
351+
self.resource_info.clone()
352+
}
353+
314354
/// Set [`RetryInfo`] details. Can be chained with other `.set_` and
315355
/// `.add_` [`ErrorDetails`] methods.
316356
///
@@ -638,4 +678,32 @@ impl ErrorDetails {
638678
self.request_info = Some(RequestInfo::new(request_id, serving_data));
639679
self
640680
}
681+
682+
/// Set [`ResourceInfo`] details. Can be chained with other `.set_` and
683+
/// `.add_` [`ErrorDetails`] methods.
684+
///
685+
/// # Examples
686+
///
687+
/// ```
688+
/// use tonic_types::ErrorDetails;
689+
///
690+
/// let mut err_details = ErrorDetails::new();
691+
///
692+
/// err_details.set_resource_info("res_type", "res_name", "owner", "description");
693+
/// ```
694+
pub fn set_resource_info(
695+
&mut self,
696+
resource_type: impl Into<String>,
697+
resource_name: impl Into<String>,
698+
owner: impl Into<String>,
699+
description: impl Into<String>,
700+
) -> &mut Self {
701+
self.resource_info = Some(ResourceInfo::new(
702+
resource_type,
703+
resource_name,
704+
owner,
705+
description,
706+
));
707+
self
708+
}
641709
}

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

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::super::std_messages::{
2-
BadRequest, DebugInfo, ErrorInfo, PreconditionFailure, QuotaFailure, RequestInfo, RetryInfo,
2+
BadRequest, DebugInfo, ErrorInfo, PreconditionFailure, QuotaFailure, RequestInfo, ResourceInfo,
3+
RetryInfo,
34
};
45

56
/// Wraps the structs corresponding to the standard error messages, allowing
@@ -27,6 +28,9 @@ pub enum ErrorDetail {
2728

2829
/// Wraps the [`RequestInfo`] struct.
2930
RequestInfo(RequestInfo),
31+
32+
/// Wraps the [`ResourceInfo`] struct.
33+
ResourceInfo(ResourceInfo),
3034
}
3135

3236
impl From<RetryInfo> for ErrorDetail {
@@ -70,3 +74,9 @@ impl From<RequestInfo> for ErrorDetail {
7074
ErrorDetail::RequestInfo(err_detail)
7175
}
7276
}
77+
78+
impl From<ResourceInfo> for ErrorDetail {
79+
fn from(err_detail: ResourceInfo) -> Self {
80+
ErrorDetail::ResourceInfo(err_detail)
81+
}
82+
}

‎tonic-types/src/richer_error/mod.rs

+63-15
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use super::pb;
1313
pub use error_details::{vec::ErrorDetail, ErrorDetails};
1414
pub use std_messages::{
1515
BadRequest, DebugInfo, ErrorInfo, FieldViolation, PreconditionFailure, PreconditionViolation,
16-
QuotaFailure, QuotaViolation, RequestInfo, RetryInfo,
16+
QuotaFailure, QuotaViolation, RequestInfo, ResourceInfo, RetryInfo,
1717
};
1818

1919
trait IntoAny {
@@ -153,12 +153,13 @@ pub trait StatusExt: crate::sealed::Sealed {
153153
/// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
154154
/// match req_result {
155155
/// Ok(_) => {},
156-
/// Err(status) => {
157-
/// let err_details = status.get_error_details();
158-
/// if let Some(bad_request) = err_details.bad_request() {
159-
/// // Handle bad_request details
156+
/// Err(status) => match status.check_error_details() {
157+
/// Ok(err_details) => {
158+
/// // Handle extracted details
159+
/// }
160+
/// Err(decode_error) => {
161+
/// // Handle decode_error
160162
/// }
161-
/// // ...
162163
/// }
163164
/// };
164165
/// }
@@ -201,19 +202,17 @@ pub trait StatusExt: crate::sealed::Sealed {
201202
///
202203
/// ```
203204
/// use tonic::{Status, Response};
204-
/// use tonic_types::{ErrorDetail, StatusExt};
205+
/// use tonic_types::StatusExt;
205206
///
206207
/// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
207208
/// match req_result {
208209
/// Ok(_) => {},
209-
/// Err(status) => {
210-
/// match status.check_error_details_vec() {
211-
/// Ok(err_details) => {
212-
/// // Handle extracted details
213-
/// }
214-
/// Err(decode_error) => {
215-
/// // Handle decode_error
216-
/// }
210+
/// Err(status) => match status.check_error_details_vec() {
211+
/// Ok(err_details) => {
212+
/// // Handle extracted details
213+
/// }
214+
/// Err(decode_error) => {
215+
/// // Handle decode_error
217216
/// }
218217
/// }
219218
/// };
@@ -403,6 +402,28 @@ pub trait StatusExt: crate::sealed::Sealed {
403402
/// }
404403
/// ```
405404
fn get_details_request_info(&self) -> Option<RequestInfo>;
405+
406+
/// Get first [`ResourceInfo`] details found on `tonic::Status`, if any.
407+
/// If some `prost::DecodeError` occurs, returns `None`.
408+
///
409+
/// # Examples
410+
///
411+
/// ```
412+
/// use tonic::{Status, Response};
413+
/// use tonic_types::StatusExt;
414+
///
415+
/// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
416+
/// match req_result {
417+
/// Ok(_) => {},
418+
/// Err(status) => {
419+
/// if let Some(resource_info) = status.get_details_resource_info() {
420+
/// // Handle resource_info details
421+
/// }
422+
/// }
423+
/// };
424+
/// }
425+
/// ```
426+
fn get_details_resource_info(&self) -> Option<ResourceInfo>;
406427
}
407428

408429
impl crate::sealed::Sealed for tonic::Status {}
@@ -446,6 +467,10 @@ impl StatusExt for tonic::Status {
446467
conv_details.push(request_info.into_any());
447468
}
448469

470+
if let Some(resource_info) = details.resource_info {
471+
conv_details.push(resource_info.into_any());
472+
}
473+
449474
let details = gen_details_bytes(code, &message, conv_details);
450475

451476
tonic::Status::with_details_and_metadata(code, message, details, metadata)
@@ -488,6 +513,9 @@ impl StatusExt for tonic::Status {
488513
ErrorDetail::RequestInfo(req_info) => {
489514
conv_details.push(req_info.into_any());
490515
}
516+
ErrorDetail::ResourceInfo(res_info) => {
517+
conv_details.push(res_info.into_any());
518+
}
491519
}
492520
}
493521

@@ -537,6 +565,9 @@ impl StatusExt for tonic::Status {
537565
RequestInfo::TYPE_URL => {
538566
details.request_info = Some(RequestInfo::from_any(any)?);
539567
}
568+
ResourceInfo::TYPE_URL => {
569+
details.resource_info = Some(ResourceInfo::from_any(any)?);
570+
}
540571
_ => {}
541572
}
542573
}
@@ -576,6 +607,9 @@ impl StatusExt for tonic::Status {
576607
RequestInfo::TYPE_URL => {
577608
details.push(RequestInfo::from_any(any)?.into());
578609
}
610+
ResourceInfo::TYPE_URL => {
611+
details.push(ResourceInfo::from_any(any)?.into());
612+
}
579613
_ => {}
580614
}
581615
}
@@ -684,6 +718,20 @@ impl StatusExt for tonic::Status {
684718

685719
None
686720
}
721+
722+
fn get_details_resource_info(&self) -> Option<ResourceInfo> {
723+
let status = pb::Status::decode(self.details()).ok()?;
724+
725+
for any in status.details.into_iter() {
726+
if any.type_url.as_str() == ResourceInfo::TYPE_URL {
727+
if let Ok(detail) = ResourceInfo::from_any(any) {
728+
return Some(detail);
729+
}
730+
}
731+
}
732+
733+
None
734+
}
687735
}
688736

689737
#[cfg(test)]

‎tonic-types/src/richer_error/std_messages/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ pub use bad_request::{BadRequest, FieldViolation};
2525
mod request_info;
2626

2727
pub use request_info::RequestInfo;
28+
29+
mod resource_info;
30+
31+
pub use resource_info::ResourceInfo;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use prost::{DecodeError, Message};
2+
use prost_types::Any;
3+
4+
use super::super::{pb, FromAny, IntoAny};
5+
6+
/// Used to encode/decode the `ResourceInfo` standard error message described
7+
/// in [error_details.proto]. Describes the resource that is being accessed.
8+
///
9+
/// [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
10+
#[derive(Clone, Debug)]
11+
pub struct ResourceInfo {
12+
/// Type of resource being accessed.
13+
pub resource_type: String,
14+
15+
/// Name of the resource being accessed.
16+
pub resource_name: String,
17+
18+
/// The owner of the resource (optional).
19+
pub owner: String,
20+
21+
/// Describes the error encountered when accessing the resource.
22+
pub description: String,
23+
}
24+
25+
impl ResourceInfo {
26+
/// Type URL of the `ResourceInfo` standard error message type.
27+
pub const TYPE_URL: &'static str = "type.googleapis.com/google.rpc.ResourceInfo";
28+
29+
/// Creates a new [`ResourceInfo`] struct.
30+
pub fn new(
31+
resource_type: impl Into<String>,
32+
resource_name: impl Into<String>,
33+
owner: impl Into<String>,
34+
description: impl Into<String>,
35+
) -> Self {
36+
ResourceInfo {
37+
resource_type: resource_type.into(),
38+
resource_name: resource_name.into(),
39+
owner: owner.into(),
40+
description: description.into(),
41+
}
42+
}
43+
44+
/// Returns `true` if [`ResourceInfo`] fields are empty, and `false` if
45+
/// they are not.
46+
pub fn is_empty(&self) -> bool {
47+
self.resource_type.is_empty()
48+
&& self.resource_name.is_empty()
49+
&& self.owner.is_empty()
50+
&& self.description.is_empty()
51+
}
52+
}
53+
54+
impl IntoAny for ResourceInfo {
55+
fn into_any(self) -> Any {
56+
let detail_data = pb::ResourceInfo {
57+
resource_type: self.resource_type,
58+
resource_name: self.resource_name,
59+
owner: self.owner,
60+
description: self.description,
61+
};
62+
63+
Any {
64+
type_url: ResourceInfo::TYPE_URL.to_string(),
65+
value: detail_data.encode_to_vec(),
66+
}
67+
}
68+
}
69+
70+
impl FromAny for ResourceInfo {
71+
fn from_any(any: Any) -> Result<Self, DecodeError> {
72+
let buf: &[u8] = &any.value;
73+
let res_info = pb::ResourceInfo::decode(buf)?;
74+
75+
let debug_info = ResourceInfo {
76+
resource_type: res_info.resource_type,
77+
resource_name: res_info.resource_name,
78+
owner: res_info.owner,
79+
description: res_info.description,
80+
};
81+
82+
Ok(debug_info)
83+
}
84+
}
85+
86+
#[cfg(test)]
87+
mod tests {
88+
use super::super::super::{FromAny, IntoAny};
89+
use super::ResourceInfo;
90+
91+
#[test]
92+
fn gen_error_info() {
93+
let error_info =
94+
ResourceInfo::new("resource-type", "resource-name", "owner", "description");
95+
96+
let formatted = format!("{:?}", error_info);
97+
98+
let expected_filled = "ResourceInfo { resource_type: \"resource-type\", resource_name: \"resource-name\", owner: \"owner\", description: \"description\" }";
99+
100+
assert!(
101+
formatted.eq(expected_filled),
102+
"filled ResourceInfo differs from expected result"
103+
);
104+
105+
let gen_any = error_info.into_any();
106+
107+
let formatted = format!("{:?}", gen_any);
108+
109+
let expected =
110+
"Any { type_url: \"type.googleapis.com/google.rpc.ResourceInfo\", value: [10, 13, 114, 101, 115, 111, 117, 114, 99, 101, 45, 116, 121, 112, 101, 18, 13, 114, 101, 115, 111, 117, 114, 99, 101, 45, 110, 97, 109, 101, 26, 5, 111, 119, 110, 101, 114, 34, 11, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110] }";
111+
112+
assert!(
113+
formatted.eq(expected),
114+
"Any from filled ResourceInfo differs from expected result"
115+
);
116+
117+
let br_details = match ResourceInfo::from_any(gen_any) {
118+
Err(error) => panic!("Error generating ResourceInfo from Any: {:?}", error),
119+
Ok(from_any) => from_any,
120+
};
121+
122+
let formatted = format!("{:?}", br_details);
123+
124+
assert!(
125+
formatted.eq(expected_filled),
126+
"ResourceInfo from Any differs from expected result"
127+
);
128+
}
129+
}

0 commit comments

Comments
 (0)
Please sign in to comment.