aws_sdk_s3_transfer_manager/
error.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6use std::fmt;
7
8/// A boxed error that is `Send` and `Sync`.
9pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
10
11use aws_sdk_s3::error::ProvideErrorMetadata;
12
13/// Errors returned by this library
14///
15/// NOTE: Use [`aws_smithy_types::error::display::DisplayErrorContext`] or similar to display
16/// the entire error cause/source chain.
17#[derive(Debug)]
18pub struct Error {
19    kind: ErrorKind,
20    source: BoxError,
21}
22
23/// General categories of transfer errors.
24#[derive(Clone, Debug, Eq, PartialEq)]
25#[non_exhaustive]
26pub enum ErrorKind {
27    /// Operation input validation issues
28    InputInvalid,
29
30    /// I/O errors
31    IOError,
32
33    /// Some kind of internal runtime issue (e.g. task failure, poisoned mutex, etc)
34    RuntimeError,
35
36    /// Object discovery failed
37    ObjectNotDiscoverable,
38
39    /// Failed to upload or download a chunk of an object
40    ChunkFailed(ChunkFailed),
41
42    /// Resource not found (e.g. bucket, key, multipart upload ID not found)
43    NotFound,
44
45    /// child operation failed (e.g. download of a single object as part of downloading all objects from a bucket)
46    ChildOperationFailed,
47
48    /// The operation is being canceled because the user explicitly called `.abort` on the handle,
49    /// or a child operation failed with the abort policy.
50    OperationCancelled,
51}
52
53/// Stores information about failed chunk
54#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct ChunkFailed {
56    /// The ID of the chunk that failed.
57    /// For downloads, this is the sequence number of the chunk,
58    /// and for uploads, it is the upload ID.
59    id: ChunkId,
60}
61
62#[derive(Clone, Debug, Eq, PartialEq)]
63pub(crate) enum ChunkId {
64    Download(u64),
65    Upload(String),
66}
67
68impl ChunkFailed {
69    // The sequence number of the chunk for download operation
70    pub(crate) fn download_seq(&self) -> Option<u64> {
71        match self.id {
72            ChunkId::Download(seq) => Some(seq),
73            _ => None,
74        }
75    }
76
77    #[allow(dead_code)]
78    // The upload ID of the chunk for upload operation
79    pub(crate) fn upload_id(&self) -> Option<&str> {
80        match &self.id {
81            ChunkId::Upload(id) => Some(id),
82            _ => None,
83        }
84    }
85}
86
87impl Error {
88    /// Creates a new transfer [`Error`] from a known kind of error as well as an arbitrary error
89    /// source.
90    pub fn new<E>(kind: ErrorKind, err: E) -> Error
91    where
92        E: Into<BoxError>,
93    {
94        Error {
95            kind,
96            source: err.into(),
97        }
98    }
99
100    /// Returns the corresponding [`ErrorKind`] for this error.
101    pub fn kind(&self) -> &ErrorKind {
102        &self.kind
103    }
104}
105
106impl fmt::Display for Error {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match &self.kind {
109            ErrorKind::InputInvalid => write!(f, "invalid input"),
110            ErrorKind::IOError => write!(f, "I/O error"),
111            ErrorKind::RuntimeError => write!(f, "runtime error"),
112            ErrorKind::ObjectNotDiscoverable => write!(f, "object discovery failed"),
113            ErrorKind::ChunkFailed(chunk_failed) => {
114                write!(f, "failed to process chunk {:?}", chunk_failed.id)
115            }
116            ErrorKind::NotFound => write!(f, "resource not found"),
117            ErrorKind::ChildOperationFailed => write!(f, "child operation failed"),
118            ErrorKind::OperationCancelled => write!(f, "operation cancelled"),
119        }
120    }
121}
122
123impl std::error::Error for Error {
124    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
125        Some(self.source.as_ref())
126    }
127}
128
129impl From<crate::io::error::Error> for Error {
130    fn from(value: crate::io::error::Error) -> Self {
131        Self::new(ErrorKind::IOError, value)
132    }
133}
134
135impl From<std::io::Error> for Error {
136    fn from(value: std::io::Error) -> Self {
137        Self::new(ErrorKind::IOError, value)
138    }
139}
140
141impl From<tokio::task::JoinError> for Error {
142    fn from(value: tokio::task::JoinError) -> Self {
143        Self::new(ErrorKind::RuntimeError, value)
144    }
145}
146
147impl<T> From<std::sync::PoisonError<T>> for Error
148where
149    T: Send + Sync + 'static,
150{
151    fn from(value: std::sync::PoisonError<T>) -> Self {
152        Self::new(ErrorKind::RuntimeError, value)
153    }
154}
155
156impl From<aws_smithy_types::error::operation::BuildError> for Error {
157    fn from(value: aws_smithy_types::error::operation::BuildError) -> Self {
158        Self::new(ErrorKind::InputInvalid, value)
159    }
160}
161
162pub(crate) fn invalid_input<E>(err: E) -> Error
163where
164    E: Into<BoxError>,
165{
166    Error::new(ErrorKind::InputInvalid, err)
167}
168
169pub(crate) fn discovery_failed<E>(err: E) -> Error
170where
171    E: Into<BoxError>,
172{
173    Error::new(ErrorKind::ObjectNotDiscoverable, err)
174}
175
176pub(crate) fn chunk_failed<E>(id: ChunkId, err: E) -> Error
177where
178    E: Into<BoxError>,
179{
180    Error::new(ErrorKind::ChunkFailed(ChunkFailed { id }), err)
181}
182
183pub(crate) fn from_kind<E>(kind: ErrorKind) -> impl FnOnce(E) -> Error
184where
185    E: Into<BoxError>,
186{
187    |err| Error::new(kind, err)
188}
189
190impl<E, R> From<aws_sdk_s3::error::SdkError<E, R>> for Error
191where
192    E: std::error::Error + ProvideErrorMetadata + Send + Sync + 'static,
193    R: Send + Sync + fmt::Debug + 'static,
194{
195    fn from(value: aws_sdk_s3::error::SdkError<E, R>) -> Self {
196        // TODO - extract request id/metadata
197        let kind = match value.code() {
198            Some("NotFound" | "NoSuchKey" | "NoSuchUpload" | "NoSuchBucket") => ErrorKind::NotFound,
199            // TODO - is this the rigth error kind? do we need something else?
200            _ => ErrorKind::ChildOperationFailed,
201        };
202
203        Error::new(kind, value)
204    }
205}
206
207static CANCELLATION_ERROR: &str =
208    "at least one operation has been aborted, cancelling all ongoing requests";
209
210pub(crate) fn operation_cancelled() -> Error {
211    Error::new(ErrorKind::OperationCancelled, CANCELLATION_ERROR)
212}