Skip to content

Commit

Permalink
Add DisplayErrorContext to aws-smithy-types
Browse files Browse the repository at this point in the history
  • Loading branch information
jdisanti committed Oct 21, 2022
1 parent aaf75fb commit 8a099b3
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 144 deletions.
147 changes: 147 additions & 0 deletions rust-runtime/aws-smithy-types/src/error.rs
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 {}
92 changes: 92 additions & 0 deletions rust-runtime/aws-smithy-types/src/error/display.rs
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<_>)
})
)
);
}
}
Loading

0 comments on commit 8a099b3

Please sign in to comment.