aws_smithy_types/error/
metadata.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Error metadata
7
8use crate::retry::{ErrorKind, ProvideErrorKind};
9use std::collections::HashMap;
10use std::fmt;
11
12/// Trait to retrieve error metadata from a result
13pub trait ProvideErrorMetadata {
14    /// Returns error metadata, which includes the error code, message,
15    /// request ID, and potentially additional information.
16    fn meta(&self) -> &ErrorMetadata;
17
18    /// Returns the error code if it's available.
19    fn code(&self) -> Option<&str> {
20        self.meta().code()
21    }
22
23    /// Returns the error message, if there is one.
24    fn message(&self) -> Option<&str> {
25        self.meta().message()
26    }
27}
28
29/// Empty error metadata
30pub const EMPTY_ERROR_METADATA: ErrorMetadata = ErrorMetadata {
31    code: None,
32    message: None,
33    extras: None,
34};
35
36/// Generic Error type
37///
38/// For many services, Errors are modeled. However, many services only partially model errors or don't
39/// model errors at all. In these cases, the SDK will return this generic error type to expose the
40/// `code`, `message` and `request_id`.
41#[derive(Debug, Eq, PartialEq, Default, Clone)]
42pub struct ErrorMetadata {
43    code: Option<String>,
44    message: Option<String>,
45    extras: Option<HashMap<&'static str, String>>,
46}
47
48impl ProvideErrorMetadata for ErrorMetadata {
49    fn meta(&self) -> &ErrorMetadata {
50        self
51    }
52}
53
54/// Builder for [`ErrorMetadata`].
55#[derive(Debug, Default)]
56pub struct Builder {
57    inner: ErrorMetadata,
58}
59
60impl Builder {
61    /// Sets the error message.
62    pub fn message(mut self, message: impl Into<String>) -> Self {
63        self.inner.message = Some(message.into());
64        self
65    }
66
67    /// Sets the error code.
68    pub fn code(mut self, code: impl Into<String>) -> Self {
69        self.inner.code = Some(code.into());
70        self
71    }
72
73    /// Set a custom field on the error metadata
74    ///
75    /// Typically, these will be accessed with an extension trait:
76    /// ```rust
77    /// use aws_smithy_types::error::ErrorMetadata;
78    /// const HOST_ID: &str = "host_id";
79    /// trait S3ErrorExt {
80    ///     fn extended_request_id(&self) -> Option<&str>;
81    /// }
82    ///
83    /// impl S3ErrorExt for ErrorMetadata {
84    ///     fn extended_request_id(&self) -> Option<&str> {
85    ///         self.extra(HOST_ID)
86    ///     }
87    /// }
88    ///
89    /// fn main() {
90    ///     // Extension trait must be brought into scope
91    ///     use S3ErrorExt;
92    ///     let sdk_response: Result<(), ErrorMetadata> = Err(ErrorMetadata::builder().custom(HOST_ID, "x-1234").build());
93    ///     if let Err(err) = sdk_response {
94    ///         println!("extended request id: {:?}", err.extended_request_id());
95    ///     }
96    /// }
97    /// ```
98    pub fn custom(mut self, key: &'static str, value: impl Into<String>) -> Self {
99        if self.inner.extras.is_none() {
100            self.inner.extras = Some(HashMap::new());
101        }
102        self.inner
103            .extras
104            .as_mut()
105            .unwrap()
106            .insert(key, value.into());
107        self
108    }
109
110    /// Creates the error.
111    pub fn build(self) -> ErrorMetadata {
112        self.inner
113    }
114}
115
116impl ErrorMetadata {
117    /// Returns the error code.
118    pub fn code(&self) -> Option<&str> {
119        self.code.as_deref()
120    }
121    /// Returns the error message.
122    pub fn message(&self) -> Option<&str> {
123        self.message.as_deref()
124    }
125    /// Returns additional information about the error if it's present.
126    pub fn extra(&self, key: &'static str) -> Option<&str> {
127        self.extras
128            .as_ref()
129            .and_then(|extras| extras.get(key).map(|k| k.as_str()))
130    }
131
132    /// Creates an `Error` builder.
133    pub fn builder() -> Builder {
134        Builder::default()
135    }
136
137    /// Converts an `Error` into a builder.
138    pub fn into_builder(self) -> Builder {
139        Builder { inner: self }
140    }
141}
142
143impl ProvideErrorKind for ErrorMetadata {
144    fn retryable_error_kind(&self) -> Option<ErrorKind> {
145        None
146    }
147
148    fn code(&self) -> Option<&str> {
149        ErrorMetadata::code(self)
150    }
151}
152
153impl fmt::Display for ErrorMetadata {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        let mut fmt = f.debug_struct("Error");
156        if let Some(code) = &self.code {
157            fmt.field("code", code);
158        }
159        if let Some(message) = &self.message {
160            fmt.field("message", message);
161        }
162        if let Some(extras) = &self.extras {
163            for (k, v) in extras {
164                fmt.field(k, &v);
165            }
166        }
167        fmt.finish()
168    }
169}
170
171impl std::error::Error for ErrorMetadata {}