Skip to content

Commit 03b4735

Browse files
authored
feat(types): Add gRPC Richer Error Model support (QuotaFailure) (#1204)
1 parent 2d7e14f commit 03b4735

File tree

6 files changed

+377
-5
lines changed

6 files changed

+377
-5
lines changed

tonic-types/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ pub use pb::Status;
4646
mod richer_error;
4747

4848
pub use richer_error::{
49-
BadRequest, DebugInfo, ErrorDetail, ErrorDetails, FieldViolation, RetryInfo, StatusExt,
49+
BadRequest, DebugInfo, ErrorDetail, ErrorDetails, FieldViolation, QuotaFailure, QuotaViolation,
50+
RetryInfo, StatusExt,
5051
};
5152

5253
mod sealed {

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

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::time;
22

3-
use super::std_messages::{BadRequest, DebugInfo, FieldViolation, RetryInfo};
3+
use super::std_messages::{
4+
BadRequest, DebugInfo, FieldViolation, QuotaFailure, QuotaViolation, RetryInfo,
5+
};
46

57
pub(crate) mod vec;
68

@@ -17,6 +19,9 @@ pub struct ErrorDetails {
1719
/// This field stores [`DebugInfo`] data, if any.
1820
pub(crate) debug_info: Option<DebugInfo>,
1921

22+
/// This field stores [`QuotaFailure`] data, if any.
23+
pub(crate) quota_failure: Option<QuotaFailure>,
24+
2025
/// This field stores [`BadRequest`] data, if any.
2126
pub(crate) bad_request: Option<BadRequest>,
2227
}
@@ -35,6 +40,7 @@ impl ErrorDetails {
3540
ErrorDetails {
3641
retry_info: None,
3742
debug_info: None,
43+
quota_failure: None,
3844
bad_request: None,
3945
}
4046
}
@@ -76,6 +82,46 @@ impl ErrorDetails {
7682
}
7783
}
7884

85+
/// Generates an [`ErrorDetails`] struct with [`QuotaFailure`] details and
86+
/// remaining fields set to `None`.
87+
///
88+
/// # Examples
89+
///
90+
/// ```
91+
/// use tonic_types::{ErrorDetails, QuotaViolation};
92+
///
93+
/// let err_details = ErrorDetails::with_quota_failure(vec![
94+
/// QuotaViolation::new("subject 1", "description 1"),
95+
/// QuotaViolation::new("subject 2", "description 2"),
96+
/// ]);
97+
/// ```
98+
pub fn with_quota_failure(violations: Vec<QuotaViolation>) -> Self {
99+
ErrorDetails {
100+
quota_failure: Some(QuotaFailure::new(violations)),
101+
..ErrorDetails::new()
102+
}
103+
}
104+
105+
/// Generates an [`ErrorDetails`] struct with [`QuotaFailure`] details (one
106+
/// [`QuotaViolation`] set) and remaining fields set to `None`.
107+
///
108+
/// # Examples
109+
///
110+
/// ```
111+
/// use tonic_types::{ErrorDetails};
112+
///
113+
/// let err_details = ErrorDetails::with_quota_failure_violation("subject", "description");
114+
/// ```
115+
pub fn with_quota_failure_violation(
116+
subject: impl Into<String>,
117+
description: impl Into<String>,
118+
) -> Self {
119+
ErrorDetails {
120+
quota_failure: Some(QuotaFailure::with_violation(subject, description)),
121+
..ErrorDetails::new()
122+
}
123+
}
124+
79125
/// Generates an [`ErrorDetails`] struct with [`BadRequest`] details and
80126
/// remaining fields set to `None`.
81127
///
@@ -129,6 +175,11 @@ impl ErrorDetails {
129175
self.debug_info.clone()
130176
}
131177

178+
/// Get [`QuotaFailure`] details, if any
179+
pub fn quota_failure(&self) -> Option<QuotaFailure> {
180+
self.quota_failure.clone()
181+
}
182+
132183
/// Get [`BadRequest`] details, if any
133184
pub fn bad_request(&self) -> Option<BadRequest> {
134185
self.bad_request.clone()
@@ -175,6 +226,78 @@ impl ErrorDetails {
175226
self
176227
}
177228

229+
/// Set [`QuotaFailure`] details. Can be chained with other `.set_` and
230+
/// `.add_` [`ErrorDetails`] methods.
231+
///
232+
/// # Examples
233+
///
234+
/// ```
235+
/// use tonic_types::{ErrorDetails, QuotaViolation};
236+
///
237+
/// let mut err_details = ErrorDetails::new();
238+
///
239+
/// err_details.set_quota_failure(vec![
240+
/// QuotaViolation::new("subject 1", "description 1"),
241+
/// QuotaViolation::new("subject 2", "description 2"),
242+
/// ]);
243+
/// ```
244+
pub fn set_quota_failure(&mut self, violations: Vec<QuotaViolation>) -> &mut Self {
245+
self.quota_failure = Some(QuotaFailure::new(violations));
246+
self
247+
}
248+
249+
/// Adds a [`QuotaViolation`] to [`QuotaFailure`] details. Sets
250+
/// [`QuotaFailure`] details if it is not set yet. Can be chained with
251+
/// other `.set_` and `.add_` [`ErrorDetails`] methods.
252+
///
253+
/// # Examples
254+
///
255+
/// ```
256+
/// use tonic_types::{ErrorDetails};
257+
///
258+
/// let mut err_details = ErrorDetails::new();
259+
///
260+
/// err_details.add_quota_failure_violation("subject", "description");
261+
/// ```
262+
pub fn add_quota_failure_violation(
263+
&mut self,
264+
subject: impl Into<String>,
265+
description: impl Into<String>,
266+
) -> &mut Self {
267+
match &mut self.quota_failure {
268+
Some(quota_failure) => {
269+
quota_failure.add_violation(subject, description);
270+
}
271+
None => {
272+
self.quota_failure = Some(QuotaFailure::with_violation(subject, description));
273+
}
274+
};
275+
self
276+
}
277+
278+
/// Returns `true` if [`QuotaFailure`] is set and its `violations` vector
279+
/// is not empty, otherwise returns `false`.
280+
///
281+
/// # Examples
282+
///
283+
/// ```
284+
/// use tonic_types::{ErrorDetails};
285+
///
286+
/// let mut err_details = ErrorDetails::with_quota_failure(vec![]);
287+
///
288+
/// assert_eq!(err_details.has_quota_failure_violations(), false);
289+
///
290+
/// err_details.add_quota_failure_violation("subject", "description");
291+
///
292+
/// assert_eq!(err_details.has_quota_failure_violations(), true);
293+
/// ```
294+
pub fn has_quota_failure_violations(&self) -> bool {
295+
if let Some(quota_failure) = &self.quota_failure {
296+
return !quota_failure.violations.is_empty();
297+
}
298+
false
299+
}
300+
178301
/// Set [`BadRequest`] details. Can be chained with other `.set_` and
179302
/// `.add_` [`ErrorDetails`] methods.
180303
///

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::super::std_messages::{BadRequest, DebugInfo, RetryInfo};
1+
use super::super::std_messages::{BadRequest, DebugInfo, QuotaFailure, RetryInfo};
22

33
/// Wraps the structs corresponding to the standard error messages, allowing
44
/// the implementation and handling of vectors containing any of them.
@@ -11,6 +11,9 @@ pub enum ErrorDetail {
1111
/// Wraps the [`DebugInfo`] struct.
1212
DebugInfo(DebugInfo),
1313

14+
/// Wraps the [`QuotaFailure`] struct.
15+
QuotaFailure(QuotaFailure),
16+
1417
/// Wraps the [`BadRequest`] struct.
1518
BadRequest(BadRequest),
1619
}
@@ -27,6 +30,12 @@ impl From<DebugInfo> for ErrorDetail {
2730
}
2831
}
2932

33+
impl From<QuotaFailure> for ErrorDetail {
34+
fn from(err_detail: QuotaFailure) -> Self {
35+
ErrorDetail::QuotaFailure(err_detail)
36+
}
37+
}
38+
3039
impl From<BadRequest> for ErrorDetail {
3140
fn from(err_detail: BadRequest) -> Self {
3241
ErrorDetail::BadRequest(err_detail)

tonic-types/src/richer_error/mod.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ mod std_messages;
88
use super::pb;
99

1010
pub use error_details::{vec::ErrorDetail, ErrorDetails};
11-
pub use std_messages::{BadRequest, DebugInfo, FieldViolation, RetryInfo};
11+
pub use std_messages::{
12+
BadRequest, DebugInfo, FieldViolation, QuotaFailure, QuotaViolation, RetryInfo,
13+
};
1214

1315
trait IntoAny {
1416
fn into_any(self) -> Any;
@@ -288,6 +290,28 @@ pub trait StatusExt: crate::sealed::Sealed {
288290
/// ```
289291
fn get_details_debug_info(&self) -> Option<DebugInfo>;
290292

293+
/// Get first [`QuotaFailure`] details found on `tonic::Status`, if any.
294+
/// If some `prost::DecodeError` occurs, returns `None`.
295+
///
296+
/// # Examples
297+
///
298+
/// ```
299+
/// use tonic::{Status, Response};
300+
/// use tonic_types::{StatusExt};
301+
///
302+
/// fn handle_request_result<T>(req_result: Result<Response<T>, Status>) {
303+
/// match req_result {
304+
/// Ok(_) => {},
305+
/// Err(status) => {
306+
/// if let Some(quota_failure) = status.get_details_quota_failure() {
307+
/// // Handle quota_failure details
308+
/// }
309+
/// }
310+
/// };
311+
/// }
312+
/// ```
313+
fn get_details_quota_failure(&self) -> Option<QuotaFailure>;
314+
291315
/// Get first [`BadRequest`] details found on `tonic::Status`, if any. If
292316
/// some `prost::DecodeError` occurs, returns `None`.
293317
///
@@ -332,6 +356,10 @@ impl StatusExt for tonic::Status {
332356
conv_details.push(debug_info.into_any());
333357
}
334358

359+
if let Some(quota_failure) = details.quota_failure {
360+
conv_details.push(quota_failure.into_any());
361+
}
362+
335363
if let Some(bad_request) = details.bad_request {
336364
conv_details.push(bad_request.into_any());
337365
}
@@ -363,6 +391,9 @@ impl StatusExt for tonic::Status {
363391
ErrorDetail::DebugInfo(debug_info) => {
364392
conv_details.push(debug_info.into_any());
365393
}
394+
ErrorDetail::QuotaFailure(quota_failure) => {
395+
conv_details.push(quota_failure.into_any());
396+
}
366397
ErrorDetail::BadRequest(bad_req) => {
367398
conv_details.push(bad_req.into_any());
368399
}
@@ -400,6 +431,9 @@ impl StatusExt for tonic::Status {
400431
DebugInfo::TYPE_URL => {
401432
details.debug_info = Some(DebugInfo::from_any(any)?);
402433
}
434+
QuotaFailure::TYPE_URL => {
435+
details.quota_failure = Some(QuotaFailure::from_any(any)?);
436+
}
403437
BadRequest::TYPE_URL => {
404438
details.bad_request = Some(BadRequest::from_any(any)?);
405439
}
@@ -427,6 +461,9 @@ impl StatusExt for tonic::Status {
427461
DebugInfo::TYPE_URL => {
428462
details.push(DebugInfo::from_any(any)?.into());
429463
}
464+
QuotaFailure::TYPE_URL => {
465+
details.push(QuotaFailure::from_any(any)?.into());
466+
}
430467
BadRequest::TYPE_URL => {
431468
details.push(BadRequest::from_any(any)?.into());
432469
}
@@ -469,6 +506,20 @@ impl StatusExt for tonic::Status {
469506
None
470507
}
471508

509+
fn get_details_quota_failure(&self) -> Option<QuotaFailure> {
510+
let status = pb::Status::decode(self.details()).ok()?;
511+
512+
for any in status.details.into_iter() {
513+
if any.type_url.as_str() == QuotaFailure::TYPE_URL {
514+
if let Ok(detail) = QuotaFailure::from_any(any) {
515+
return Some(detail);
516+
}
517+
}
518+
}
519+
520+
None
521+
}
522+
472523
fn get_details_bad_request(&self) -> Option<BadRequest> {
473524
let status = pb::Status::decode(self.details()).ok()?;
474525

@@ -489,7 +540,7 @@ mod tests {
489540
use std::time::Duration;
490541
use tonic::{Code, Status};
491542

492-
use super::{BadRequest, DebugInfo, ErrorDetails, RetryInfo, StatusExt};
543+
use super::{BadRequest, DebugInfo, ErrorDetails, QuotaFailure, RetryInfo, StatusExt};
493544

494545
#[test]
495546
fn gen_status_with_details() {
@@ -501,6 +552,7 @@ mod tests {
501552
vec!["trace3".into(), "trace2".into(), "trace1".into()],
502553
"details",
503554
)
555+
.add_quota_failure_violation("clientip:<ip address>", "description")
504556
.add_bad_request_violation("field", "description");
505557

506558
let fmt_details = format!("{:?}", err_details);
@@ -512,6 +564,7 @@ mod tests {
512564
"details",
513565
)
514566
.into(),
567+
QuotaFailure::with_violation("clientip:<ip address>", "description").into(),
515568
BadRequest::with_violation("field", "description").into(),
516569
];
517570

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ mod debug_info;
66

77
pub use debug_info::DebugInfo;
88

9+
mod quota_failure;
10+
11+
pub use quota_failure::{QuotaFailure, QuotaViolation};
12+
913
mod bad_request;
1014

1115
pub use bad_request::{BadRequest, FieldViolation};

0 commit comments

Comments
 (0)