-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
DisplayErrorContext
to aws-smithy-types
- Loading branch information
Showing
3 changed files
with
241 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
//! Generic errors for Smithy codegen | ||
use crate::retry::{ErrorKind, ProvideErrorKind}; | ||
use std::collections::HashMap; | ||
use std::fmt; | ||
use std::fmt::{Display, Formatter}; | ||
|
||
pub mod display; | ||
|
||
/// Generic Error type | ||
/// | ||
/// For many services, Errors are modeled. However, many services only partially model errors or don't | ||
/// model errors at all. In these cases, the SDK will return this generic error type to expose the | ||
/// `code`, `message` and `request_id`. | ||
#[derive(Debug, Eq, PartialEq, Default, Clone)] | ||
pub struct Error { | ||
code: Option<String>, | ||
message: Option<String>, | ||
request_id: Option<String>, | ||
extras: HashMap<&'static str, String>, | ||
} | ||
|
||
/// Builder for [`Error`]. | ||
#[derive(Debug, Default)] | ||
pub struct Builder { | ||
inner: Error, | ||
} | ||
|
||
impl Builder { | ||
/// Sets the error message. | ||
pub fn message(&mut self, message: impl Into<String>) -> &mut Self { | ||
self.inner.message = Some(message.into()); | ||
self | ||
} | ||
|
||
/// Sets the error code. | ||
pub fn code(&mut self, code: impl Into<String>) -> &mut Self { | ||
self.inner.code = Some(code.into()); | ||
self | ||
} | ||
|
||
/// Sets the request ID the error happened for. | ||
pub fn request_id(&mut self, request_id: impl Into<String>) -> &mut Self { | ||
self.inner.request_id = Some(request_id.into()); | ||
self | ||
} | ||
|
||
/// Set a custom field on the error metadata | ||
/// | ||
/// Typically, these will be accessed with an extension trait: | ||
/// ```rust | ||
/// use aws_smithy_types::Error; | ||
/// const HOST_ID: &str = "host_id"; | ||
/// trait S3ErrorExt { | ||
/// fn extended_request_id(&self) -> Option<&str>; | ||
/// } | ||
/// | ||
/// impl S3ErrorExt for Error { | ||
/// fn extended_request_id(&self) -> Option<&str> { | ||
/// self.extra(HOST_ID) | ||
/// } | ||
/// } | ||
/// | ||
/// fn main() { | ||
/// // Extension trait must be brought into scope | ||
/// use S3ErrorExt; | ||
/// let sdk_response: Result<(), Error> = Err(Error::builder().custom(HOST_ID, "x-1234").build()); | ||
/// if let Err(err) = sdk_response { | ||
/// println!("request id: {:?}, extended request id: {:?}", err.request_id(), err.extended_request_id()); | ||
/// } | ||
/// } | ||
/// ``` | ||
pub fn custom(&mut self, key: &'static str, value: impl Into<String>) -> &mut Self { | ||
self.inner.extras.insert(key, value.into()); | ||
self | ||
} | ||
|
||
/// Creates the error. | ||
pub fn build(&mut self) -> Error { | ||
std::mem::take(&mut self.inner) | ||
} | ||
} | ||
|
||
impl Error { | ||
/// Returns the error code. | ||
pub fn code(&self) -> Option<&str> { | ||
self.code.as_deref() | ||
} | ||
/// Returns the error message. | ||
pub fn message(&self) -> Option<&str> { | ||
self.message.as_deref() | ||
} | ||
/// Returns the request ID the error occurred for, if it's available. | ||
pub fn request_id(&self) -> Option<&str> { | ||
self.request_id.as_deref() | ||
} | ||
/// Returns additional information about the error if it's present. | ||
pub fn extra(&self, key: &'static str) -> Option<&str> { | ||
self.extras.get(key).map(|k| k.as_str()) | ||
} | ||
|
||
/// Creates an `Error` builder. | ||
pub fn builder() -> Builder { | ||
Builder::default() | ||
} | ||
|
||
/// Converts an `Error` into a builder. | ||
pub fn into_builder(self) -> Builder { | ||
Builder { inner: self } | ||
} | ||
} | ||
|
||
impl ProvideErrorKind for Error { | ||
fn retryable_error_kind(&self) -> Option<ErrorKind> { | ||
None | ||
} | ||
|
||
fn code(&self) -> Option<&str> { | ||
Error::code(self) | ||
} | ||
} | ||
|
||
impl Display for Error { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
let mut fmt = f.debug_struct("Error"); | ||
if let Some(code) = &self.code { | ||
fmt.field("code", code); | ||
} | ||
if let Some(message) = &self.message { | ||
fmt.field("message", message); | ||
} | ||
if let Some(req_id) = &self.request_id { | ||
fmt.field("request_id", req_id); | ||
} | ||
for (k, v) in &self.extras { | ||
fmt.field(k, &v); | ||
} | ||
fmt.finish() | ||
} | ||
} | ||
|
||
impl std::error::Error for Error {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
//! Error wrapper that displays error context | ||
use std::error::Error; | ||
use std::fmt; | ||
|
||
/// Provides a `Display` impl for an `Error` that outputs the full error context | ||
/// | ||
/// This is useful for emitting errors with `tracing` in cases where the error | ||
/// is not returned back to the customer. | ||
#[derive(Debug)] | ||
pub struct DisplayErrorContext<E: Error>(pub E); | ||
|
||
impl<E: Error> fmt::Display for DisplayErrorContext<E> { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
write_err(f, &self.0)?; | ||
// Also add a debug version of the error at the end | ||
write!(f, " ({:?})", self.0) | ||
} | ||
} | ||
|
||
fn write_err(f: &mut fmt::Formatter<'_>, err: &dyn Error) -> fmt::Result { | ||
write!(f, "{}", err)?; | ||
if let Some(source) = err.source() { | ||
write!(f, ": ")?; | ||
write_err(f, source)?; | ||
} | ||
Ok(()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use std::error::Error; | ||
use std::fmt; | ||
|
||
#[derive(Debug)] | ||
struct TestError { | ||
what: &'static str, | ||
source: Option<Box<dyn Error>>, | ||
} | ||
|
||
impl fmt::Display for TestError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
write!(f, "{}", self.what) | ||
} | ||
} | ||
|
||
impl Error for TestError { | ||
fn source(&self) -> Option<&(dyn Error + 'static)> { | ||
self.source.as_deref() | ||
} | ||
} | ||
|
||
#[test] | ||
fn no_sources() { | ||
assert_eq!( | ||
"test (TestError { what: \"test\", source: None })", | ||
format!( | ||
"{}", | ||
DisplayErrorContext(TestError { | ||
what: "test", | ||
source: None | ||
}) | ||
) | ||
); | ||
} | ||
|
||
#[test] | ||
fn sources() { | ||
assert_eq!( | ||
"foo: bar: baz (TestError { what: \"foo\", source: Some(TestError { what: \"bar\", source: Some(TestError { what: \"baz\", source: None }) }) })", | ||
format!( | ||
"{}", | ||
DisplayErrorContext(TestError { | ||
what: "foo", | ||
source: Some(Box::new(TestError { | ||
what: "bar", | ||
source: Some(Box::new(TestError { | ||
what: "baz", | ||
source: None | ||
})) | ||
}) as Box<_>) | ||
}) | ||
) | ||
); | ||
} | ||
} |
Oops, something went wrong.