From 2378581850483f26fd7c1dee0a797d936b73e881 Mon Sep 17 00:00:00 2001 From: Rafael Lemos Date: Thu, 16 Feb 2023 13:08:53 -0300 Subject: [PATCH] feat(types): Add gRPC Richer Error Model support (PreconditionFailure) (#1276) * types: add support for `PreconditionFailure` error message type Following implementation at flemosr/tonic-richer-error. * types: doc comments nits * types: merge `impl` blocks at `std_messages` --- tonic-types/src/lib.rs | 4 +- .../src/richer_error/error_details/mod.rs | 201 +++++++++++++++-- .../src/richer_error/error_details/vec.rs | 13 +- tonic-types/src/richer_error/mod.rs | 71 +++++- .../richer_error/std_messages/debug_info.rs | 2 - .../richer_error/std_messages/error_info.rs | 2 - .../src/richer_error/std_messages/mod.rs | 4 + .../richer_error/std_messages/prec_failure.rs | 203 ++++++++++++++++++ .../std_messages/quota_failure.rs | 2 - .../richer_error/std_messages/retry_info.rs | 2 - 10 files changed, 465 insertions(+), 39 deletions(-) create mode 100644 tonic-types/src/richer_error/std_messages/prec_failure.rs diff --git a/tonic-types/src/lib.rs b/tonic-types/src/lib.rs index e2b5208a6..6016dd884 100644 --- a/tonic-types/src/lib.rs +++ b/tonic-types/src/lib.rs @@ -46,8 +46,8 @@ pub use pb::Status; mod richer_error; pub use richer_error::{ - BadRequest, DebugInfo, ErrorDetail, ErrorDetails, ErrorInfo, FieldViolation, QuotaFailure, - QuotaViolation, RetryInfo, StatusExt, + BadRequest, DebugInfo, ErrorDetail, ErrorDetails, ErrorInfo, FieldViolation, + PreconditionFailure, PreconditionViolation, QuotaFailure, QuotaViolation, RetryInfo, StatusExt, }; mod sealed { diff --git a/tonic-types/src/richer_error/error_details/mod.rs b/tonic-types/src/richer_error/error_details/mod.rs index 91d119387..843bd34ca 100644 --- a/tonic-types/src/richer_error/error_details/mod.rs +++ b/tonic-types/src/richer_error/error_details/mod.rs @@ -1,7 +1,8 @@ use std::{collections::HashMap, time}; use super::std_messages::{ - BadRequest, DebugInfo, ErrorInfo, FieldViolation, QuotaFailure, QuotaViolation, RetryInfo, + BadRequest, DebugInfo, ErrorInfo, FieldViolation, PreconditionFailure, PreconditionViolation, + QuotaFailure, QuotaViolation, RetryInfo, }; pub(crate) mod vec; @@ -25,6 +26,9 @@ pub struct ErrorDetails { /// This field stores [`ErrorInfo`] data, if any. pub(crate) error_info: Option, + /// This field stores [`PreconditionFailure`] data, if any. + pub(crate) precondition_failure: Option, + /// This field stores [`BadRequest`] data, if any. pub(crate) bad_request: Option, } @@ -35,7 +39,7 @@ impl ErrorDetails { /// # Examples /// /// ``` - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let err_details = ErrorDetails::new(); /// ``` @@ -50,7 +54,7 @@ impl ErrorDetails { /// /// ``` /// use std::time::Duration; - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let err_details = ErrorDetails::with_retry_info(Some(Duration::from_secs(5))); /// ``` @@ -67,7 +71,7 @@ impl ErrorDetails { /// # Examples /// /// ``` - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let err_stack = vec!["...".into(), "...".into()]; /// @@ -106,7 +110,7 @@ impl ErrorDetails { /// # Examples /// /// ``` - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let err_details = ErrorDetails::with_quota_failure_violation("subject", "description"); /// ``` @@ -127,7 +131,7 @@ impl ErrorDetails { /// /// ``` /// use std::collections::HashMap; - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let mut metadata: HashMap = HashMap::new(); /// metadata.insert("instanceLimitPerRequest".into(), "100".into()); @@ -145,6 +149,64 @@ impl ErrorDetails { } } + /// Generates an [`ErrorDetails`] struct with [`PreconditionFailure`] + /// details and remaining fields set to `None`. + /// + /// # Examples + /// + /// ``` + /// use tonic_types::{ErrorDetails, PreconditionViolation}; + /// + /// let err_details = ErrorDetails::with_precondition_failure(vec![ + /// PreconditionViolation::new( + /// "violation type 1", + /// "subject 1", + /// "description 1", + /// ), + /// PreconditionViolation::new( + /// "violation type 2", + /// "subject 2", + /// "description 2", + /// ), + /// ]); + /// ``` + pub fn with_precondition_failure(violations: Vec) -> Self { + ErrorDetails { + precondition_failure: Some(PreconditionFailure::new(violations)), + ..ErrorDetails::new() + } + } + + /// Generates an [`ErrorDetails`] struct with [`PreconditionFailure`] + /// details (one [`PreconditionViolation`] set) and remaining fields set to + /// `None`. + /// + /// # Examples + /// + /// ``` + /// use tonic_types::ErrorDetails; + /// + /// let err_details = ErrorDetails::with_precondition_failure_violation( + /// "violation type", + /// "subject", + /// "description", + /// ); + /// ``` + pub fn with_precondition_failure_violation( + violation_type: impl Into, + subject: impl Into, + description: impl Into, + ) -> Self { + ErrorDetails { + precondition_failure: Some(PreconditionFailure::with_violation( + violation_type, + subject, + description, + )), + ..ErrorDetails::new() + } + } + /// Generates an [`ErrorDetails`] struct with [`BadRequest`] details and /// remaining fields set to `None`. /// @@ -171,7 +233,7 @@ impl ErrorDetails { /// # Examples /// /// ``` - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let err_details = ErrorDetails::with_bad_request_violation( /// "field", @@ -188,27 +250,32 @@ impl ErrorDetails { } } - /// Get [`RetryInfo`] details, if any + /// Get [`RetryInfo`] details, if any. pub fn retry_info(&self) -> Option { self.retry_info.clone() } - /// Get [`DebugInfo`] details, if any + /// Get [`DebugInfo`] details, if any. pub fn debug_info(&self) -> Option { self.debug_info.clone() } - /// Get [`QuotaFailure`] details, if any + /// Get [`QuotaFailure`] details, if any. pub fn quota_failure(&self) -> Option { self.quota_failure.clone() } - /// Get [`ErrorInfo`] details, if any + /// Get [`ErrorInfo`] details, if any. pub fn error_info(&self) -> Option { self.error_info.clone() } - /// Get [`BadRequest`] details, if any + /// Get [`PreconditionFailure`] details, if any. + pub fn precondition_failure(&self) -> Option { + self.precondition_failure.clone() + } + + /// Get [`BadRequest`] details, if any. pub fn bad_request(&self) -> Option { self.bad_request.clone() } @@ -220,7 +287,7 @@ impl ErrorDetails { /// /// ``` /// use std::time::Duration; - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let mut err_details = ErrorDetails::new(); /// @@ -237,7 +304,7 @@ impl ErrorDetails { /// # Examples /// /// ``` - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let mut err_details = ErrorDetails::new(); /// @@ -281,7 +348,7 @@ impl ErrorDetails { /// # Examples /// /// ``` - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let mut err_details = ErrorDetails::new(); /// @@ -309,7 +376,7 @@ impl ErrorDetails { /// # Examples /// /// ``` - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let mut err_details = ErrorDetails::with_quota_failure(vec![]); /// @@ -333,7 +400,7 @@ impl ErrorDetails { /// /// ``` /// use std::collections::HashMap; - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let mut err_details = ErrorDetails::new(); /// @@ -352,6 +419,102 @@ impl ErrorDetails { self } + /// Set [`PreconditionFailure`] details. Can be chained with other `.set_` + /// and `.add_` [`ErrorDetails`] methods. + /// + /// # Examples + /// + /// ``` + /// use tonic_types::{ErrorDetails, PreconditionViolation}; + /// + /// let mut err_details = ErrorDetails::new(); + /// + /// err_details.set_precondition_failure(vec![ + /// PreconditionViolation::new( + /// "violation type 1", + /// "subject 1", + /// "description 1", + /// ), + /// PreconditionViolation::new( + /// "violation type 2", + /// "subject 2", + /// "description 2", + /// ), + /// ]); + /// ``` + pub fn set_precondition_failure( + &mut self, + violations: Vec, + ) -> &mut Self { + self.precondition_failure = Some(PreconditionFailure::new(violations)); + self + } + + /// Adds a [`PreconditionViolation`] to [`PreconditionFailure`] details. + /// Sets [`PreconditionFailure`] 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_precondition_failure_violation( + /// "violation type", + /// "subject", + /// "description" + /// ); + /// ``` + pub fn add_precondition_failure_violation( + &mut self, + violation_type: impl Into, + subject: impl Into, + description: impl Into, + ) -> &mut Self { + match &mut self.precondition_failure { + Some(precondition_failure) => { + precondition_failure.add_violation(violation_type, subject, description); + } + None => { + self.precondition_failure = Some(PreconditionFailure::with_violation( + violation_type, + subject, + description, + )); + } + }; + self + } + + /// Returns `true` if [`PreconditionFailure`] is set and its `violations` + /// vector is not empty, otherwise returns `false`. + /// + /// # Examples + /// + /// ``` + /// use tonic_types::ErrorDetails; + /// + /// let mut err_details = ErrorDetails::with_precondition_failure(vec![]); + /// + /// assert_eq!(err_details.has_precondition_failure_violations(), false); + /// + /// err_details.add_precondition_failure_violation( + /// "violation type", + /// "subject", + /// "description" + /// ); + /// + /// assert_eq!(err_details.has_precondition_failure_violations(), true); + /// ``` + pub fn has_precondition_failure_violations(&self) -> bool { + if let Some(precondition_failure) = &self.precondition_failure { + return !precondition_failure.violations.is_empty(); + } + false + } + /// Set [`BadRequest`] details. Can be chained with other `.set_` and /// `.add_` [`ErrorDetails`] methods. /// @@ -379,7 +542,7 @@ impl ErrorDetails { /// # Examples /// /// ``` - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let mut err_details = ErrorDetails::new(); /// @@ -407,7 +570,7 @@ impl ErrorDetails { /// # Examples /// /// ``` - /// use tonic_types::{ErrorDetails}; + /// use tonic_types::ErrorDetails; /// /// let mut err_details = ErrorDetails::with_bad_request(vec![]); /// diff --git a/tonic-types/src/richer_error/error_details/vec.rs b/tonic-types/src/richer_error/error_details/vec.rs index 900e134a0..4393515d2 100644 --- a/tonic-types/src/richer_error/error_details/vec.rs +++ b/tonic-types/src/richer_error/error_details/vec.rs @@ -1,4 +1,6 @@ -use super::super::std_messages::{BadRequest, DebugInfo, ErrorInfo, QuotaFailure, RetryInfo}; +use super::super::std_messages::{ + BadRequest, DebugInfo, ErrorInfo, PreconditionFailure, QuotaFailure, RetryInfo, +}; /// Wraps the structs corresponding to the standard error messages, allowing /// the implementation and handling of vectors containing any of them. @@ -17,6 +19,9 @@ pub enum ErrorDetail { /// Wraps the [`ErrorInfo`] struct. ErrorInfo(ErrorInfo), + /// Wraps the [`PreconditionFailure`] struct. + PreconditionFailure(PreconditionFailure), + /// Wraps the [`BadRequest`] struct. BadRequest(BadRequest), } @@ -45,6 +50,12 @@ impl From for ErrorDetail { } } +impl From for ErrorDetail { + fn from(err_detail: PreconditionFailure) -> Self { + ErrorDetail::PreconditionFailure(err_detail) + } +} + impl From for ErrorDetail { fn from(err_detail: BadRequest) -> Self { ErrorDetail::BadRequest(err_detail) diff --git a/tonic-types/src/richer_error/mod.rs b/tonic-types/src/richer_error/mod.rs index 2b2720dd8..906dea43a 100644 --- a/tonic-types/src/richer_error/mod.rs +++ b/tonic-types/src/richer_error/mod.rs @@ -12,7 +12,8 @@ use super::pb; pub use error_details::{vec::ErrorDetail, ErrorDetails}; pub use std_messages::{ - BadRequest, DebugInfo, ErrorInfo, FieldViolation, QuotaFailure, QuotaViolation, RetryInfo, + BadRequest, DebugInfo, ErrorInfo, FieldViolation, PreconditionFailure, PreconditionViolation, + QuotaFailure, QuotaViolation, RetryInfo, }; trait IntoAny { @@ -147,7 +148,7 @@ pub trait StatusExt: crate::sealed::Sealed { /// /// ``` /// use tonic::{Status, Response}; - /// use tonic_types::{StatusExt}; + /// use tonic_types::StatusExt; /// /// fn handle_request_result(req_result: Result, Status>) { /// match req_result { @@ -172,7 +173,7 @@ pub trait StatusExt: crate::sealed::Sealed { /// /// ``` /// use tonic::{Status, Response}; - /// use tonic_types::{StatusExt}; + /// use tonic_types::StatusExt; /// /// fn handle_request_result(req_result: Result, Status>) { /// match req_result { @@ -256,7 +257,7 @@ pub trait StatusExt: crate::sealed::Sealed { /// /// ``` /// use tonic::{Status, Response}; - /// use tonic_types::{StatusExt}; + /// use tonic_types::StatusExt; /// /// fn handle_request_result(req_result: Result, Status>) { /// match req_result { @@ -278,7 +279,7 @@ pub trait StatusExt: crate::sealed::Sealed { /// /// ``` /// use tonic::{Status, Response}; - /// use tonic_types::{StatusExt}; + /// use tonic_types::StatusExt; /// /// fn handle_request_result(req_result: Result, Status>) { /// match req_result { @@ -300,7 +301,7 @@ pub trait StatusExt: crate::sealed::Sealed { /// /// ``` /// use tonic::{Status, Response}; - /// use tonic_types::{StatusExt}; + /// use tonic_types::StatusExt; /// /// fn handle_request_result(req_result: Result, Status>) { /// match req_result { @@ -322,7 +323,7 @@ pub trait StatusExt: crate::sealed::Sealed { /// /// ``` /// use tonic::{Status, Response}; - /// use tonic_types::{StatusExt}; + /// use tonic_types::StatusExt; /// /// fn handle_request_result(req_result: Result, Status>) { /// match req_result { @@ -337,6 +338,28 @@ pub trait StatusExt: crate::sealed::Sealed { /// ``` fn get_details_error_info(&self) -> Option; + /// Get first [`PreconditionFailure`] details found on `tonic::Status`, + /// if any. If some `prost::DecodeError` occurs, returns `None`. + /// + /// # Examples + /// + /// ``` + /// use tonic::{Status, Response}; + /// use tonic_types::StatusExt; + /// + /// fn handle_request_result(req_result: Result, Status>) { + /// match req_result { + /// Ok(_) => {}, + /// Err(status) => { + /// if let Some(precondition_failure) = status.get_details_precondition_failure() { + /// // Handle precondition_failure details + /// } + /// } + /// }; + /// } + /// ``` + fn get_details_precondition_failure(&self) -> Option; + /// Get first [`BadRequest`] details found on `tonic::Status`, if any. If /// some `prost::DecodeError` occurs, returns `None`. /// @@ -344,7 +367,7 @@ pub trait StatusExt: crate::sealed::Sealed { /// /// ``` /// use tonic::{Status, Response}; - /// use tonic_types::{StatusExt}; + /// use tonic_types::StatusExt; /// /// fn handle_request_result(req_result: Result, Status>) { /// match req_result { @@ -389,6 +412,10 @@ impl StatusExt for tonic::Status { conv_details.push(error_info.into_any()); } + if let Some(precondition_failure) = details.precondition_failure { + conv_details.push(precondition_failure.into_any()); + } + if let Some(bad_request) = details.bad_request { conv_details.push(bad_request.into_any()); } @@ -426,6 +453,9 @@ impl StatusExt for tonic::Status { ErrorDetail::ErrorInfo(error_info) => { conv_details.push(error_info.into_any()); } + ErrorDetail::PreconditionFailure(prec_failure) => { + conv_details.push(prec_failure.into_any()); + } ErrorDetail::BadRequest(bad_req) => { conv_details.push(bad_req.into_any()); } @@ -469,6 +499,9 @@ impl StatusExt for tonic::Status { ErrorInfo::TYPE_URL => { details.error_info = Some(ErrorInfo::from_any(any)?); } + PreconditionFailure::TYPE_URL => { + details.precondition_failure = Some(PreconditionFailure::from_any(any)?); + } BadRequest::TYPE_URL => { details.bad_request = Some(BadRequest::from_any(any)?); } @@ -502,6 +535,9 @@ impl StatusExt for tonic::Status { ErrorInfo::TYPE_URL => { details.push(ErrorInfo::from_any(any)?.into()); } + PreconditionFailure::TYPE_URL => { + details.push(PreconditionFailure::from_any(any)?.into()); + } BadRequest::TYPE_URL => { details.push(BadRequest::from_any(any)?.into()); } @@ -572,6 +608,20 @@ impl StatusExt for tonic::Status { None } + fn get_details_precondition_failure(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + for any in status.details.into_iter() { + if any.type_url.as_str() == PreconditionFailure::TYPE_URL { + if let Ok(detail) = PreconditionFailure::from_any(any) { + return Some(detail); + } + } + } + + None + } + fn get_details_bad_request(&self) -> Option { let status = pb::Status::decode(self.details()).ok()?; @@ -593,7 +643,8 @@ mod tests { use tonic::{Code, Status}; use super::{ - BadRequest, DebugInfo, ErrorDetails, ErrorInfo, QuotaFailure, RetryInfo, StatusExt, + BadRequest, DebugInfo, ErrorDetails, ErrorInfo, PreconditionFailure, QuotaFailure, + RetryInfo, StatusExt, }; #[test] @@ -611,6 +662,7 @@ mod tests { ) .add_quota_failure_violation("clientip:", "description") .set_error_info("SOME_INFO", "example.local", metadata.clone()) + .add_precondition_failure_violation("TOS", "example.local", "description") .add_bad_request_violation("field", "description"); let fmt_details = format!("{:?}", err_details); @@ -624,6 +676,7 @@ mod tests { .into(), QuotaFailure::with_violation("clientip:", "description").into(), ErrorInfo::new("SOME_INFO", "example.local", metadata).into(), + PreconditionFailure::with_violation("TOS", "example.local", "description").into(), BadRequest::with_violation("field", "description").into(), ]; diff --git a/tonic-types/src/richer_error/std_messages/debug_info.rs b/tonic-types/src/richer_error/std_messages/debug_info.rs index bde7aa805..839f8ca93 100644 --- a/tonic-types/src/richer_error/std_messages/debug_info.rs +++ b/tonic-types/src/richer_error/std_messages/debug_info.rs @@ -27,9 +27,7 @@ impl DebugInfo { detail: detail.into(), } } -} -impl DebugInfo { /// Returns `true` if [`DebugInfo`] fields are empty, and `false` if they /// are not. pub fn is_empty(&self) -> bool { diff --git a/tonic-types/src/richer_error/std_messages/error_info.rs b/tonic-types/src/richer_error/std_messages/error_info.rs index 072d8f9ec..39c65dcde 100644 --- a/tonic-types/src/richer_error/std_messages/error_info.rs +++ b/tonic-types/src/richer_error/std_messages/error_info.rs @@ -43,9 +43,7 @@ impl ErrorInfo { metadata: metadata.into(), } } -} -impl ErrorInfo { /// Returns `true` if [`ErrorInfo`] fields are empty, and `false` if they /// are not. pub fn is_empty(&self) -> bool { diff --git a/tonic-types/src/richer_error/std_messages/mod.rs b/tonic-types/src/richer_error/std_messages/mod.rs index b2cf4782d..799aaca99 100644 --- a/tonic-types/src/richer_error/std_messages/mod.rs +++ b/tonic-types/src/richer_error/std_messages/mod.rs @@ -14,6 +14,10 @@ mod error_info; pub use error_info::ErrorInfo; +mod prec_failure; + +pub use prec_failure::{PreconditionFailure, PreconditionViolation}; + mod bad_request; pub use bad_request::{BadRequest, FieldViolation}; diff --git a/tonic-types/src/richer_error/std_messages/prec_failure.rs b/tonic-types/src/richer_error/std_messages/prec_failure.rs new file mode 100644 index 000000000..be2e5409c --- /dev/null +++ b/tonic-types/src/richer_error/std_messages/prec_failure.rs @@ -0,0 +1,203 @@ +use prost::{DecodeError, Message}; +use prost_types::Any; + +use super::super::{pb, FromAny, IntoAny}; + +/// Used at the `violations` field of the [`PreconditionFailure`] struct. +/// Describes a single precondition failure. +#[derive(Clone, Debug)] +pub struct PreconditionViolation { + /// Type of the PreconditionFailure. At [error_details.proto], the usage + /// of a service-specific enum type is recommended. For example, "TOS" for + /// a "Terms of Service" violation. + /// + /// [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto + pub r#type: String, + + /// Subject, relative to the type, that failed. + pub subject: String, + + /// A description of how the precondition failed. + pub description: String, +} + +impl PreconditionViolation { + /// Creates a new [`PreconditionViolation`] struct. + pub fn new( + r#type: impl Into, + subject: impl Into, + description: impl Into, + ) -> Self { + PreconditionViolation { + r#type: r#type.into(), + subject: subject.into(), + description: description.into(), + } + } +} + +/// Used to encode/decode the `PreconditionFailure` standard error message +/// described in [error_details.proto]. Describes what preconditions have +/// failed. +/// +/// [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto +#[derive(Clone, Debug)] +pub struct PreconditionFailure { + /// Describes all precondition violations of the request. + pub violations: Vec, +} + +impl PreconditionFailure { + /// Type URL of the `PreconditionFailure` standard error message type. + pub const TYPE_URL: &'static str = "type.googleapis.com/google.rpc.PreconditionFailure"; + + /// Creates a new [`PreconditionFailure`] struct. + pub fn new(violations: Vec) -> Self { + PreconditionFailure { violations } + } + + /// Creates a new [`PreconditionFailure`] struct with a single + /// [`PreconditionViolation`] in `violations`. + pub fn with_violation( + violation_type: impl Into, + subject: impl Into, + description: impl Into, + ) -> Self { + PreconditionFailure { + violations: vec![PreconditionViolation { + r#type: violation_type.into(), + subject: subject.into(), + description: description.into(), + }], + } + } + + /// Adds a [`PreconditionViolation`] to [`PreconditionFailure`]'s + /// `violations` vector. + pub fn add_violation( + &mut self, + r#type: impl Into, + subject: impl Into, + description: impl Into, + ) -> &mut Self { + self.violations.append(&mut vec![PreconditionViolation { + r#type: r#type.into(), + subject: subject.into(), + description: description.into(), + }]); + self + } + + /// Returns `true` if [`PreconditionFailure`]'s `violations` vector is + /// empty, and `false` if it is not. + pub fn is_empty(&self) -> bool { + self.violations.is_empty() + } +} + +impl IntoAny for PreconditionFailure { + fn into_any(self) -> Any { + let detail_data = pb::PreconditionFailure { + violations: self + .violations + .into_iter() + .map(|v| pb::precondition_failure::Violation { + r#type: v.r#type, + subject: v.subject, + description: v.description, + }) + .collect(), + }; + + Any { + type_url: PreconditionFailure::TYPE_URL.to_string(), + value: detail_data.encode_to_vec(), + } + } +} + +impl FromAny for PreconditionFailure { + fn from_any(any: Any) -> Result { + let buf: &[u8] = &any.value; + let precondition_failure = pb::PreconditionFailure::decode(buf)?; + + let precondition_failure = PreconditionFailure { + violations: precondition_failure + .violations + .into_iter() + .map(|v| PreconditionViolation { + r#type: v.r#type, + subject: v.subject, + description: v.description, + }) + .collect(), + }; + + Ok(precondition_failure) + } +} + +#[cfg(test)] +mod tests { + use super::super::super::{FromAny, IntoAny}; + use super::PreconditionFailure; + + #[test] + fn gen_prec_failure() { + let mut prec_failure = PreconditionFailure::new(Vec::new()); + let formatted = format!("{:?}", prec_failure); + + let expected = "PreconditionFailure { violations: [] }"; + + assert!( + formatted.eq(expected), + "empty PreconditionFailure differs from expected result" + ); + + assert!( + prec_failure.is_empty(), + "empty PreconditionFailure returns 'false' from .is_empty()" + ); + + prec_failure + .add_violation("TOS", "example.local", "Terms of service not accepted") + .add_violation("FNF", "example.local", "File not found"); + + let formatted = format!("{:?}", prec_failure); + + let expected_filled = "PreconditionFailure { violations: [PreconditionViolation { type: \"TOS\", subject: \"example.local\", description: \"Terms of service not accepted\" }, PreconditionViolation { type: \"FNF\", subject: \"example.local\", description: \"File not found\" }] }"; + + assert!( + formatted.eq(expected_filled), + "filled PreconditionFailure differs from expected result" + ); + + assert!( + !prec_failure.is_empty(), + "filled PreconditionFailure returns 'true' from .is_empty()" + ); + + let gen_any = prec_failure.into_any(); + + let formatted = format!("{:?}", gen_any); + + let expected = "Any { type_url: \"type.googleapis.com/google.rpc.PreconditionFailure\", value: [10, 51, 10, 3, 84, 79, 83, 18, 13, 101, 120, 97, 109, 112, 108, 101, 46, 108, 111, 99, 97, 108, 26, 29, 84, 101, 114, 109, 115, 32, 111, 102, 32, 115, 101, 114, 118, 105, 99, 101, 32, 110, 111, 116, 32, 97, 99, 99, 101, 112, 116, 101, 100, 10, 36, 10, 3, 70, 78, 70, 18, 13, 101, 120, 97, 109, 112, 108, 101, 46, 108, 111, 99, 97, 108, 26, 14, 70, 105, 108, 101, 32, 110, 111, 116, 32, 102, 111, 117, 110, 100] }"; + + assert!( + formatted.eq(expected), + "Any from filled PreconditionFailure differs from expected result" + ); + + let br_details = match PreconditionFailure::from_any(gen_any) { + Err(error) => panic!("Error generating PreconditionFailure from Any: {:?}", error), + Ok(from_any) => from_any, + }; + + let formatted = format!("{:?}", br_details); + + assert!( + formatted.eq(expected_filled), + "PreconditionFailure from Any differs from expected result" + ); + } +} diff --git a/tonic-types/src/richer_error/std_messages/quota_failure.rs b/tonic-types/src/richer_error/std_messages/quota_failure.rs index d8797260b..1c0017589 100644 --- a/tonic-types/src/richer_error/std_messages/quota_failure.rs +++ b/tonic-types/src/richer_error/std_messages/quota_failure.rs @@ -53,9 +53,7 @@ impl QuotaFailure { }], } } -} -impl QuotaFailure { /// Adds a [`QuotaViolation`] to [`QuotaFailure`]'s `violations`. pub fn add_violation( &mut self, diff --git a/tonic-types/src/richer_error/std_messages/retry_info.rs b/tonic-types/src/richer_error/std_messages/retry_info.rs index 4e1fe25da..7b5492cbd 100644 --- a/tonic-types/src/richer_error/std_messages/retry_info.rs +++ b/tonic-types/src/richer_error/std_messages/retry_info.rs @@ -42,9 +42,7 @@ impl RetryInfo { RetryInfo { retry_delay } } -} -impl RetryInfo { /// Returns `true` if [`RetryInfo`]'s `retry_delay` is set as `None`, and /// `false` if it is not. pub fn is_empty(&self) -> bool {