Skip to main content

aws_multipart_upload/
error.rs

1//! Working with errors in this crate.
2use std::error::Error as StdError;
3use std::fmt::{self, Display, Formatter};
4
5use aws_sdk::error::{ProvideErrorMetadata, SdkError};
6use aws_sdk::operation::RequestId as _;
7
8use crate::client::UploadId;
9use crate::client::part::CompletedParts;
10use crate::encoder::{EncodeError, EncodeErrorKind};
11use crate::uri::ObjectUri;
12
13/// A specialized `Result` type for errors originating in this crate.
14pub type Result<T, E = Error> = ::std::result::Result<T, E>;
15
16/// The value returned when some operation in this crate fails.
17#[derive(Debug, thiserror::Error)]
18pub struct Error(pub(crate) ErrorRepr);
19
20impl Error {
21    /// Returns the details of the upload that failed if available.
22    pub fn failed_upload(&self) -> Option<&FailedUpload> {
23        if let ErrorRepr::UploadFailed { failed, .. } = &self.0 {
24            return Some(failed);
25        }
26        None
27    }
28
29    /// Returns the category under which this error falls.
30    pub fn kind(&self) -> ErrorKind {
31        match self.0 {
32            ErrorRepr::Sdk { .. } => ErrorKind::Sdk,
33            ErrorRepr::Missing(..) => ErrorKind::Config,
34            ErrorRepr::Encoding(..) => ErrorKind::Encoding,
35            ErrorRepr::State(_) | ErrorRepr::UploadFailed { .. } => {
36                ErrorKind::Upload
37            },
38            ErrorRepr::DynStd(_) => ErrorKind::Unknown,
39            ErrorRepr::Other { kind, .. } => kind,
40        }
41    }
42
43    /// Convert an arbitrary [`std::error::Error`] to this error type.
44    pub fn from_std<E>(e: E) -> Self
45    where
46        E: StdError + Send + Sync + 'static,
47    {
48        let err = Box::new(e);
49        Self(ErrorRepr::DynStd(err))
50    }
51
52    /// Create this error from a category and message.
53    pub fn other(kind: ErrorKind, msg: &'static str) -> Self {
54        Self(ErrorRepr::Other { kind, msg })
55    }
56
57    pub(crate) fn state(msg: &'static str) -> Self {
58        Self(ErrorRepr::State(msg))
59    }
60}
61
62impl Display for Error {
63    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
64        self.0.fmt(f)
65    }
66}
67
68impl From<ErrorRepr> for Error {
69    fn from(value: ErrorRepr) -> Self {
70        Self(value)
71    }
72}
73
74impl<E: EncodeError> From<E> for Error {
75    fn from(value: E) -> Self {
76        ErrorRepr::from(value).into()
77    }
78}
79
80impl<E> From<SdkError<E>> for Error
81where
82    E: ProvideErrorMetadata + StdError + Send + Sync + 'static,
83{
84    fn from(value: SdkError<E>) -> Self {
85        ErrorRepr::from(value).into()
86    }
87}
88
89/// The category of the error.
90#[derive(Debug, Clone, Copy)]
91#[non_exhaustive]
92pub enum ErrorKind {
93    /// There was an error in configuration.
94    Config,
95    /// There was an error encoding an item in a part.
96    Encoding,
97    /// An error was returned by the underlying SDK.
98    Sdk,
99    /// There was an error operating the upload.
100    Upload,
101    /// The origin of the error is not known.
102    Unknown,
103}
104
105impl Display for ErrorKind {
106    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
107        match self {
108            Self::Config => write!(f, "config"),
109            Self::Encoding => write!(f, "encoding"),
110            Self::Sdk => write!(f, "sdk"),
111            Self::Upload => write!(f, "upload"),
112            Self::Unknown => write!(f, "unknown"),
113        }
114    }
115}
116
117/// The data of an upload that failed.
118///
119/// This may be found using [`Error::failed_upload`] on the error returned by
120/// some operation. The data is what would be required to resume a multipart
121/// upload or abort it.
122///
123/// Resuming an upload requires the full `CompletedParts`, but the failure that
124/// led to this value being created may have prevented that from being obtained.
125/// In this case the value cannot be used to resume an upload, only abort it.
126#[derive(Debug, Clone)]
127pub struct FailedUpload {
128    /// The ID of the upload assigned on creation.
129    pub id: UploadId,
130    /// The destination URI of the upload.
131    pub uri: ObjectUri,
132    /// Collection of parts that were successfully uploaded.
133    pub completed: CompletedParts,
134}
135
136impl FailedUpload {
137    pub(crate) fn new(
138        id: &UploadId,
139        uri: &ObjectUri,
140        completed: &CompletedParts,
141    ) -> Self {
142        Self { id: id.clone(), uri: uri.clone(), completed: completed.clone() }
143    }
144}
145
146impl Display for FailedUpload {
147    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
148        write!(
149            f,
150            r#"{{ "id": "{}", "uri": "{}", "completed": {:?} }}"#,
151            &self.id, &self.uri, &self.completed
152        )
153    }
154}
155
156/// Appending upload data to the error if available.
157pub(crate) trait ErrorWithUpload<T> {
158    fn err_with_upl(
159        self,
160        id: &UploadId,
161        uri: &ObjectUri,
162        completed: &CompletedParts,
163    ) -> Result<T>;
164}
165
166impl<T, E> ErrorWithUpload<T> for Result<T, E>
167where
168    E: StdError + Send + Sync + 'static,
169{
170    fn err_with_upl(
171        self,
172        id: &UploadId,
173        uri: &ObjectUri,
174        completed: &CompletedParts,
175    ) -> Result<T> {
176        match self {
177            Ok(t) => Ok(t),
178            Err(e) => {
179                let failed = FailedUpload::new(id, uri, completed);
180                let err =
181                    ErrorRepr::UploadFailed { failed, source: Box::new(e) };
182                Err(err.into())
183            },
184        }
185    }
186}
187
188/// Internal error representation.
189#[derive(Debug, thiserror::Error)]
190pub(crate) enum ErrorRepr {
191    #[error("{0} missing required field: {1}")]
192    Missing(&'static str, &'static str),
193    #[error("{1} error encoding value: {0}")]
194    Encoding(String, EncodeErrorKind),
195    #[error("{failed}: {source}")]
196    UploadFailed {
197        failed: FailedUpload,
198        source: Box<dyn StdError + Send + Sync>,
199    },
200    #[error("corrupted upload state: {0}")]
201    State(&'static str),
202    #[error("request {rid} returned {code}: {msg}")]
203    Sdk { code: String, msg: String, rid: String },
204    #[error("{kind} error: {msg}")]
205    Other { kind: ErrorKind, msg: &'static str },
206    #[error(transparent)]
207    DynStd(Box<dyn StdError + Send + Sync>),
208}
209
210impl<E> From<SdkError<E>> for ErrorRepr
211where
212    E: ProvideErrorMetadata + StdError + Send + Sync + 'static,
213{
214    fn from(value: SdkError<E>) -> Self {
215        let rid = value.request_id().unwrap_or("-1").to_string();
216        let code = value.code().unwrap_or_default().to_string();
217        let msg = value.message().unwrap_or_default().to_string();
218        Self::Sdk { code, msg, rid }
219    }
220}
221
222impl<E: EncodeError> From<E> for ErrorRepr {
223    fn from(value: E) -> Self {
224        ErrorRepr::Encoding(value.message(), value.kind())
225    }
226}