1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

//! Error metadata

use crate::retry::{ErrorKind, ProvideErrorKind};
use std::collections::HashMap;
use std::fmt;

/// Trait to retrieve error metadata from a result
pub trait ProvideErrorMetadata {
    /// Returns error metadata, which includes the error code, message,
    /// request ID, and potentially additional information.
    fn meta(&self) -> &ErrorMetadata;

    /// Returns the error code if it's available.
    fn code(&self) -> Option<&str> {
        self.meta().code()
    }

    /// Returns the error message, if there is one.
    fn message(&self) -> Option<&str> {
        self.meta().message()
    }
}

/// Empty error metadata
#[doc(hidden)]
pub const EMPTY_ERROR_METADATA: ErrorMetadata = ErrorMetadata {
    code: None,
    message: None,
    extras: None,
};

/// 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 ErrorMetadata {
    code: Option<String>,
    message: Option<String>,
    extras: Option<HashMap<&'static str, String>>,
}

impl ProvideErrorMetadata for ErrorMetadata {
    fn meta(&self) -> &ErrorMetadata {
        self
    }
}

/// Builder for [`ErrorMetadata`].
#[derive(Debug, Default)]
pub struct Builder {
    inner: ErrorMetadata,
}

impl Builder {
    /// Sets the error message.
    pub fn message(mut self, message: impl Into<String>) -> Self {
        self.inner.message = Some(message.into());
        self
    }

    /// Sets the error code.
    pub fn code(mut self, code: impl Into<String>) -> Self {
        self.inner.code = Some(code.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!("extended request id: {:?}", err.extended_request_id());
    ///     }
    /// }
    /// ```
    pub fn custom(mut self, key: &'static str, value: impl Into<String>) -> Self {
        if self.inner.extras.is_none() {
            self.inner.extras = Some(HashMap::new());
        }
        self.inner
            .extras
            .as_mut()
            .unwrap()
            .insert(key, value.into());
        self
    }

    /// Creates the error.
    pub fn build(self) -> ErrorMetadata {
        self.inner
    }
}

impl ErrorMetadata {
    /// 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 additional information about the error if it's present.
    pub fn extra(&self, key: &'static str) -> Option<&str> {
        self.extras
            .as_ref()
            .and_then(|extras| 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 ErrorMetadata {
    fn retryable_error_kind(&self) -> Option<ErrorKind> {
        None
    }

    fn code(&self) -> Option<&str> {
        ErrorMetadata::code(self)
    }
}

impl fmt::Display for ErrorMetadata {
    fn fmt(&self, f: &mut fmt::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(extras) = &self.extras {
            for (k, v) in extras {
                fmt.field(k, &v);
            }
        }
        fmt.finish()
    }
}

impl std::error::Error for ErrorMetadata {}