Skip to content

Commit

Permalink
types: add support for ErrorInfo error message type (#1269)
Browse files Browse the repository at this point in the history
Following implementation at flemosr/tonic-richer-error.
  • Loading branch information
flemosr authored Feb 15, 2023
1 parent 5acde56 commit 555a8bc
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 8 deletions.
4 changes: 2 additions & 2 deletions tonic-types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ pub use pb::Status;
mod richer_error;

pub use richer_error::{
BadRequest, DebugInfo, ErrorDetail, ErrorDetails, FieldViolation, QuotaFailure, QuotaViolation,
RetryInfo, StatusExt,
BadRequest, DebugInfo, ErrorDetail, ErrorDetails, ErrorInfo, FieldViolation, QuotaFailure,
QuotaViolation, RetryInfo, StatusExt,
};

mod sealed {
Expand Down
63 changes: 61 additions & 2 deletions tonic-types/src/richer_error/error_details/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::time;
use std::{collections::HashMap, time};

use super::std_messages::{
BadRequest, DebugInfo, FieldViolation, QuotaFailure, QuotaViolation, RetryInfo,
BadRequest, DebugInfo, ErrorInfo, FieldViolation, QuotaFailure, QuotaViolation, RetryInfo,
};

pub(crate) mod vec;
Expand All @@ -22,6 +22,9 @@ pub struct ErrorDetails {
/// This field stores [`QuotaFailure`] data, if any.
pub(crate) quota_failure: Option<QuotaFailure>,

/// This field stores [`ErrorInfo`] data, if any.
pub(crate) error_info: Option<ErrorInfo>,

/// This field stores [`BadRequest`] data, if any.
pub(crate) bad_request: Option<BadRequest>,
}
Expand Down Expand Up @@ -117,6 +120,31 @@ impl ErrorDetails {
}
}

/// Generates an [`ErrorDetails`] struct with [`ErrorInfo`] details and
/// remaining fields set to `None`.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
/// use tonic_types::{ErrorDetails};
///
/// let mut metadata: HashMap<String, String> = HashMap::new();
/// metadata.insert("instanceLimitPerRequest".into(), "100".into());
///
/// let err_details = ErrorDetails::with_error_info("reason", "domain", metadata);
/// ```
pub fn with_error_info(
reason: impl Into<String>,
domain: impl Into<String>,
metadata: impl Into<HashMap<String, String>>,
) -> Self {
ErrorDetails {
error_info: Some(ErrorInfo::new(reason, domain, metadata)),
..ErrorDetails::new()
}
}

/// Generates an [`ErrorDetails`] struct with [`BadRequest`] details and
/// remaining fields set to `None`.
///
Expand Down Expand Up @@ -175,6 +203,11 @@ impl ErrorDetails {
self.quota_failure.clone()
}

/// Get [`ErrorInfo`] details, if any
pub fn error_info(&self) -> Option<ErrorInfo> {
self.error_info.clone()
}

/// Get [`BadRequest`] details, if any
pub fn bad_request(&self) -> Option<BadRequest> {
self.bad_request.clone()
Expand Down Expand Up @@ -293,6 +326,32 @@ impl ErrorDetails {
false
}

/// Set [`ErrorInfo`] details. Can be chained with other `.set_` and
/// `.add_` [`ErrorDetails`] methods.
///
/// # Examples
///
/// ```
/// use std::collections::HashMap;
/// use tonic_types::{ErrorDetails};
///
/// let mut err_details = ErrorDetails::new();
///
/// let mut metadata: HashMap<String, String> = HashMap::new();
/// metadata.insert("instanceLimitPerRequest".into(), "100".into());
///
/// err_details.set_error_info("reason", "example.local", metadata);
/// ```
pub fn set_error_info(
&mut self,
reason: impl Into<String>,
domain: impl Into<String>,
metadata: impl Into<HashMap<String, String>>,
) -> &mut Self {
self.error_info = Some(ErrorInfo::new(reason, domain, metadata));
self
}

/// Set [`BadRequest`] details. Can be chained with other `.set_` and
/// `.add_` [`ErrorDetails`] methods.
///
Expand Down
11 changes: 10 additions & 1 deletion tonic-types/src/richer_error/error_details/vec.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::super::std_messages::{BadRequest, DebugInfo, QuotaFailure, RetryInfo};
use super::super::std_messages::{BadRequest, DebugInfo, ErrorInfo, QuotaFailure, RetryInfo};

/// Wraps the structs corresponding to the standard error messages, allowing
/// the implementation and handling of vectors containing any of them.
Expand All @@ -14,6 +14,9 @@ pub enum ErrorDetail {
/// Wraps the [`QuotaFailure`] struct.
QuotaFailure(QuotaFailure),

/// Wraps the [`ErrorInfo`] struct.
ErrorInfo(ErrorInfo),

/// Wraps the [`BadRequest`] struct.
BadRequest(BadRequest),
}
Expand All @@ -36,6 +39,12 @@ impl From<QuotaFailure> for ErrorDetail {
}
}

impl From<ErrorInfo> for ErrorDetail {
fn from(err_detail: ErrorInfo) -> Self {
ErrorDetail::ErrorInfo(err_detail)
}
}

impl From<BadRequest> for ErrorDetail {
fn from(err_detail: BadRequest) -> Self {
ErrorDetail::BadRequest(err_detail)
Expand Down
62 changes: 59 additions & 3 deletions tonic-types/src/richer_error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use super::pb;

pub use error_details::{vec::ErrorDetail, ErrorDetails};
pub use std_messages::{
BadRequest, DebugInfo, FieldViolation, QuotaFailure, QuotaViolation, RetryInfo,
BadRequest, DebugInfo, ErrorInfo, FieldViolation, QuotaFailure, QuotaViolation, RetryInfo,
};

trait IntoAny {
Expand Down Expand Up @@ -315,6 +315,28 @@ pub trait StatusExt: crate::sealed::Sealed {
/// ```
fn get_details_quota_failure(&self) -> Option<QuotaFailure>;

/// 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<T>(req_result: Result<Response<T>, 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<ErrorInfo>;

/// Get first [`BadRequest`] details found on `tonic::Status`, if any. If
/// some `prost::DecodeError` occurs, returns `None`.
///
Expand Down Expand Up @@ -363,6 +385,10 @@ impl StatusExt for tonic::Status {
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(bad_request) = details.bad_request {
conv_details.push(bad_request.into_any());
}
Expand Down Expand Up @@ -397,6 +423,9 @@ impl StatusExt for tonic::Status {
ErrorDetail::QuotaFailure(quota_failure) => {
conv_details.push(quota_failure.into_any());
}
ErrorDetail::ErrorInfo(error_info) => {
conv_details.push(error_info.into_any());
}
ErrorDetail::BadRequest(bad_req) => {
conv_details.push(bad_req.into_any());
}
Expand Down Expand Up @@ -437,6 +466,9 @@ impl StatusExt for tonic::Status {
QuotaFailure::TYPE_URL => {
details.quota_failure = Some(QuotaFailure::from_any(any)?);
}
ErrorInfo::TYPE_URL => {
details.error_info = Some(ErrorInfo::from_any(any)?);
}
BadRequest::TYPE_URL => {
details.bad_request = Some(BadRequest::from_any(any)?);
}
Expand Down Expand Up @@ -467,6 +499,9 @@ impl StatusExt for tonic::Status {
QuotaFailure::TYPE_URL => {
details.push(QuotaFailure::from_any(any)?.into());
}
ErrorInfo::TYPE_URL => {
details.push(ErrorInfo::from_any(any)?.into());
}
BadRequest::TYPE_URL => {
details.push(BadRequest::from_any(any)?.into());
}
Expand Down Expand Up @@ -523,6 +558,20 @@ impl StatusExt for tonic::Status {
None
}

fn get_details_error_info(&self) -> Option<ErrorInfo> {
let status = pb::Status::decode(self.details()).ok()?;

for any in status.details.into_iter() {
if any.type_url.as_str() == ErrorInfo::TYPE_URL {
if let Ok(detail) = ErrorInfo::from_any(any) {
return Some(detail);
}
}
}

None
}

fn get_details_bad_request(&self) -> Option<BadRequest> {
let status = pb::Status::decode(self.details()).ok()?;

Expand All @@ -540,13 +589,18 @@ impl StatusExt for tonic::Status {

#[cfg(test)]
mod tests {
use std::time::Duration;
use std::{collections::HashMap, time::Duration};
use tonic::{Code, Status};

use super::{BadRequest, DebugInfo, ErrorDetails, QuotaFailure, RetryInfo, StatusExt};
use super::{
BadRequest, DebugInfo, ErrorDetails, ErrorInfo, QuotaFailure, 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
Expand All @@ -556,6 +610,7 @@ mod tests {
"details",
)
.add_quota_failure_violation("clientip:<ip address>", "description")
.set_error_info("SOME_INFO", "example.local", metadata.clone())
.add_bad_request_violation("field", "description");

let fmt_details = format!("{:?}", err_details);
Expand All @@ -568,6 +623,7 @@ mod tests {
)
.into(),
QuotaFailure::with_violation("clientip:<ip address>", "description").into(),
ErrorInfo::new("SOME_INFO", "example.local", metadata).into(),
BadRequest::with_violation("field", "description").into(),
];

Expand Down
Loading

0 comments on commit 555a8bc

Please sign in to comment.