aws_smithy_runtime_api/client/
result.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Types for [error](SdkError) responses.
7
8use crate::client::connection::ConnectionMetadata;
9use aws_smithy_types::error::metadata::{ProvideErrorMetadata, EMPTY_ERROR_METADATA};
10use aws_smithy_types::error::operation::BuildError;
11use aws_smithy_types::error::ErrorMetadata;
12use aws_smithy_types::retry::ErrorKind;
13use std::error::Error;
14use std::fmt;
15use std::fmt::{Debug, Display, Formatter};
16
17type BoxError = Box<dyn Error + Send + Sync>;
18
19/// Builders for `SdkError` variant context.
20pub mod builders {
21    use super::*;
22
23    macro_rules! source_only_error_builder {
24        ($errorName:ident, $builderName:ident, $sourceType:ident) => {
25            #[doc = concat!("Builder for [`", stringify!($errorName), "`](super::", stringify!($errorName), ").")]
26            #[derive(Debug, Default)]
27            pub struct $builderName {
28                source: Option<$sourceType>,
29            }
30
31            impl $builderName {
32                #[doc = "Creates a new builder."]
33                pub fn new() -> Self { Default::default() }
34
35                #[doc = "Sets the error source."]
36                pub fn source(mut self, source: impl Into<$sourceType>) -> Self {
37                    self.source = Some(source.into());
38                    self
39                }
40
41                #[doc = "Sets the error source."]
42                pub fn set_source(&mut self, source: Option<$sourceType>) -> &mut Self {
43                    self.source = source;
44                    self
45                }
46
47                #[doc = "Builds the error context."]
48                pub fn build(self) -> $errorName {
49                    $errorName { source: self.source.expect("source is required") }
50                }
51            }
52        };
53    }
54
55    source_only_error_builder!(ConstructionFailure, ConstructionFailureBuilder, BoxError);
56    source_only_error_builder!(TimeoutError, TimeoutErrorBuilder, BoxError);
57    source_only_error_builder!(DispatchFailure, DispatchFailureBuilder, ConnectorError);
58
59    /// Builder for [`ResponseError`].
60    #[derive(Debug)]
61    pub struct ResponseErrorBuilder<R> {
62        source: Option<BoxError>,
63        raw: Option<R>,
64    }
65
66    impl<R> Default for ResponseErrorBuilder<R> {
67        fn default() -> Self {
68            Self {
69                source: None,
70                raw: None,
71            }
72        }
73    }
74
75    impl<R> ResponseErrorBuilder<R> {
76        /// Creates a new builder.
77        pub fn new() -> Self {
78            Default::default()
79        }
80
81        /// Sets the error source.
82        pub fn source(mut self, source: impl Into<BoxError>) -> Self {
83            self.source = Some(source.into());
84            self
85        }
86
87        /// Sets the error source.
88        pub fn set_source(&mut self, source: Option<BoxError>) -> &mut Self {
89            self.source = source;
90            self
91        }
92
93        /// Sets the raw response.
94        pub fn raw(mut self, raw: R) -> Self {
95            self.raw = Some(raw);
96            self
97        }
98
99        /// Sets the raw response.
100        pub fn set_raw(&mut self, raw: Option<R>) -> &mut Self {
101            self.raw = raw;
102            self
103        }
104
105        /// Builds the error context.
106        pub fn build(self) -> ResponseError<R> {
107            ResponseError {
108                source: self.source.expect("source is required"),
109                raw: self.raw.expect("a raw response is required"),
110            }
111        }
112    }
113
114    /// Builder for [`ServiceError`].
115    #[derive(Debug)]
116    pub struct ServiceErrorBuilder<E, R> {
117        source: Option<E>,
118        raw: Option<R>,
119    }
120
121    impl<E, R> Default for ServiceErrorBuilder<E, R> {
122        fn default() -> Self {
123            Self {
124                source: None,
125                raw: None,
126            }
127        }
128    }
129
130    impl<E, R> ServiceErrorBuilder<E, R> {
131        /// Creates a new builder.
132        pub fn new() -> Self {
133            Default::default()
134        }
135
136        /// Sets the error source.
137        pub fn source(mut self, source: impl Into<E>) -> Self {
138            self.source = Some(source.into());
139            self
140        }
141
142        /// Sets the error source.
143        pub fn set_source(&mut self, source: Option<E>) -> &mut Self {
144            self.source = source;
145            self
146        }
147
148        /// Sets the raw response.
149        pub fn raw(mut self, raw: R) -> Self {
150            self.raw = Some(raw);
151            self
152        }
153
154        /// Sets the raw response.
155        pub fn set_raw(&mut self, raw: Option<R>) -> &mut Self {
156            self.raw = raw;
157            self
158        }
159
160        /// Builds the error context.
161        pub fn build(self) -> ServiceError<E, R> {
162            ServiceError {
163                source: self.source.expect("source is required"),
164                raw: self.raw.expect("a raw response is required"),
165            }
166        }
167    }
168}
169
170/// Error context for [`SdkError::ConstructionFailure`]
171#[derive(Debug)]
172pub struct ConstructionFailure {
173    pub(crate) source: BoxError,
174}
175
176impl ConstructionFailure {
177    /// Creates a builder for this error context type.
178    pub fn builder() -> builders::ConstructionFailureBuilder {
179        builders::ConstructionFailureBuilder::new()
180    }
181}
182
183/// Error context for [`SdkError::TimeoutError`]
184#[derive(Debug)]
185pub struct TimeoutError {
186    source: BoxError,
187}
188
189impl TimeoutError {
190    /// Creates a builder for this error context type.
191    pub fn builder() -> builders::TimeoutErrorBuilder {
192        builders::TimeoutErrorBuilder::new()
193    }
194}
195
196/// Error context for [`SdkError::DispatchFailure`]
197#[derive(Debug)]
198pub struct DispatchFailure {
199    source: ConnectorError,
200}
201
202impl DispatchFailure {
203    /// Creates a builder for this error context type.
204    pub fn builder() -> builders::DispatchFailureBuilder {
205        builders::DispatchFailureBuilder::new()
206    }
207
208    /// Returns true if the error is an IO error
209    pub fn is_io(&self) -> bool {
210        self.source.is_io()
211    }
212
213    /// Returns true if the error is an timeout error
214    pub fn is_timeout(&self) -> bool {
215        self.source.is_timeout()
216    }
217
218    /// Returns true if the error is a user-caused error (e.g., invalid HTTP request)
219    pub fn is_user(&self) -> bool {
220        self.source.is_user()
221    }
222
223    /// Returns true if the error is an unclassified error.
224    pub fn is_other(&self) -> bool {
225        self.source.is_other()
226    }
227
228    /// Returns the optional error kind associated with an unclassified error
229    pub fn as_other(&self) -> Option<ErrorKind> {
230        self.source.as_other()
231    }
232
233    /// Returns the inner error if it is a connector error
234    pub fn as_connector_error(&self) -> Option<&ConnectorError> {
235        Some(&self.source)
236    }
237}
238
239/// Error context for [`SdkError::ResponseError`]
240#[derive(Debug)]
241pub struct ResponseError<R> {
242    /// Error encountered while parsing the response
243    source: BoxError,
244    /// Raw response that was available
245    raw: R,
246}
247
248impl<R> ResponseError<R> {
249    /// Creates a builder for this error context type.
250    pub fn builder() -> builders::ResponseErrorBuilder<R> {
251        builders::ResponseErrorBuilder::new()
252    }
253
254    /// Returns a reference to the raw response
255    pub fn raw(&self) -> &R {
256        &self.raw
257    }
258
259    /// Converts this error context into the raw response
260    pub fn into_raw(self) -> R {
261        self.raw
262    }
263}
264
265/// Error context for [`SdkError::ServiceError`]
266#[derive(Debug)]
267pub struct ServiceError<E, R> {
268    /// Modeled service error
269    source: E,
270    /// Raw response from the service
271    raw: R,
272}
273
274impl<E, R> ServiceError<E, R> {
275    /// Creates a builder for this error context type.
276    pub fn builder() -> builders::ServiceErrorBuilder<E, R> {
277        builders::ServiceErrorBuilder::new()
278    }
279
280    /// Returns the underlying error of type `E`
281    pub fn err(&self) -> &E {
282        &self.source
283    }
284
285    /// Converts this error context into the underlying error `E`
286    pub fn into_err(self) -> E {
287        self.source
288    }
289
290    /// Returns a reference to the raw response
291    pub fn raw(&self) -> &R {
292        &self.raw
293    }
294
295    /// Converts this error context into the raw response
296    pub fn into_raw(self) -> R {
297        self.raw
298    }
299}
300
301/// Constructs the unhandled variant of a code generated error.
302///
303/// This trait exists so that [`SdkError::into_service_error`] can be infallible.
304pub trait CreateUnhandledError {
305    /// Creates an unhandled error variant with the given `source` and error metadata.
306    fn create_unhandled_error(
307        source: Box<dyn Error + Send + Sync + 'static>,
308        meta: Option<ErrorMetadata>,
309    ) -> Self;
310}
311
312/// Failed SDK Result
313///
314/// When logging an error from the SDK, it is recommended that you either wrap the error in
315/// [`DisplayErrorContext`](aws_smithy_types::error::display::DisplayErrorContext), use another
316/// error reporter library that visits the error's cause/source chain, or call
317/// [`Error::source`] for more details about the underlying cause.
318#[non_exhaustive]
319#[derive(Debug)]
320pub enum SdkError<E, R> {
321    /// The request failed during construction. It was not dispatched over the network.
322    ConstructionFailure(ConstructionFailure),
323
324    /// The request failed due to a timeout. The request MAY have been sent and received.
325    TimeoutError(TimeoutError),
326
327    /// The request failed during dispatch. An HTTP response was not received. The request MAY
328    /// have been sent.
329    DispatchFailure(DispatchFailure),
330
331    /// A response was received but it was not parseable according the the protocol (for example
332    /// the server hung up without sending a complete response)
333    ResponseError(ResponseError<R>),
334
335    /// An error response was received from the service
336    ServiceError(ServiceError<E, R>),
337}
338
339impl<E, R> SdkError<E, R> {
340    /// Construct a `SdkError` for a construction failure
341    pub fn construction_failure(source: impl Into<BoxError>) -> Self {
342        Self::ConstructionFailure(ConstructionFailure {
343            source: source.into(),
344        })
345    }
346
347    /// Construct a `SdkError` for a timeout
348    pub fn timeout_error(source: impl Into<BoxError>) -> Self {
349        Self::TimeoutError(TimeoutError {
350            source: source.into(),
351        })
352    }
353
354    /// Construct a `SdkError` for a dispatch failure with a [`ConnectorError`]
355    pub fn dispatch_failure(source: ConnectorError) -> Self {
356        Self::DispatchFailure(DispatchFailure { source })
357    }
358
359    /// Construct a `SdkError` for a response error
360    pub fn response_error(source: impl Into<BoxError>, raw: R) -> Self {
361        Self::ResponseError(ResponseError {
362            source: source.into(),
363            raw,
364        })
365    }
366
367    /// Construct a `SdkError` for a service failure
368    pub fn service_error(source: E, raw: R) -> Self {
369        Self::ServiceError(ServiceError { source, raw })
370    }
371
372    /// Returns the underlying service error `E` if there is one
373    ///
374    /// If the `SdkError` is not a `ServiceError` (for example, the error is a network timeout),
375    /// then it will be converted into an unhandled variant of `E`. This makes it easy to match
376    /// on the service's error response while simultaneously bubbling up transient failures.
377    /// For example, handling the `NoSuchKey` error for S3's `GetObject` operation may look as
378    /// follows:
379    ///
380    /// ```no_run
381    /// # use aws_smithy_runtime_api::client::result::{SdkError, CreateUnhandledError};
382    /// # #[derive(Debug)] enum GetObjectError { NoSuchKey(()), Other(()) }
383    /// # impl std::fmt::Display for GetObjectError {
384    /// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { unimplemented!() }
385    /// # }
386    /// # impl std::error::Error for GetObjectError {}
387    /// # impl CreateUnhandledError for GetObjectError {
388    /// #     fn create_unhandled_error(
389    /// #         _: Box<dyn std::error::Error + Send + Sync + 'static>,
390    /// #         _: Option<aws_smithy_types::error::ErrorMetadata>,
391    /// #     ) -> Self { unimplemented!() }
392    /// # }
393    /// # fn example() -> Result<(), GetObjectError> {
394    /// # let sdk_err = SdkError::service_error(GetObjectError::NoSuchKey(()), ());
395    /// match sdk_err.into_service_error() {
396    ///     GetObjectError::NoSuchKey(_) => {
397    ///         // handle NoSuchKey
398    ///     }
399    ///     err @ _ => return Err(err),
400    /// }
401    /// # Ok(())
402    /// # }
403    /// ```
404    pub fn into_service_error(self) -> E
405    where
406        E: std::error::Error + Send + Sync + CreateUnhandledError + 'static,
407        R: Debug + Send + Sync + 'static,
408    {
409        match self {
410            Self::ServiceError(context) => context.source,
411            _ => E::create_unhandled_error(self.into(), None),
412        }
413    }
414
415    /// Returns a reference underlying service error `E` if there is one
416    ///
417    /// # Examples
418    /// ```no_run
419    /// # use aws_smithy_runtime_api::client::result::SdkError;
420    /// # #[derive(Debug)] enum GetObjectError { NoSuchKey(()), Other(()) }
421    /// # impl std::fmt::Display for GetObjectError {
422    /// #     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { unimplemented!() }
423    /// # }
424    /// # impl std::error::Error for GetObjectError {}
425    /// # impl GetObjectError {
426    /// #   fn is_not_found(&self) -> bool { true }
427    /// # }
428    /// # fn example() -> Result<(), GetObjectError> {
429    /// # let sdk_err = SdkError::service_error(GetObjectError::NoSuchKey(()), ());
430    /// if sdk_err.as_service_error().map(|e|e.is_not_found()) == Some(true) {
431    ///     println!("the object doesn't exist");
432    ///     // return None, or handle this error specifically
433    /// }
434    /// // ... handle other error cases, happy path, etc.
435    /// # Ok(())
436    /// # }
437    /// ```
438    pub fn as_service_error(&self) -> Option<&E> {
439        match self {
440            Self::ServiceError(err) => Some(&err.source),
441            _ => None,
442        }
443    }
444
445    /// Converts this error into its error source.
446    ///
447    /// If there is no error source, then `Err(Self)` is returned.
448    pub fn into_source(self) -> Result<Box<dyn Error + Send + Sync + 'static>, Self>
449    where
450        E: std::error::Error + Send + Sync + 'static,
451    {
452        match self {
453            SdkError::ConstructionFailure(context) => Ok(context.source),
454            SdkError::TimeoutError(context) => Ok(context.source),
455            SdkError::ResponseError(context) => Ok(context.source),
456            SdkError::DispatchFailure(context) => Ok(context.source.into()),
457            SdkError::ServiceError(context) => Ok(context.source.into()),
458        }
459    }
460
461    /// Return a reference to this error's raw response, if it contains one. Otherwise, return `None`.
462    pub fn raw_response(&self) -> Option<&R> {
463        match self {
464            SdkError::ServiceError(inner) => Some(inner.raw()),
465            SdkError::ResponseError(inner) => Some(inner.raw()),
466            _ => None,
467        }
468    }
469
470    /// Maps the service error type in `SdkError::ServiceError`
471    pub fn map_service_error<E2>(self, map: impl FnOnce(E) -> E2) -> SdkError<E2, R> {
472        match self {
473            SdkError::ServiceError(context) => SdkError::<E2, R>::ServiceError(ServiceError {
474                source: map(context.source),
475                raw: context.raw,
476            }),
477            SdkError::ConstructionFailure(context) => {
478                SdkError::<E2, R>::ConstructionFailure(context)
479            }
480            SdkError::DispatchFailure(context) => SdkError::<E2, R>::DispatchFailure(context),
481            SdkError::ResponseError(context) => SdkError::<E2, R>::ResponseError(context),
482            SdkError::TimeoutError(context) => SdkError::<E2, R>::TimeoutError(context),
483        }
484    }
485}
486
487impl<E, R> Display for SdkError<E, R> {
488    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
489        match self {
490            SdkError::ConstructionFailure(_) => write!(f, "failed to construct request"),
491            SdkError::TimeoutError(_) => write!(f, "request has timed out"),
492            SdkError::DispatchFailure(_) => write!(f, "dispatch failure"),
493            SdkError::ResponseError(_) => write!(f, "response error"),
494            SdkError::ServiceError(_) => write!(f, "service error"),
495        }
496    }
497}
498
499impl<E, R> Error for SdkError<E, R>
500where
501    E: Error + 'static,
502    R: Debug,
503{
504    fn source(&self) -> Option<&(dyn Error + 'static)> {
505        match self {
506            SdkError::ConstructionFailure(context) => Some(context.source.as_ref()),
507            SdkError::TimeoutError(context) => Some(context.source.as_ref()),
508            SdkError::ResponseError(context) => Some(context.source.as_ref()),
509            SdkError::DispatchFailure(context) => Some(&context.source),
510            SdkError::ServiceError(context) => Some(&context.source),
511        }
512    }
513}
514
515impl<E, R> From<BuildError> for SdkError<E, R> {
516    fn from(value: BuildError) -> Self {
517        SdkError::ConstructionFailure(ConstructionFailure::builder().source(value).build())
518    }
519}
520
521impl<E, R> ProvideErrorMetadata for SdkError<E, R>
522where
523    E: ProvideErrorMetadata,
524{
525    fn meta(&self) -> &aws_smithy_types::error::ErrorMetadata {
526        match self {
527            SdkError::ConstructionFailure(_) => &EMPTY_ERROR_METADATA,
528            SdkError::TimeoutError(_) => &EMPTY_ERROR_METADATA,
529            SdkError::DispatchFailure(_) => &EMPTY_ERROR_METADATA,
530            SdkError::ResponseError(_) => &EMPTY_ERROR_METADATA,
531            SdkError::ServiceError(err) => err.source.meta(),
532        }
533    }
534}
535
536#[derive(Debug)]
537enum ConnectorErrorKind {
538    /// A timeout occurred while processing the request
539    Timeout,
540
541    /// A user-caused error (e.g., invalid HTTP request)
542    User,
543
544    /// Socket/IO error
545    Io,
546
547    /// An unclassified Error with an explicit error kind
548    Other(Option<ErrorKind>),
549}
550
551/// Error from the underlying Connector
552///
553/// Connector exists to attach a `ConnectorErrorKind` to what would otherwise be an opaque `Box<dyn Error>`
554/// that comes off a potentially generic or dynamic connector.
555/// The attached `kind` is used to determine what retry behavior should occur (if any) based on the
556/// connector error.
557#[derive(Debug)]
558pub struct ConnectorError {
559    kind: ConnectorErrorKind,
560    source: BoxError,
561    connection: ConnectionStatus,
562}
563
564#[non_exhaustive]
565#[derive(Debug)]
566pub(crate) enum ConnectionStatus {
567    /// This request was never connected to the remote
568    ///
569    /// This indicates the failure was during connection establishment
570    NeverConnected,
571
572    /// It is unknown whether a connection was established
573    Unknown,
574
575    /// The request connected to the remote prior to failure
576    Connected(ConnectionMetadata),
577}
578
579impl Display for ConnectorError {
580    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
581        match self.kind {
582            ConnectorErrorKind::Timeout => write!(f, "timeout"),
583            ConnectorErrorKind::User => write!(f, "user error"),
584            ConnectorErrorKind::Io => write!(f, "io error"),
585            ConnectorErrorKind::Other(_) => write!(f, "other"),
586        }
587    }
588}
589
590impl Error for ConnectorError {
591    fn source(&self) -> Option<&(dyn Error + 'static)> {
592        Some(self.source.as_ref())
593    }
594}
595
596impl ConnectorError {
597    /// Construct a [`ConnectorError`] from an error caused by a timeout
598    ///
599    /// Timeout errors are typically retried on a new connection.
600    pub fn timeout(source: BoxError) -> Self {
601        Self {
602            kind: ConnectorErrorKind::Timeout,
603            source,
604            connection: ConnectionStatus::Unknown,
605        }
606    }
607
608    /// Include connection information along with this error
609    pub fn with_connection(mut self, info: ConnectionMetadata) -> Self {
610        self.connection = ConnectionStatus::Connected(info);
611        self
612    }
613
614    /// Set the connection status on this error to report that a connection was never established
615    pub fn never_connected(mut self) -> Self {
616        self.connection = ConnectionStatus::NeverConnected;
617        self
618    }
619
620    /// Construct a [`ConnectorError`] from an error caused by the user (e.g. invalid HTTP request)
621    pub fn user(source: BoxError) -> Self {
622        Self {
623            kind: ConnectorErrorKind::User,
624            source,
625            connection: ConnectionStatus::Unknown,
626        }
627    }
628
629    /// Construct a [`ConnectorError`] from an IO related error (e.g. socket hangup)
630    pub fn io(source: BoxError) -> Self {
631        Self {
632            kind: ConnectorErrorKind::Io,
633            source,
634            connection: ConnectionStatus::Unknown,
635        }
636    }
637
638    /// Construct a [`ConnectorError`] from an different unclassified error.
639    ///
640    /// Optionally, an explicit `Kind` may be passed.
641    pub fn other(source: BoxError, kind: Option<ErrorKind>) -> Self {
642        Self {
643            source,
644            kind: ConnectorErrorKind::Other(kind),
645            connection: ConnectionStatus::Unknown,
646        }
647    }
648
649    /// Returns true if the error is an IO error
650    pub fn is_io(&self) -> bool {
651        matches!(self.kind, ConnectorErrorKind::Io)
652    }
653
654    /// Returns true if the error is an timeout error
655    pub fn is_timeout(&self) -> bool {
656        matches!(self.kind, ConnectorErrorKind::Timeout)
657    }
658
659    /// Returns true if the error is a user-caused error (e.g., invalid HTTP request)
660    pub fn is_user(&self) -> bool {
661        matches!(self.kind, ConnectorErrorKind::User)
662    }
663
664    /// Returns true if the error is an unclassified error.
665    pub fn is_other(&self) -> bool {
666        matches!(self.kind, ConnectorErrorKind::Other(..))
667    }
668
669    /// Returns the optional error kind associated with an unclassified error
670    pub fn as_other(&self) -> Option<ErrorKind> {
671        match &self.kind {
672            ConnectorErrorKind::Other(ek) => *ek,
673            _ => None,
674        }
675    }
676
677    /// Grants ownership of this error's source.
678    pub fn into_source(self) -> BoxError {
679        self.source
680    }
681
682    /// Returns metadata about the connection
683    ///
684    /// If a connection was established and provided by the internal connector, a connection will
685    /// be returned.
686    pub fn connection_metadata(&self) -> Option<&ConnectionMetadata> {
687        match &self.connection {
688            ConnectionStatus::NeverConnected => None,
689            ConnectionStatus::Unknown => None,
690            ConnectionStatus::Connected(conn) => Some(conn),
691        }
692    }
693}