From c44ab3d2b92979d282588711fa327b9b4b00ae56 Mon Sep 17 00:00:00 2001 From: gibbz00 Date: Sat, 16 Mar 2024 20:53:30 +0100 Subject: [PATCH] Place `StatusExt` and `RpcStatusExt` into separate files. Also does some minor module imports cleanup. The goal is to make room for a `CodeExt` trait. --- tonic-types/src/lib.rs | 7 +- .../src/richer_error/error_details/mod.rs | 10 +- .../src/richer_error/error_details/vec.rs | 5 +- tonic-types/src/richer_error/helpers.rs | 40 + tonic-types/src/richer_error/mod.rs | 1070 +---------------- .../src/richer_error/rpc_status_ext.rs | 291 +++++ tonic-types/src/richer_error/status_ext.rs | 739 ++++++++++++ .../richer_error/std_messages/bad_request.rs | 4 +- .../richer_error/std_messages/debug_info.rs | 4 +- .../richer_error/std_messages/error_info.rs | 4 +- .../src/richer_error/std_messages/help.rs | 4 +- .../richer_error/std_messages/loc_message.rs | 4 +- .../src/richer_error/std_messages/mod.rs | 10 - .../richer_error/std_messages/prec_failure.rs | 4 +- .../std_messages/quota_failure.rs | 4 +- .../richer_error/std_messages/request_info.rs | 4 +- .../std_messages/resource_info.rs | 4 +- .../richer_error/std_messages/retry_info.rs | 4 +- 18 files changed, 1103 insertions(+), 1109 deletions(-) create mode 100644 tonic-types/src/richer_error/helpers.rs create mode 100644 tonic-types/src/richer_error/rpc_status_ext.rs create mode 100644 tonic-types/src/richer_error/status_ext.rs diff --git a/tonic-types/src/lib.rs b/tonic-types/src/lib.rs index fe2886a60..e2d8cdc9f 100644 --- a/tonic-types/src/lib.rs +++ b/tonic-types/src/lib.rs @@ -182,12 +182,7 @@ pub mod pb { pub use pb::Status; mod richer_error; - -pub use richer_error::{ - BadRequest, DebugInfo, ErrorDetail, ErrorDetails, ErrorInfo, FieldViolation, Help, HelpLink, - LocalizedMessage, PreconditionFailure, PreconditionViolation, QuotaFailure, QuotaViolation, - RequestInfo, ResourceInfo, RetryInfo, RpcStatusExt, StatusExt, -}; +pub use richer_error::*; mod sealed { pub trait Sealed {} diff --git a/tonic-types/src/richer_error/error_details/mod.rs b/tonic-types/src/richer_error/error_details/mod.rs index 70074aa9c..ebad231a7 100644 --- a/tonic-types/src/richer_error/error_details/mod.rs +++ b/tonic-types/src/richer_error/error_details/mod.rs @@ -1,12 +1,8 @@ -use std::{collections::HashMap, time}; +pub(super) mod vec; -use super::std_messages::{ - BadRequest, DebugInfo, ErrorInfo, FieldViolation, Help, HelpLink, LocalizedMessage, - PreconditionFailure, PreconditionViolation, QuotaFailure, QuotaViolation, RequestInfo, - ResourceInfo, RetryInfo, -}; +use std::{collections::HashMap, time}; -pub(crate) mod vec; +use super::std_messages::*; /// Groups the standard error messages structs. Provides associated /// functions and methods to setup and edit each error message independently. diff --git a/tonic-types/src/richer_error/error_details/vec.rs b/tonic-types/src/richer_error/error_details/vec.rs index 9a09285e8..978d92a32 100644 --- a/tonic-types/src/richer_error/error_details/vec.rs +++ b/tonic-types/src/richer_error/error_details/vec.rs @@ -1,7 +1,4 @@ -use super::super::std_messages::{ - BadRequest, DebugInfo, ErrorInfo, Help, LocalizedMessage, PreconditionFailure, QuotaFailure, - RequestInfo, ResourceInfo, RetryInfo, -}; +use super::super::std_messages::*; /// Wraps the structs corresponding to the standard error messages, allowing /// the implementation and handling of vectors containing any of them. diff --git a/tonic-types/src/richer_error/helpers.rs b/tonic-types/src/richer_error/helpers.rs new file mode 100644 index 000000000..1f2fe841a --- /dev/null +++ b/tonic-types/src/richer_error/helpers.rs @@ -0,0 +1,40 @@ +use prost::{ + bytes::{Bytes, BytesMut}, + DecodeError, Message, +}; +use prost_types::Any; +use tonic::Code; + +use crate::pb; + +pub(super) trait IntoAny { + fn into_any(self) -> Any; +} + +#[allow(dead_code)] +pub(super) trait FromAny { + fn from_any(any: Any) -> Result + where + Self: Sized; +} + +pub(super) trait FromAnyRef { + fn from_any_ref(any: &Any) -> Result + where + Self: Sized; +} + +pub(super) fn gen_details_bytes(code: Code, message: &str, details: Vec) -> Bytes { + let status = pb::Status { + code: code as i32, + message: message.to_owned(), + details, + }; + + let mut buf = BytesMut::with_capacity(status.encoded_len()); + + // Should never panic since `buf` is initialized with sufficient capacity + status.encode(&mut buf).unwrap(); + + buf.freeze() +} diff --git a/tonic-types/src/richer_error/mod.rs b/tonic-types/src/richer_error/mod.rs index 927bbc97d..f62484e88 100644 --- a/tonic-types/src/richer_error/mod.rs +++ b/tonic-types/src/richer_error/mod.rs @@ -1,1068 +1,14 @@ -use prost::{ - bytes::{Bytes, BytesMut}, - DecodeError, Message, -}; -use prost_types::Any; -use tonic::{metadata::MetadataMap, Code}; - mod error_details; -mod std_messages; - -use super::pb; - pub use error_details::{vec::ErrorDetail, ErrorDetails}; -pub use std_messages::{ - BadRequest, DebugInfo, ErrorInfo, FieldViolation, Help, HelpLink, LocalizedMessage, - PreconditionFailure, PreconditionViolation, QuotaFailure, QuotaViolation, RequestInfo, - ResourceInfo, RetryInfo, -}; - -trait IntoAny { - fn into_any(self) -> Any; -} - -#[allow(dead_code)] -trait FromAny { - fn from_any(any: Any) -> Result - where - Self: Sized; -} - -trait FromAnyRef { - fn from_any_ref(any: &Any) -> Result - where - Self: Sized; -} - -fn gen_details_bytes(code: Code, message: &str, details: Vec) -> Bytes { - let status = pb::Status { - code: code as i32, - message: message.to_owned(), - details, - }; - - let mut buf = BytesMut::with_capacity(status.encoded_len()); - - // Should never panic since `buf` is initialized with sufficient capacity - status.encode(&mut buf).unwrap(); - - buf.freeze() -} - -/// Used to implement associated functions and methods on `tonic::Status`, that -/// allow the addition and extraction of standard error details. This trait is -/// sealed and not meant to be implemented outside of `tonic-types`. -pub trait StatusExt: crate::sealed::Sealed { - /// Generates a `tonic::Status` with error details obtained from an - /// [`ErrorDetails`] struct, and custom metadata. - /// - /// # Examples - /// - /// ``` - /// use tonic::{metadata::MetadataMap, Code, Status}; - /// use tonic_types::{ErrorDetails, StatusExt}; - /// - /// let status = Status::with_error_details_and_metadata( - /// Code::InvalidArgument, - /// "bad request", - /// ErrorDetails::with_bad_request_violation("field", "description"), - /// MetadataMap::new() - /// ); - /// ``` - fn with_error_details_and_metadata( - code: Code, - message: impl Into, - details: ErrorDetails, - metadata: MetadataMap, - ) -> tonic::Status; - - /// Generates a `tonic::Status` with error details obtained from an - /// [`ErrorDetails`] struct. - /// - /// # Examples - /// - /// ``` - /// use tonic::{Code, Status}; - /// use tonic_types::{ErrorDetails, StatusExt}; - /// - /// let status = Status::with_error_details( - /// Code::InvalidArgument, - /// "bad request", - /// ErrorDetails::with_bad_request_violation("field", "description"), - /// ); - /// ``` - fn with_error_details( - code: Code, - message: impl Into, - details: ErrorDetails, - ) -> tonic::Status; - - /// Generates a `tonic::Status` with error details provided in a vector of - /// [`ErrorDetail`] enums, and custom metadata. - /// - /// # Examples - /// - /// ``` - /// use tonic::{metadata::MetadataMap, Code, Status}; - /// use tonic_types::{BadRequest, StatusExt}; - /// - /// let status = Status::with_error_details_vec_and_metadata( - /// Code::InvalidArgument, - /// "bad request", - /// vec![ - /// BadRequest::with_violation("field", "description").into(), - /// ], - /// MetadataMap::new() - /// ); - /// ``` - fn with_error_details_vec_and_metadata( - code: Code, - message: impl Into, - details: impl IntoIterator, - metadata: MetadataMap, - ) -> tonic::Status; - - /// Generates a `tonic::Status` with error details provided in a vector of - /// [`ErrorDetail`] enums. - /// - /// # Examples - /// - /// ``` - /// use tonic::{Code, Status}; - /// use tonic_types::{BadRequest, StatusExt}; - /// - /// let status = Status::with_error_details_vec( - /// Code::InvalidArgument, - /// "bad request", - /// vec![ - /// BadRequest::with_violation("field", "description").into(), - /// ] - /// ); - /// ``` - fn with_error_details_vec( - code: Code, - message: impl Into, - details: impl IntoIterator, - ) -> tonic::Status; - - /// Can be used to check if the error details contained in `tonic::Status` - /// are malformed or not. Tries to get an [`ErrorDetails`] struct from a - /// `tonic::Status`. If some `prost::DecodeError` occurs, it will be - /// returned. If not debugging, consider using - /// [`StatusExt::get_error_details`] or - /// [`StatusExt::get_error_details_vec`]. - /// - /// # Examples - /// - /// ``` - /// use tonic::{Status, Response}; - /// use tonic_types::StatusExt; - /// - /// fn handle_request_result(req_result: Result, Status>) { - /// match req_result { - /// Ok(_) => {}, - /// Err(status) => match status.check_error_details() { - /// Ok(err_details) => { - /// // Handle extracted details - /// } - /// Err(decode_error) => { - /// // Handle decode_error - /// } - /// } - /// }; - /// } - /// ``` - fn check_error_details(&self) -> Result; - - /// Get an [`ErrorDetails`] struct from `tonic::Status`. If some - /// `prost::DecodeError` occurs, an empty [`ErrorDetails`] struct will be - /// returned. - /// - /// # Examples - /// - /// ``` - /// use tonic::{Status, Response}; - /// use tonic_types::StatusExt; - /// - /// fn handle_request_result(req_result: Result, Status>) { - /// match req_result { - /// Ok(_) => {}, - /// Err(status) => { - /// let err_details = status.get_error_details(); - /// if let Some(bad_request) = err_details.bad_request() { - /// // Handle bad_request details - /// } - /// // ... - /// } - /// }; - /// } - /// ``` - fn get_error_details(&self) -> ErrorDetails; - - /// Can be used to check if the error details contained in `tonic::Status` - /// are malformed or not. Tries to get a vector of [`ErrorDetail`] enums - /// from a `tonic::Status`. If some `prost::DecodeError` occurs, it will be - /// returned. If not debugging, consider using - /// [`StatusExt::get_error_details_vec`] or - /// [`StatusExt::get_error_details`]. - /// - /// # Examples - /// - /// ``` - /// use tonic::{Status, Response}; - /// use tonic_types::StatusExt; - /// - /// fn handle_request_result(req_result: Result, Status>) { - /// match req_result { - /// Ok(_) => {}, - /// Err(status) => match status.check_error_details_vec() { - /// Ok(err_details) => { - /// // Handle extracted details - /// } - /// Err(decode_error) => { - /// // Handle decode_error - /// } - /// } - /// }; - /// } - /// ``` - fn check_error_details_vec(&self) -> Result, DecodeError>; - - /// Get a vector of [`ErrorDetail`] enums from `tonic::Status`. If some - /// `prost::DecodeError` occurs, an empty vector will be returned. - /// - /// # Examples - /// - /// ``` - /// use tonic::{Status, Response}; - /// use tonic_types::{ErrorDetail, StatusExt}; - /// - /// fn handle_request_result(req_result: Result, Status>) { - /// match req_result { - /// Ok(_) => {}, - /// Err(status) => { - /// let err_details = status.get_error_details_vec(); - /// for err_detail in err_details.iter() { - /// match err_detail { - /// ErrorDetail::BadRequest(bad_request) => { - /// // Handle bad_request details - /// } - /// // ... - /// _ => {} - /// } - /// } - /// } - /// }; - /// } - /// ``` - fn get_error_details_vec(&self) -> Vec; - - /// Get first [`RetryInfo`] 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(retry_info) = status.get_details_retry_info() { - /// // Handle retry_info details - /// } - /// } - /// }; - /// } - /// ``` - fn get_details_retry_info(&self) -> Option; - - /// Get first [`DebugInfo`] 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(debug_info) = status.get_details_debug_info() { - /// // Handle debug_info details - /// } - /// } - /// }; - /// } - /// ``` - fn get_details_debug_info(&self) -> Option; - - /// Get first [`QuotaFailure`] 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(quota_failure) = status.get_details_quota_failure() { - /// // Handle quota_failure details - /// } - /// } - /// }; - /// } - /// ``` - fn get_details_quota_failure(&self) -> Option; - - /// Get first [`ErrorInfo`] 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(error_info) = status.get_details_error_info() { - /// // Handle error_info details - /// } - /// } - /// }; - /// } - /// ``` - 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`. - /// - /// # 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(bad_request) = status.get_details_bad_request() { - /// // Handle bad_request details - /// } - /// } - /// }; - /// } - /// ``` - fn get_details_bad_request(&self) -> Option; - - /// Get first [`RequestInfo`] 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(request_info) = status.get_details_request_info() { - /// // Handle request_info details - /// } - /// } - /// }; - /// } - /// ``` - fn get_details_request_info(&self) -> Option; - - /// Get first [`ResourceInfo`] 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(resource_info) = status.get_details_resource_info() { - /// // Handle resource_info details - /// } - /// } - /// }; - /// } - /// ``` - fn get_details_resource_info(&self) -> Option; - - /// Get first [`Help`] 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(help) = status.get_details_help() { - /// // Handle help details - /// } - /// } - /// }; - /// } - /// ``` - fn get_details_help(&self) -> Option; - - /// Get first [`LocalizedMessage`] 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(localized_message) = status.get_details_localized_message() { - /// // Handle localized_message details - /// } - /// } - /// }; - /// } - /// ``` - fn get_details_localized_message(&self) -> Option; -} - -impl crate::sealed::Sealed for tonic::Status {} - -impl StatusExt for tonic::Status { - fn with_error_details_and_metadata( - code: Code, - message: impl Into, - details: ErrorDetails, - metadata: MetadataMap, - ) -> Self { - let message: String = message.into(); - - let mut conv_details: Vec = Vec::with_capacity(10); - - if let Some(retry_info) = details.retry_info { - conv_details.push(retry_info.into_any()); - } - - if let Some(debug_info) = details.debug_info { - conv_details.push(debug_info.into_any()); - } - - if let Some(quota_failure) = details.quota_failure { - conv_details.push(quota_failure.into_any()); - } - - if let Some(error_info) = details.error_info { - 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()); - } - - if let Some(request_info) = details.request_info { - conv_details.push(request_info.into_any()); - } - - if let Some(resource_info) = details.resource_info { - conv_details.push(resource_info.into_any()); - } - - if let Some(help) = details.help { - conv_details.push(help.into_any()); - } - - if let Some(localized_message) = details.localized_message { - conv_details.push(localized_message.into_any()); - } - - let details = gen_details_bytes(code, &message, conv_details); - - tonic::Status::with_details_and_metadata(code, message, details, metadata) - } - - fn with_error_details(code: Code, message: impl Into, details: ErrorDetails) -> Self { - tonic::Status::with_error_details_and_metadata(code, message, details, MetadataMap::new()) - } - - fn with_error_details_vec_and_metadata( - code: Code, - message: impl Into, - details: impl IntoIterator, - metadata: MetadataMap, - ) -> Self { - let message: String = message.into(); - - let mut conv_details: Vec = Vec::new(); - - for error_detail in details.into_iter() { - match error_detail { - ErrorDetail::RetryInfo(retry_info) => { - conv_details.push(retry_info.into_any()); - } - ErrorDetail::DebugInfo(debug_info) => { - conv_details.push(debug_info.into_any()); - } - ErrorDetail::QuotaFailure(quota_failure) => { - conv_details.push(quota_failure.into_any()); - } - 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()); - } - ErrorDetail::RequestInfo(req_info) => { - conv_details.push(req_info.into_any()); - } - ErrorDetail::ResourceInfo(res_info) => { - conv_details.push(res_info.into_any()); - } - ErrorDetail::Help(help) => { - conv_details.push(help.into_any()); - } - ErrorDetail::LocalizedMessage(loc_message) => { - conv_details.push(loc_message.into_any()); - } - } - } - - let details = gen_details_bytes(code, &message, conv_details); - - tonic::Status::with_details_and_metadata(code, message, details, metadata) - } - - fn with_error_details_vec( - code: Code, - message: impl Into, - details: impl IntoIterator, - ) -> Self { - tonic::Status::with_error_details_vec_and_metadata( - code, - message, - details, - MetadataMap::new(), - ) - } - - fn check_error_details(&self) -> Result { - let status = pb::Status::decode(self.details())?; - - status.check_error_details() - } - - fn get_error_details(&self) -> ErrorDetails { - self.check_error_details().unwrap_or_default() - } - - fn check_error_details_vec(&self) -> Result, DecodeError> { - let status = pb::Status::decode(self.details())?; - - status.check_error_details_vec() - } - - fn get_error_details_vec(&self) -> Vec { - self.check_error_details_vec().unwrap_or_default() - } - - fn get_details_retry_info(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_retry_info() - } - - fn get_details_debug_info(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_debug_info() - } - - fn get_details_quota_failure(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_quota_failure() - } - - fn get_details_error_info(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_error_info() - } - - fn get_details_precondition_failure(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_precondition_failure() - } - fn get_details_bad_request(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_bad_request() - } - - fn get_details_request_info(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_request_info() - } - - fn get_details_resource_info(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_resource_info() - } - - fn get_details_help(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_help() - } - - fn get_details_localized_message(&self) -> Option { - let status = pb::Status::decode(self.details()).ok()?; - - status.get_details_localized_message() - } -} - -impl crate::sealed::Sealed for pb::Status {} - -/// Used to implement associated functions and methods on `pb::Status`, that -/// allow the extraction of standard error details. This trait is -/// sealed and not meant to be implemented outside of `tonic-types`. -pub trait RpcStatusExt: crate::sealed::Sealed { - /// Can be used to check if the error details contained in `pb::Status` - /// are malformed or not. Tries to get an [`ErrorDetails`] struct from a - /// `pb::Status`. If some `prost::DecodeError` occurs, it will be - /// returned. If not debugging, consider using - /// [`RpcStatusExt::get_error_details`] or - /// [`RpcStatusExt::get_error_details_vec`]. - fn check_error_details(&self) -> Result; - - /// Get an [`ErrorDetails`] struct from `pb::Status`. If some - /// `prost::DecodeError` occurs, an empty [`ErrorDetails`] struct will be - /// returned. - fn get_error_details(&self) -> ErrorDetails; - - /// Can be used to check if the error details contained in `pb::Status` - /// are malformed or not. Tries to get a vector of [`ErrorDetail`] enums - /// from a `pb::Status`. If some `prost::DecodeError` occurs, it will be - /// returned. If not debugging, consider using - /// [`StatusExt::get_error_details_vec`] or - /// [`StatusExt::get_error_details`]. - fn check_error_details_vec(&self) -> Result, DecodeError>; - - /// Get a vector of [`ErrorDetail`] enums from `pb::Status`. If some - /// `prost::DecodeError` occurs, an empty vector will be returned. - fn get_error_details_vec(&self) -> Vec; - - /// Get first [`RetryInfo`] details found on `pb::Status`, if any. If - /// some `prost::DecodeError` occurs, returns `None`. - fn get_details_retry_info(&self) -> Option; - - /// Get first [`DebugInfo`] details found on `pb::Status`, if any. If - /// some `prost::DecodeError` occurs, returns `None`. - fn get_details_debug_info(&self) -> Option; - - /// Get first [`QuotaFailure`] details found on `pb::Status`, if any. - /// If some `prost::DecodeError` occurs, returns `None`. - fn get_details_quota_failure(&self) -> Option; - - /// Get first [`ErrorInfo`] details found on `pb::Status`, if any. If - /// some `prost::DecodeError` occurs, returns `None`. - fn get_details_error_info(&self) -> Option; - - /// Get first [`PreconditionFailure`] details found on `pb::Status`, - /// if any. If some `prost::DecodeError` occurs, returns `None`. - fn get_details_precondition_failure(&self) -> Option; - - /// Get first [`BadRequest`] details found on `pb::Status`, if any. If - /// some `prost::DecodeError` occurs, returns `None`. - fn get_details_bad_request(&self) -> Option; - - /// Get first [`RequestInfo`] details found on `pb::Status`, if any. - /// If some `prost::DecodeError` occurs, returns `None`. - fn get_details_request_info(&self) -> Option; - - /// Get first [`ResourceInfo`] details found on `pb::Status`, if any. - /// If some `prost::DecodeError` occurs, returns `None`. - fn get_details_resource_info(&self) -> Option; - - /// Get first [`Help`] details found on `pb::Status`, if any. If some - /// `prost::DecodeError` occurs, returns `None`. - fn get_details_help(&self) -> Option; - - /// Get first [`LocalizedMessage`] details found on `pb::Status`, if - /// any. If some `prost::DecodeError` occurs, returns `None`. - fn get_details_localized_message(&self) -> Option; -} - -impl RpcStatusExt for pb::Status { - fn check_error_details(&self) -> Result { - let mut details = ErrorDetails::new(); - - for any in self.details.iter() { - match any.type_url.as_str() { - RetryInfo::TYPE_URL => { - details.retry_info = Some(RetryInfo::from_any_ref(any)?); - } - DebugInfo::TYPE_URL => { - details.debug_info = Some(DebugInfo::from_any_ref(any)?); - } - QuotaFailure::TYPE_URL => { - details.quota_failure = Some(QuotaFailure::from_any_ref(any)?); - } - ErrorInfo::TYPE_URL => { - details.error_info = Some(ErrorInfo::from_any_ref(any)?); - } - PreconditionFailure::TYPE_URL => { - details.precondition_failure = Some(PreconditionFailure::from_any_ref(any)?); - } - BadRequest::TYPE_URL => { - details.bad_request = Some(BadRequest::from_any_ref(any)?); - } - RequestInfo::TYPE_URL => { - details.request_info = Some(RequestInfo::from_any_ref(any)?); - } - ResourceInfo::TYPE_URL => { - details.resource_info = Some(ResourceInfo::from_any_ref(any)?); - } - Help::TYPE_URL => { - details.help = Some(Help::from_any_ref(any)?); - } - LocalizedMessage::TYPE_URL => { - details.localized_message = Some(LocalizedMessage::from_any_ref(any)?); - } - _ => {} - } - } - - Ok(details) - } - - fn get_error_details(&self) -> ErrorDetails { - self.check_error_details().unwrap_or_default() - } - - fn check_error_details_vec(&self) -> Result, DecodeError> { - let mut details: Vec = Vec::with_capacity(self.details.len()); - - for any in self.details.iter() { - match any.type_url.as_str() { - RetryInfo::TYPE_URL => { - details.push(RetryInfo::from_any_ref(any)?.into()); - } - DebugInfo::TYPE_URL => { - details.push(DebugInfo::from_any_ref(any)?.into()); - } - QuotaFailure::TYPE_URL => { - details.push(QuotaFailure::from_any_ref(any)?.into()); - } - ErrorInfo::TYPE_URL => { - details.push(ErrorInfo::from_any_ref(any)?.into()); - } - PreconditionFailure::TYPE_URL => { - details.push(PreconditionFailure::from_any_ref(any)?.into()); - } - BadRequest::TYPE_URL => { - details.push(BadRequest::from_any_ref(any)?.into()); - } - RequestInfo::TYPE_URL => { - details.push(RequestInfo::from_any_ref(any)?.into()); - } - ResourceInfo::TYPE_URL => { - details.push(ResourceInfo::from_any_ref(any)?.into()); - } - Help::TYPE_URL => { - details.push(Help::from_any_ref(any)?.into()); - } - LocalizedMessage::TYPE_URL => { - details.push(LocalizedMessage::from_any_ref(any)?.into()); - } - _ => {} - } - } - - Ok(details) - } - - fn get_error_details_vec(&self) -> Vec { - self.check_error_details_vec().unwrap_or_default() - } - - fn get_details_retry_info(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == RetryInfo::TYPE_URL { - if let Ok(detail) = RetryInfo::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } - - fn get_details_debug_info(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == DebugInfo::TYPE_URL { - if let Ok(detail) = DebugInfo::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } - - fn get_details_quota_failure(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == QuotaFailure::TYPE_URL { - if let Ok(detail) = QuotaFailure::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } - - fn get_details_error_info(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == ErrorInfo::TYPE_URL { - if let Ok(detail) = ErrorInfo::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } - - fn get_details_precondition_failure(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == PreconditionFailure::TYPE_URL { - if let Ok(detail) = PreconditionFailure::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } - - fn get_details_bad_request(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == BadRequest::TYPE_URL { - if let Ok(detail) = BadRequest::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } - - fn get_details_request_info(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == RequestInfo::TYPE_URL { - if let Ok(detail) = RequestInfo::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } - - fn get_details_resource_info(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == ResourceInfo::TYPE_URL { - if let Ok(detail) = ResourceInfo::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } - - fn get_details_help(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == Help::TYPE_URL { - if let Ok(detail) = Help::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } - - fn get_details_localized_message(&self) -> Option { - for any in self.details.iter() { - if any.type_url.as_str() == LocalizedMessage::TYPE_URL { - if let Ok(detail) = LocalizedMessage::from_any_ref(any) { - return Some(detail); - } - } - } - - None - } -} - -#[cfg(test)] -mod tests { - use std::{collections::HashMap, time::Duration}; - use tonic::{Code, Status}; - - use super::{ - BadRequest, DebugInfo, ErrorDetails, ErrorInfo, Help, LocalizedMessage, - PreconditionFailure, QuotaFailure, RequestInfo, ResourceInfo, RetryInfo, StatusExt, - }; - - #[test] - fn gen_status_with_details() { - let mut metadata = HashMap::new(); - metadata.insert("limitPerRequest".into(), "100".into()); - - let mut err_details = ErrorDetails::new(); - - err_details - .set_retry_info(Some(Duration::from_secs(5))) - .set_debug_info( - vec!["trace3".into(), "trace2".into(), "trace1".into()], - "details", - ) - .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") - .set_request_info("request-id", "some-request-data") - .set_resource_info("resource-type", "resource-name", "owner", "description") - .add_help_link("link to resource", "resource.example.local") - .set_localized_message("en-US", "message for the user"); - - let fmt_details = format!("{:?}", err_details); - - let err_details_vec = vec![ - RetryInfo::new(Some(Duration::from_secs(5))).into(), - DebugInfo::new( - vec!["trace3".into(), "trace2".into(), "trace1".into()], - "details", - ) - .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(), - RequestInfo::new("request-id", "some-request-data").into(), - ResourceInfo::new("resource-type", "resource-name", "owner", "description").into(), - Help::with_link("link to resource", "resource.example.local").into(), - LocalizedMessage::new("en-US", "message for the user").into(), - ]; - - let fmt_details_vec = format!("{:?}", err_details_vec); - - let status_from_struct = Status::with_error_details( - Code::InvalidArgument, - "error with bad request details", - err_details, - ); - - let status_from_vec = Status::with_error_details_vec( - Code::InvalidArgument, - "error with bad request details", - err_details_vec, - ); - - let ext_details = match status_from_vec.check_error_details() { - Ok(ext_details) => ext_details, - Err(err) => panic!( - "Error extracting details struct from status_from_vec: {:?}", - err - ), - }; - - let fmt_ext_details = format!("{:?}", ext_details); - - assert!( - fmt_ext_details.eq(&fmt_details), - "Extracted details struct differs from original details struct" - ); +mod std_messages; +pub use std_messages::*; - let ext_details_vec = match status_from_struct.check_error_details_vec() { - Ok(ext_details) => ext_details, - Err(err) => panic!( - "Error extracting details_vec from status_from_struct: {:?}", - err - ), - }; +mod status_ext; +pub use status_ext::StatusExt; - let fmt_ext_details_vec = format!("{:?}", ext_details_vec); +mod rpc_status_ext; +pub use rpc_status_ext::RpcStatusExt; - assert!( - fmt_ext_details_vec.eq(&fmt_details_vec), - "Extracted details vec differs from original details vec" - ); - } -} +mod helpers; +use helpers::{gen_details_bytes, FromAny, FromAnyRef, IntoAny}; diff --git a/tonic-types/src/richer_error/rpc_status_ext.rs b/tonic-types/src/richer_error/rpc_status_ext.rs new file mode 100644 index 000000000..444350873 --- /dev/null +++ b/tonic-types/src/richer_error/rpc_status_ext.rs @@ -0,0 +1,291 @@ +use prost::DecodeError; + +use crate::pb; + +use super::*; + +/// Used to implement associated functions and methods on `pb::Status`, that +/// allow the extraction of standard error details. This trait is +/// sealed and not meant to be implemented outside of `tonic-types`. +pub trait RpcStatusExt: crate::sealed::Sealed { + /// Can be used to check if the error details contained in `pb::Status` + /// are malformed or not. Tries to get an [`ErrorDetails`] struct from a + /// `pb::Status`. If some `prost::DecodeError` occurs, it will be + /// returned. If not debugging, consider using + /// [`RpcStatusExt::get_error_details`] or + /// [`RpcStatusExt::get_error_details_vec`]. + fn check_error_details(&self) -> Result; + + /// Get an [`ErrorDetails`] struct from `pb::Status`. If some + /// `prost::DecodeError` occurs, an empty [`ErrorDetails`] struct will be + /// returned. + fn get_error_details(&self) -> ErrorDetails; + + /// Can be used to check if the error details contained in `pb::Status` + /// are malformed or not. Tries to get a vector of [`ErrorDetail`] enums + /// from a `pb::Status`. If some `prost::DecodeError` occurs, it will be + /// returned. If not debugging, consider using + /// [`StatusExt::get_error_details_vec`] or + /// [`StatusExt::get_error_details`]. + fn check_error_details_vec(&self) -> Result, DecodeError>; + + /// Get a vector of [`ErrorDetail`] enums from `pb::Status`. If some + /// `prost::DecodeError` occurs, an empty vector will be returned. + fn get_error_details_vec(&self) -> Vec; + + /// Get first [`RetryInfo`] details found on `pb::Status`, if any. If + /// some `prost::DecodeError` occurs, returns `None`. + fn get_details_retry_info(&self) -> Option; + + /// Get first [`DebugInfo`] details found on `pb::Status`, if any. If + /// some `prost::DecodeError` occurs, returns `None`. + fn get_details_debug_info(&self) -> Option; + + /// Get first [`QuotaFailure`] details found on `pb::Status`, if any. + /// If some `prost::DecodeError` occurs, returns `None`. + fn get_details_quota_failure(&self) -> Option; + + /// Get first [`ErrorInfo`] details found on `pb::Status`, if any. If + /// some `prost::DecodeError` occurs, returns `None`. + fn get_details_error_info(&self) -> Option; + + /// Get first [`PreconditionFailure`] details found on `pb::Status`, + /// if any. If some `prost::DecodeError` occurs, returns `None`. + fn get_details_precondition_failure(&self) -> Option; + + /// Get first [`BadRequest`] details found on `pb::Status`, if any. If + /// some `prost::DecodeError` occurs, returns `None`. + fn get_details_bad_request(&self) -> Option; + + /// Get first [`RequestInfo`] details found on `pb::Status`, if any. + /// If some `prost::DecodeError` occurs, returns `None`. + fn get_details_request_info(&self) -> Option; + + /// Get first [`ResourceInfo`] details found on `pb::Status`, if any. + /// If some `prost::DecodeError` occurs, returns `None`. + fn get_details_resource_info(&self) -> Option; + + /// Get first [`Help`] details found on `pb::Status`, if any. If some + /// `prost::DecodeError` occurs, returns `None`. + fn get_details_help(&self) -> Option; + + /// Get first [`LocalizedMessage`] details found on `pb::Status`, if + /// any. If some `prost::DecodeError` occurs, returns `None`. + fn get_details_localized_message(&self) -> Option; +} + +impl RpcStatusExt for pb::Status { + fn check_error_details(&self) -> Result { + let mut details = ErrorDetails::new(); + + for any in self.details.iter() { + match any.type_url.as_str() { + RetryInfo::TYPE_URL => { + details.retry_info = Some(RetryInfo::from_any_ref(any)?); + } + DebugInfo::TYPE_URL => { + details.debug_info = Some(DebugInfo::from_any_ref(any)?); + } + QuotaFailure::TYPE_URL => { + details.quota_failure = Some(QuotaFailure::from_any_ref(any)?); + } + ErrorInfo::TYPE_URL => { + details.error_info = Some(ErrorInfo::from_any_ref(any)?); + } + PreconditionFailure::TYPE_URL => { + details.precondition_failure = Some(PreconditionFailure::from_any_ref(any)?); + } + BadRequest::TYPE_URL => { + details.bad_request = Some(BadRequest::from_any_ref(any)?); + } + RequestInfo::TYPE_URL => { + details.request_info = Some(RequestInfo::from_any_ref(any)?); + } + ResourceInfo::TYPE_URL => { + details.resource_info = Some(ResourceInfo::from_any_ref(any)?); + } + Help::TYPE_URL => { + details.help = Some(Help::from_any_ref(any)?); + } + LocalizedMessage::TYPE_URL => { + details.localized_message = Some(LocalizedMessage::from_any_ref(any)?); + } + _ => {} + } + } + + Ok(details) + } + + fn get_error_details(&self) -> ErrorDetails { + self.check_error_details().unwrap_or_default() + } + + fn check_error_details_vec(&self) -> Result, DecodeError> { + let mut details: Vec = Vec::with_capacity(self.details.len()); + + for any in self.details.iter() { + match any.type_url.as_str() { + RetryInfo::TYPE_URL => { + details.push(RetryInfo::from_any_ref(any)?.into()); + } + DebugInfo::TYPE_URL => { + details.push(DebugInfo::from_any_ref(any)?.into()); + } + QuotaFailure::TYPE_URL => { + details.push(QuotaFailure::from_any_ref(any)?.into()); + } + ErrorInfo::TYPE_URL => { + details.push(ErrorInfo::from_any_ref(any)?.into()); + } + PreconditionFailure::TYPE_URL => { + details.push(PreconditionFailure::from_any_ref(any)?.into()); + } + BadRequest::TYPE_URL => { + details.push(BadRequest::from_any_ref(any)?.into()); + } + RequestInfo::TYPE_URL => { + details.push(RequestInfo::from_any_ref(any)?.into()); + } + ResourceInfo::TYPE_URL => { + details.push(ResourceInfo::from_any_ref(any)?.into()); + } + Help::TYPE_URL => { + details.push(Help::from_any_ref(any)?.into()); + } + LocalizedMessage::TYPE_URL => { + details.push(LocalizedMessage::from_any_ref(any)?.into()); + } + _ => {} + } + } + + Ok(details) + } + + fn get_error_details_vec(&self) -> Vec { + self.check_error_details_vec().unwrap_or_default() + } + + fn get_details_retry_info(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == RetryInfo::TYPE_URL { + if let Ok(detail) = RetryInfo::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } + + fn get_details_debug_info(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == DebugInfo::TYPE_URL { + if let Ok(detail) = DebugInfo::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } + + fn get_details_quota_failure(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == QuotaFailure::TYPE_URL { + if let Ok(detail) = QuotaFailure::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } + + fn get_details_error_info(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == ErrorInfo::TYPE_URL { + if let Ok(detail) = ErrorInfo::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } + + fn get_details_precondition_failure(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == PreconditionFailure::TYPE_URL { + if let Ok(detail) = PreconditionFailure::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } + + fn get_details_bad_request(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == BadRequest::TYPE_URL { + if let Ok(detail) = BadRequest::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } + + fn get_details_request_info(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == RequestInfo::TYPE_URL { + if let Ok(detail) = RequestInfo::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } + + fn get_details_resource_info(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == ResourceInfo::TYPE_URL { + if let Ok(detail) = ResourceInfo::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } + + fn get_details_help(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == Help::TYPE_URL { + if let Ok(detail) = Help::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } + + fn get_details_localized_message(&self) -> Option { + for any in self.details.iter() { + if any.type_url.as_str() == LocalizedMessage::TYPE_URL { + if let Ok(detail) = LocalizedMessage::from_any_ref(any) { + return Some(detail); + } + } + } + + None + } +} + +impl crate::sealed::Sealed for pb::Status {} diff --git a/tonic-types/src/richer_error/status_ext.rs b/tonic-types/src/richer_error/status_ext.rs new file mode 100644 index 000000000..a134e21ac --- /dev/null +++ b/tonic-types/src/richer_error/status_ext.rs @@ -0,0 +1,739 @@ +use prost::{DecodeError, Message}; +use prost_types::Any; +use tonic::{metadata::MetadataMap, Code}; + +use crate::pb; + +use super::*; + +/// Used to implement associated functions and methods on `tonic::Status`, that +/// allow the addition and extraction of standard error details. This trait is +/// sealed and not meant to be implemented outside of `tonic-types`. +pub trait StatusExt: crate::sealed::Sealed { + /// Generates a `tonic::Status` with error details obtained from an + /// [`ErrorDetails`] struct, and custom metadata. + /// + /// # Examples + /// + /// ``` + /// use tonic::{metadata::MetadataMap, Code, Status}; + /// use tonic_types::{ErrorDetails, StatusExt}; + /// + /// let status = Status::with_error_details_and_metadata( + /// Code::InvalidArgument, + /// "bad request", + /// ErrorDetails::with_bad_request_violation("field", "description"), + /// MetadataMap::new() + /// ); + /// ``` + fn with_error_details_and_metadata( + code: Code, + message: impl Into, + details: ErrorDetails, + metadata: MetadataMap, + ) -> tonic::Status; + + /// Generates a `tonic::Status` with error details obtained from an + /// [`ErrorDetails`] struct. + /// + /// # Examples + /// + /// ``` + /// use tonic::{Code, Status}; + /// use tonic_types::{ErrorDetails, StatusExt}; + /// + /// let status = Status::with_error_details( + /// Code::InvalidArgument, + /// "bad request", + /// ErrorDetails::with_bad_request_violation("field", "description"), + /// ); + /// ``` + fn with_error_details( + code: Code, + message: impl Into, + details: ErrorDetails, + ) -> tonic::Status; + + /// Generates a `tonic::Status` with error details provided in a vector of + /// [`ErrorDetail`] enums, and custom metadata. + /// + /// # Examples + /// + /// ``` + /// use tonic::{metadata::MetadataMap, Code, Status}; + /// use tonic_types::{BadRequest, StatusExt}; + /// + /// let status = Status::with_error_details_vec_and_metadata( + /// Code::InvalidArgument, + /// "bad request", + /// vec![ + /// BadRequest::with_violation("field", "description").into(), + /// ], + /// MetadataMap::new() + /// ); + /// ``` + fn with_error_details_vec_and_metadata( + code: Code, + message: impl Into, + details: impl IntoIterator, + metadata: MetadataMap, + ) -> tonic::Status; + + /// Generates a `tonic::Status` with error details provided in a vector of + /// [`ErrorDetail`] enums. + /// + /// # Examples + /// + /// ``` + /// use tonic::{Code, Status}; + /// use tonic_types::{BadRequest, StatusExt}; + /// + /// let status = Status::with_error_details_vec( + /// Code::InvalidArgument, + /// "bad request", + /// vec![ + /// BadRequest::with_violation("field", "description").into(), + /// ] + /// ); + /// ``` + fn with_error_details_vec( + code: Code, + message: impl Into, + details: impl IntoIterator, + ) -> tonic::Status; + + /// Can be used to check if the error details contained in `tonic::Status` + /// are malformed or not. Tries to get an [`ErrorDetails`] struct from a + /// `tonic::Status`. If some `prost::DecodeError` occurs, it will be + /// returned. If not debugging, consider using + /// [`StatusExt::get_error_details`] or + /// [`StatusExt::get_error_details_vec`]. + /// + /// # Examples + /// + /// ``` + /// use tonic::{Status, Response}; + /// use tonic_types::StatusExt; + /// + /// fn handle_request_result(req_result: Result, Status>) { + /// match req_result { + /// Ok(_) => {}, + /// Err(status) => match status.check_error_details() { + /// Ok(err_details) => { + /// // Handle extracted details + /// } + /// Err(decode_error) => { + /// // Handle decode_error + /// } + /// } + /// }; + /// } + /// ``` + fn check_error_details(&self) -> Result; + + /// Get an [`ErrorDetails`] struct from `tonic::Status`. If some + /// `prost::DecodeError` occurs, an empty [`ErrorDetails`] struct will be + /// returned. + /// + /// # Examples + /// + /// ``` + /// use tonic::{Status, Response}; + /// use tonic_types::StatusExt; + /// + /// fn handle_request_result(req_result: Result, Status>) { + /// match req_result { + /// Ok(_) => {}, + /// Err(status) => { + /// let err_details = status.get_error_details(); + /// if let Some(bad_request) = err_details.bad_request() { + /// // Handle bad_request details + /// } + /// // ... + /// } + /// }; + /// } + /// ``` + fn get_error_details(&self) -> ErrorDetails; + + /// Can be used to check if the error details contained in `tonic::Status` + /// are malformed or not. Tries to get a vector of [`ErrorDetail`] enums + /// from a `tonic::Status`. If some `prost::DecodeError` occurs, it will be + /// returned. If not debugging, consider using + /// [`StatusExt::get_error_details_vec`] or + /// [`StatusExt::get_error_details`]. + /// + /// # Examples + /// + /// ``` + /// use tonic::{Status, Response}; + /// use tonic_types::StatusExt; + /// + /// fn handle_request_result(req_result: Result, Status>) { + /// match req_result { + /// Ok(_) => {}, + /// Err(status) => match status.check_error_details_vec() { + /// Ok(err_details) => { + /// // Handle extracted details + /// } + /// Err(decode_error) => { + /// // Handle decode_error + /// } + /// } + /// }; + /// } + /// ``` + fn check_error_details_vec(&self) -> Result, DecodeError>; + + /// Get a vector of [`ErrorDetail`] enums from `tonic::Status`. If some + /// `prost::DecodeError` occurs, an empty vector will be returned. + /// + /// # Examples + /// + /// ``` + /// use tonic::{Status, Response}; + /// use tonic_types::{ErrorDetail, StatusExt}; + /// + /// fn handle_request_result(req_result: Result, Status>) { + /// match req_result { + /// Ok(_) => {}, + /// Err(status) => { + /// let err_details = status.get_error_details_vec(); + /// for err_detail in err_details.iter() { + /// match err_detail { + /// ErrorDetail::BadRequest(bad_request) => { + /// // Handle bad_request details + /// } + /// // ... + /// _ => {} + /// } + /// } + /// } + /// }; + /// } + /// ``` + fn get_error_details_vec(&self) -> Vec; + + /// Get first [`RetryInfo`] 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(retry_info) = status.get_details_retry_info() { + /// // Handle retry_info details + /// } + /// } + /// }; + /// } + /// ``` + fn get_details_retry_info(&self) -> Option; + + /// Get first [`DebugInfo`] 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(debug_info) = status.get_details_debug_info() { + /// // Handle debug_info details + /// } + /// } + /// }; + /// } + /// ``` + fn get_details_debug_info(&self) -> Option; + + /// Get first [`QuotaFailure`] 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(quota_failure) = status.get_details_quota_failure() { + /// // Handle quota_failure details + /// } + /// } + /// }; + /// } + /// ``` + fn get_details_quota_failure(&self) -> Option; + + /// Get first [`ErrorInfo`] 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(error_info) = status.get_details_error_info() { + /// // Handle error_info details + /// } + /// } + /// }; + /// } + /// ``` + 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`. + /// + /// # 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(bad_request) = status.get_details_bad_request() { + /// // Handle bad_request details + /// } + /// } + /// }; + /// } + /// ``` + fn get_details_bad_request(&self) -> Option; + + /// Get first [`RequestInfo`] 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(request_info) = status.get_details_request_info() { + /// // Handle request_info details + /// } + /// } + /// }; + /// } + /// ``` + fn get_details_request_info(&self) -> Option; + + /// Get first [`ResourceInfo`] 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(resource_info) = status.get_details_resource_info() { + /// // Handle resource_info details + /// } + /// } + /// }; + /// } + /// ``` + fn get_details_resource_info(&self) -> Option; + + /// Get first [`Help`] 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(help) = status.get_details_help() { + /// // Handle help details + /// } + /// } + /// }; + /// } + /// ``` + fn get_details_help(&self) -> Option; + + /// Get first [`LocalizedMessage`] 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(localized_message) = status.get_details_localized_message() { + /// // Handle localized_message details + /// } + /// } + /// }; + /// } + /// ``` + fn get_details_localized_message(&self) -> Option; +} + +impl crate::sealed::Sealed for tonic::Status {} + +impl StatusExt for tonic::Status { + fn with_error_details_and_metadata( + code: Code, + message: impl Into, + details: ErrorDetails, + metadata: MetadataMap, + ) -> Self { + let message: String = message.into(); + + let mut conv_details: Vec = Vec::with_capacity(10); + + if let Some(retry_info) = details.retry_info { + conv_details.push(retry_info.into_any()); + } + + if let Some(debug_info) = details.debug_info { + conv_details.push(debug_info.into_any()); + } + + if let Some(quota_failure) = details.quota_failure { + conv_details.push(quota_failure.into_any()); + } + + if let Some(error_info) = details.error_info { + 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()); + } + + if let Some(request_info) = details.request_info { + conv_details.push(request_info.into_any()); + } + + if let Some(resource_info) = details.resource_info { + conv_details.push(resource_info.into_any()); + } + + if let Some(help) = details.help { + conv_details.push(help.into_any()); + } + + if let Some(localized_message) = details.localized_message { + conv_details.push(localized_message.into_any()); + } + + let details = gen_details_bytes(code, &message, conv_details); + + tonic::Status::with_details_and_metadata(code, message, details, metadata) + } + + fn with_error_details(code: Code, message: impl Into, details: ErrorDetails) -> Self { + tonic::Status::with_error_details_and_metadata(code, message, details, MetadataMap::new()) + } + + fn with_error_details_vec_and_metadata( + code: Code, + message: impl Into, + details: impl IntoIterator, + metadata: MetadataMap, + ) -> Self { + let message: String = message.into(); + + let mut conv_details: Vec = Vec::new(); + + for error_detail in details.into_iter() { + match error_detail { + ErrorDetail::RetryInfo(retry_info) => { + conv_details.push(retry_info.into_any()); + } + ErrorDetail::DebugInfo(debug_info) => { + conv_details.push(debug_info.into_any()); + } + ErrorDetail::QuotaFailure(quota_failure) => { + conv_details.push(quota_failure.into_any()); + } + 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()); + } + ErrorDetail::RequestInfo(req_info) => { + conv_details.push(req_info.into_any()); + } + ErrorDetail::ResourceInfo(res_info) => { + conv_details.push(res_info.into_any()); + } + ErrorDetail::Help(help) => { + conv_details.push(help.into_any()); + } + ErrorDetail::LocalizedMessage(loc_message) => { + conv_details.push(loc_message.into_any()); + } + } + } + + let details = gen_details_bytes(code, &message, conv_details); + + tonic::Status::with_details_and_metadata(code, message, details, metadata) + } + + fn with_error_details_vec( + code: Code, + message: impl Into, + details: impl IntoIterator, + ) -> Self { + tonic::Status::with_error_details_vec_and_metadata( + code, + message, + details, + MetadataMap::new(), + ) + } + + fn check_error_details(&self) -> Result { + let status = pb::Status::decode(self.details())?; + + status.check_error_details() + } + + fn get_error_details(&self) -> ErrorDetails { + self.check_error_details().unwrap_or_default() + } + + fn check_error_details_vec(&self) -> Result, DecodeError> { + let status = pb::Status::decode(self.details())?; + + status.check_error_details_vec() + } + + fn get_error_details_vec(&self) -> Vec { + self.check_error_details_vec().unwrap_or_default() + } + + fn get_details_retry_info(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_retry_info() + } + + fn get_details_debug_info(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_debug_info() + } + + fn get_details_quota_failure(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_quota_failure() + } + + fn get_details_error_info(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_error_info() + } + + fn get_details_precondition_failure(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_precondition_failure() + } + + fn get_details_bad_request(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_bad_request() + } + + fn get_details_request_info(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_request_info() + } + + fn get_details_resource_info(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_resource_info() + } + + fn get_details_help(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_help() + } + + fn get_details_localized_message(&self) -> Option { + let status = pb::Status::decode(self.details()).ok()?; + + status.get_details_localized_message() + } +} + +#[cfg(test)] +mod tests { + use std::{collections::HashMap, time::Duration}; + use tonic::{Code, Status}; + + use super::{ + BadRequest, DebugInfo, ErrorDetails, ErrorInfo, Help, LocalizedMessage, + PreconditionFailure, QuotaFailure, RequestInfo, ResourceInfo, RetryInfo, StatusExt, + }; + + #[test] + fn gen_status_with_details() { + let mut metadata = HashMap::new(); + metadata.insert("limitPerRequest".into(), "100".into()); + + let mut err_details = ErrorDetails::new(); + + err_details + .set_retry_info(Some(Duration::from_secs(5))) + .set_debug_info( + vec!["trace3".into(), "trace2".into(), "trace1".into()], + "details", + ) + .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") + .set_request_info("request-id", "some-request-data") + .set_resource_info("resource-type", "resource-name", "owner", "description") + .add_help_link("link to resource", "resource.example.local") + .set_localized_message("en-US", "message for the user"); + + let fmt_details = format!("{:?}", err_details); + + let err_details_vec = vec![ + RetryInfo::new(Some(Duration::from_secs(5))).into(), + DebugInfo::new( + vec!["trace3".into(), "trace2".into(), "trace1".into()], + "details", + ) + .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(), + RequestInfo::new("request-id", "some-request-data").into(), + ResourceInfo::new("resource-type", "resource-name", "owner", "description").into(), + Help::with_link("link to resource", "resource.example.local").into(), + LocalizedMessage::new("en-US", "message for the user").into(), + ]; + + let fmt_details_vec = format!("{:?}", err_details_vec); + + let status_from_struct = Status::with_error_details( + Code::InvalidArgument, + "error with bad request details", + err_details, + ); + + let status_from_vec = Status::with_error_details_vec( + Code::InvalidArgument, + "error with bad request details", + err_details_vec, + ); + + let ext_details = match status_from_vec.check_error_details() { + Ok(ext_details) => ext_details, + Err(err) => panic!( + "Error extracting details struct from status_from_vec: {:?}", + err + ), + }; + + let fmt_ext_details = format!("{:?}", ext_details); + + assert!( + fmt_ext_details.eq(&fmt_details), + "Extracted details struct differs from original details struct" + ); + + let ext_details_vec = match status_from_struct.check_error_details_vec() { + Ok(ext_details) => ext_details, + Err(err) => panic!( + "Error extracting details_vec from status_from_struct: {:?}", + err + ), + }; + + let fmt_ext_details_vec = format!("{:?}", ext_details_vec); + + assert!( + fmt_ext_details_vec.eq(&fmt_details_vec), + "Extracted details vec differs from original details vec" + ); + } +} diff --git a/tonic-types/src/richer_error/std_messages/bad_request.rs b/tonic-types/src/richer_error/std_messages/bad_request.rs index c9fcb984a..f0eac4d1e 100644 --- a/tonic-types/src/richer_error/std_messages/bad_request.rs +++ b/tonic-types/src/richer_error/std_messages/bad_request.rs @@ -1,9 +1,9 @@ use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::pb; -use super::super::{pb, FromAny, IntoAny}; +use super::super::*; /// Used at the `field_violations` field of the [`BadRequest`] struct. /// Describes a single bad request field. 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 9c450c774..795979fb8 100644 --- a/tonic-types/src/richer_error/std_messages/debug_info.rs +++ b/tonic-types/src/richer_error/std_messages/debug_info.rs @@ -1,9 +1,9 @@ use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::pb; -use super::super::{pb, FromAny, IntoAny}; +use super::super::*; /// Used to encode/decode the `DebugInfo` standard error message described in /// [error_details.proto]. Describes additional debugging info. 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 8d535a157..3ab321aaf 100644 --- a/tonic-types/src/richer_error/std_messages/error_info.rs +++ b/tonic-types/src/richer_error/std_messages/error_info.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::pb; -use super::super::{pb, FromAny, IntoAny}; +use super::super::*; /// Used to encode/decode the `ErrorInfo` standard error message described in /// [error_details.proto]. Describes the cause of the error with structured diff --git a/tonic-types/src/richer_error/std_messages/help.rs b/tonic-types/src/richer_error/std_messages/help.rs index 08549b2e9..754592ef5 100644 --- a/tonic-types/src/richer_error/std_messages/help.rs +++ b/tonic-types/src/richer_error/std_messages/help.rs @@ -1,9 +1,9 @@ use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::pb; -use super::super::{pb, FromAny, IntoAny}; +use super::super::*; /// Used at the `links` field of the [`Help`] struct. Describes a URL link. #[derive(Clone, Debug)] diff --git a/tonic-types/src/richer_error/std_messages/loc_message.rs b/tonic-types/src/richer_error/std_messages/loc_message.rs index 98ab72bce..602405664 100644 --- a/tonic-types/src/richer_error/std_messages/loc_message.rs +++ b/tonic-types/src/richer_error/std_messages/loc_message.rs @@ -1,9 +1,9 @@ use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::pb; -use super::super::{pb, FromAny, IntoAny}; +use super::super::*; /// Used to encode/decode the `LocalizedMessage` standard error message /// described in [error_details.proto]. Provides a localized error message diff --git a/tonic-types/src/richer_error/std_messages/mod.rs b/tonic-types/src/richer_error/std_messages/mod.rs index 25b773b0b..b39f6d25e 100644 --- a/tonic-types/src/richer_error/std_messages/mod.rs +++ b/tonic-types/src/richer_error/std_messages/mod.rs @@ -1,39 +1,29 @@ mod retry_info; - pub use retry_info::RetryInfo; mod debug_info; - pub use debug_info::DebugInfo; mod quota_failure; - pub use quota_failure::{QuotaFailure, QuotaViolation}; 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}; mod request_info; - pub use request_info::RequestInfo; mod resource_info; - pub use resource_info::ResourceInfo; mod help; - pub use help::{Help, HelpLink}; mod loc_message; - pub use loc_message::LocalizedMessage; diff --git a/tonic-types/src/richer_error/std_messages/prec_failure.rs b/tonic-types/src/richer_error/std_messages/prec_failure.rs index a8a5c50f1..f07976cf6 100644 --- a/tonic-types/src/richer_error/std_messages/prec_failure.rs +++ b/tonic-types/src/richer_error/std_messages/prec_failure.rs @@ -1,9 +1,9 @@ use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::pb; -use super::super::{pb, FromAny, IntoAny}; +use super::super::*; /// Used at the `violations` field of the [`PreconditionFailure`] struct. /// Describes a single precondition failure. 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 23d160521..cac987c7e 100644 --- a/tonic-types/src/richer_error/std_messages/quota_failure.rs +++ b/tonic-types/src/richer_error/std_messages/quota_failure.rs @@ -1,9 +1,9 @@ use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::pb; -use super::super::{pb, FromAny, IntoAny}; +use super::super::*; /// Used at the `violations` field of the [`QuotaFailure`] struct. Describes a /// single quota violation. diff --git a/tonic-types/src/richer_error/std_messages/request_info.rs b/tonic-types/src/richer_error/std_messages/request_info.rs index bb09f0c5b..f54a564f5 100644 --- a/tonic-types/src/richer_error/std_messages/request_info.rs +++ b/tonic-types/src/richer_error/std_messages/request_info.rs @@ -1,9 +1,9 @@ use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::pb; -use super::super::{pb, FromAny, IntoAny}; +use super::super::*; /// Used to encode/decode the `RequestInfo` standard error message described /// in [error_details.proto]. Contains metadata about the request that diff --git a/tonic-types/src/richer_error/std_messages/resource_info.rs b/tonic-types/src/richer_error/std_messages/resource_info.rs index ccb23e1a7..d166a07ed 100644 --- a/tonic-types/src/richer_error/std_messages/resource_info.rs +++ b/tonic-types/src/richer_error/std_messages/resource_info.rs @@ -1,9 +1,9 @@ use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::pb; -use super::super::{pb, FromAny, IntoAny}; +use super::super::*; /// Used to encode/decode the `ResourceInfo` standard error message described /// in [error_details.proto]. Describes the resource that is being accessed. 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 a26e62076..d01c118dd 100644 --- a/tonic-types/src/richer_error/std_messages/retry_info.rs +++ b/tonic-types/src/richer_error/std_messages/retry_info.rs @@ -3,9 +3,9 @@ use std::time; use prost::{DecodeError, Message}; use prost_types::Any; -use crate::richer_error::FromAnyRef; +use crate::{pb, richer_error::FromAnyRef}; -use super::super::{pb, FromAny, IntoAny}; +use super::super::{FromAny, IntoAny}; /// Used to encode/decode the `RetryInfo` standard error message described in /// [error_details.proto]. Describes when the clients can retry a failed