Skip to content

Commit 6cdb3d4

Browse files
authoredDec 1, 2022
feat(types): Add gRPC Richer Error Model support (RetryInfo) (#1095)
Add the code related to richer error model support to a new `richer_error` module. This improves readability and hopefully will make it easier to add new features to `tonic-types` in the future.
1 parent f7499c8 commit 6cdb3d4

File tree

7 files changed

+731
-443
lines changed

7 files changed

+731
-443
lines changed
 

‎tonic-types/src/lib.rs

+7-438
Large diffs are not rendered by default.

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

+51-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use super::std_messages::{BadRequest, FieldViolation};
1+
use std::time;
2+
3+
use super::std_messages::{BadRequest, FieldViolation, RetryInfo};
24

35
pub(crate) mod vec;
46

@@ -9,6 +11,9 @@ pub(crate) mod vec;
911
#[non_exhaustive]
1012
#[derive(Clone, Debug)]
1113
pub struct ErrorDetails {
14+
/// This field stores [`RetryInfo`] data, if any.
15+
pub(crate) retry_info: Option<RetryInfo>,
16+
1217
/// This field stores [`BadRequest`] data, if any.
1318
pub(crate) bad_request: Option<BadRequest>,
1419
}
@@ -24,7 +29,28 @@ impl ErrorDetails {
2429
/// let err_details = ErrorDetails::new();
2530
/// ```
2631
pub fn new() -> Self {
27-
ErrorDetails { bad_request: None }
32+
ErrorDetails {
33+
retry_info: None,
34+
bad_request: None,
35+
}
36+
}
37+
38+
/// Generates an [`ErrorDetails`] struct with [`RetryInfo`] details and
39+
/// remaining fields set to `None`.
40+
///
41+
/// # Examples
42+
///
43+
/// ```
44+
/// use std::time::Duration;
45+
/// use tonic_types::{ErrorDetails};
46+
///
47+
/// let err_details = ErrorDetails::with_retry_info(Some(Duration::from_secs(5)));
48+
/// ```
49+
pub fn with_retry_info(retry_delay: Option<time::Duration>) -> Self {
50+
ErrorDetails {
51+
retry_info: Some(RetryInfo::new(retry_delay)),
52+
..ErrorDetails::new()
53+
}
2854
}
2955

3056
/// Generates an [`ErrorDetails`] struct with [`BadRequest`] details and
@@ -70,11 +96,34 @@ impl ErrorDetails {
7096
}
7197
}
7298

99+
/// Get [`RetryInfo`] details, if any
100+
pub fn retry_info(&self) -> Option<RetryInfo> {
101+
self.retry_info.clone()
102+
}
103+
73104
/// Get [`BadRequest`] details, if any
74105
pub fn bad_request(&self) -> Option<BadRequest> {
75106
self.bad_request.clone()
76107
}
77108

109+
/// Set [`RetryInfo`] details. Can be chained with other `.set_` and
110+
/// `.add_` [`ErrorDetails`] methods.
111+
///
112+
/// # Examples
113+
///
114+
/// ```
115+
/// use std::time::Duration;
116+
/// use tonic_types::{ErrorDetails};
117+
///
118+
/// let mut err_details = ErrorDetails::new();
119+
///
120+
/// err_details.set_retry_info(Some(Duration::from_secs(5)));
121+
/// ```
122+
pub fn set_retry_info(&mut self, retry_delay: Option<time::Duration>) -> &mut Self {
123+
self.retry_info = Some(RetryInfo::new(retry_delay));
124+
self
125+
}
126+
78127
/// Set [`BadRequest`] details. Can be chained with other `.set_` and
79128
/// `.add_` [`ErrorDetails`] methods.
80129
///

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

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
use super::super::std_messages::BadRequest;
1+
use super::super::std_messages::{BadRequest, RetryInfo};
22

33
/// Wraps the structs corresponding to the standard error messages, allowing
44
/// the implementation and handling of vectors containing any of them.
55
#[non_exhaustive]
66
#[derive(Clone, Debug)]
77
pub enum ErrorDetail {
8+
/// Wraps the [`RetryInfo`] struct.
9+
RetryInfo(RetryInfo),
10+
811
/// Wraps the [`BadRequest`] struct.
912
BadRequest(BadRequest),
1013
}
1114

15+
impl From<RetryInfo> for ErrorDetail {
16+
fn from(err_detail: RetryInfo) -> Self {
17+
ErrorDetail::RetryInfo(err_detail)
18+
}
19+
}
20+
1221
impl From<BadRequest> for ErrorDetail {
1322
fn from(err_detail: BadRequest) -> Self {
1423
ErrorDetail::BadRequest(err_detail)

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

+508
Large diffs are not rendered by default.

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use prost::{DecodeError, Message};
22
use prost_types::Any;
33

4-
use super::super::pb;
5-
use super::super::{FromAny, IntoAny};
4+
use super::super::{pb, FromAny, IntoAny};
65

76
/// Used at the `field_violations` field of the [`BadRequest`] struct.
87
/// Describes a single bad request field.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
mod retry_info;
2+
3+
pub use retry_info::RetryInfo;
4+
15
mod bad_request;
26

37
pub use bad_request::{BadRequest, FieldViolation};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
use std::{convert::TryFrom, time};
2+
3+
use prost::{DecodeError, Message};
4+
use prost_types::Any;
5+
6+
use super::super::{pb, FromAny, IntoAny};
7+
8+
/// Used to encode/decode the `RetryInfo` standard error message described in
9+
/// [error_details.proto]. Describes when the clients can retry a failed
10+
/// request.
11+
/// Note: When obtained from decoding `RetryInfo` messages, negative
12+
/// `retry_delay`'s become 0.
13+
///
14+
/// [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
15+
#[derive(Clone, Debug)]
16+
pub struct RetryInfo {
17+
/// Informs the amout of time that clients should wait before retrying.
18+
pub retry_delay: Option<time::Duration>,
19+
}
20+
21+
impl RetryInfo {
22+
/// Type URL of the `RetryInfo` standard error message type.
23+
pub const TYPE_URL: &'static str = "type.googleapis.com/google.rpc.RetryInfo";
24+
25+
/// Should not exceed `prost_types::Duration` range. Limited to
26+
/// approximately 10,000 years.
27+
pub const MAX_RETRY_DELAY: time::Duration = time::Duration::new(315_576_000_000, 999_999_999);
28+
29+
/// Creates a new [`RetryInfo`] struct. If `retry_delay` exceeds
30+
/// [`RetryInfo::MAX_RETRY_DELAY`], [`RetryInfo::MAX_RETRY_DELAY`] will
31+
/// be used instead.
32+
pub fn new(retry_delay: Option<time::Duration>) -> Self {
33+
let retry_delay = match retry_delay {
34+
Some(mut delay) => {
35+
if delay > RetryInfo::MAX_RETRY_DELAY {
36+
delay = RetryInfo::MAX_RETRY_DELAY
37+
}
38+
Some(delay)
39+
}
40+
None => None,
41+
};
42+
43+
RetryInfo { retry_delay }
44+
}
45+
}
46+
47+
impl RetryInfo {
48+
/// Returns `true` if [`RetryInfo`]'s `retry_delay` is set as `None`, and
49+
/// `false` if it is not.
50+
pub fn is_empty(&self) -> bool {
51+
self.retry_delay.is_none()
52+
}
53+
}
54+
55+
impl IntoAny for RetryInfo {
56+
fn into_any(self) -> Any {
57+
let retry_delay = match self.retry_delay {
58+
Some(duration) => {
59+
// If duration is too large, uses max `prost_types::Duration`
60+
let duration = match prost_types::Duration::try_from(duration) {
61+
Ok(duration) => duration,
62+
Err(_) => prost_types::Duration {
63+
seconds: 315_576_000_000,
64+
nanos: 999_999_999,
65+
},
66+
};
67+
Some(duration)
68+
}
69+
None => None,
70+
};
71+
72+
let detail_data = pb::RetryInfo { retry_delay };
73+
74+
Any {
75+
type_url: RetryInfo::TYPE_URL.to_string(),
76+
value: detail_data.encode_to_vec(),
77+
}
78+
}
79+
}
80+
81+
impl FromAny for RetryInfo {
82+
fn from_any(any: Any) -> Result<Self, DecodeError> {
83+
let buf: &[u8] = &any.value;
84+
let retry_info = pb::RetryInfo::decode(buf)?;
85+
86+
let retry_delay = match retry_info.retry_delay {
87+
Some(duration) => {
88+
// Negative retry_delays become 0
89+
let duration = time::Duration::try_from(duration).unwrap_or(time::Duration::ZERO);
90+
Some(duration)
91+
}
92+
None => None,
93+
};
94+
95+
let retry_info = RetryInfo { retry_delay };
96+
97+
Ok(retry_info)
98+
}
99+
}
100+
101+
#[cfg(test)]
102+
mod tests {
103+
use core::time::Duration;
104+
105+
use super::super::super::{FromAny, IntoAny};
106+
use super::RetryInfo;
107+
108+
#[test]
109+
fn gen_retry_info() {
110+
let error_info = RetryInfo::new(Some(Duration::from_secs(u64::MAX)));
111+
112+
let formatted = format!("{:?}", error_info);
113+
114+
let expected_filled = "RetryInfo { retry_delay: Some(315576000000.999999999s) }";
115+
116+
assert!(
117+
formatted.eq(expected_filled),
118+
"filled RetryInfo differs from expected result"
119+
);
120+
121+
assert!(
122+
error_info.is_empty() == false,
123+
"filled RetryInfo returns 'false' from .has_retry_delay()"
124+
);
125+
126+
let gen_any = error_info.into_any();
127+
128+
let formatted = format!("{:?}", gen_any);
129+
130+
let expected =
131+
"Any { type_url: \"type.googleapis.com/google.rpc.RetryInfo\", value: [10, 13, 8, 128, 188, 174, 206, 151, 9, 16, 255, 147, 235, 220, 3] }";
132+
133+
assert!(
134+
formatted.eq(expected),
135+
"Any from filled RetryInfo differs from expected result"
136+
);
137+
138+
let br_details = match RetryInfo::from_any(gen_any) {
139+
Err(error) => panic!("Error generating RetryInfo from Any: {:?}", error),
140+
Ok(from_any) => from_any,
141+
};
142+
143+
let formatted = format!("{:?}", br_details);
144+
145+
assert!(
146+
formatted.eq(expected_filled),
147+
"RetryInfo from Any differs from expected result"
148+
);
149+
}
150+
}

0 commit comments

Comments
 (0)
Please sign in to comment.