remi_s3/
error.rs

1// ๐Ÿปโ€โ„๏ธ๐Ÿงถ remi-rs: Asynchronous Rust crate to handle communication between applications and object storage providers
2// Copyright (c) 2022-2025 Noelware, LLC. <team@noelware.org>
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy
5// of this software and associated documentation files (the "Software"), to deal
6// in the Software without restriction, including without limitation the rights
7// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8// copies of the Software, and to permit persons to whom the Software is
9// furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in all
12// copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20// SOFTWARE.
21
22use aws_sdk_s3::{
23    operation::{
24        create_bucket::CreateBucketError, delete_object::DeleteObjectError, get_object::GetObjectError,
25        head_bucket::HeadBucketError, head_object::HeadObjectError, list_buckets::ListBucketsError,
26        list_objects_v2::ListObjectsV2Error, put_object::PutObjectError,
27    },
28    primitives::SdkBody,
29};
30use aws_smithy_runtime_api::{
31    client::result::SdkError,
32    client::result::{ConstructionFailure, DispatchFailure, ResponseError, TimeoutError},
33    http::Response,
34};
35use std::{
36    borrow::Cow,
37    fmt::{Debug, Display},
38};
39
40/// Type alias for [`std::result::Result`]<`T`, [`Error`]>.
41pub type Result<T> = std::result::Result<T, Error>;
42
43pub(crate) fn lib<T: Into<Cow<'static, str>>>(msg: T) -> Error {
44    Error::Library(msg.into())
45}
46
47/// Represents a generalised error that inlines all service errors and uses [`Response`]<[`SdkBody`]>
48/// as the response type.
49#[derive(Debug)]
50#[non_exhaustive]
51pub enum Error {
52    /// Request failed during construction, it was not dispatched over the network.
53    ConstructionFailure(ConstructionFailure),
54
55    /// Request failed due to a timeout, the request MAY have been sent and received.
56    TimeoutError(TimeoutError),
57
58    /// Request failed during dispatch, an HTTP response was not received. The request MAY
59    /// have been set.
60    DispatchFailure(DispatchFailure),
61
62    /// A response was received but it was not parseable according to the protocol. (for example, the
63    /// server hung up without sending a complete response)
64    Response(ResponseError<Response<SdkBody>>),
65
66    /// Amazon S3 was unable to list buckets. This happens when you call `StorageService::init`,
67    /// since the library performs checks whenever if the bucket exists or not and it needs the ability to check.
68    ListBuckets(ListBucketsError),
69
70    /// Amazon S3 was unable to create the bucket for some reason, this will never hit the
71    /// [`CreateBucketError::BucketAlreadyExists`] or [`CreateBucketError::BucketAlreadyOwnedByYou`]
72    /// variants, something might've been unhandled and is probably isn't your fault.
73    ///
74    /// * this would be thrown from the [`StorageService::init`][remi::StorageService::init]
75    ///   trait method
76    CreateBucket(Box<CreateBucketError>),
77
78    /// Amazon S3 was unable to get the object that you were looking for either
79    /// from the [`StorageService::open`][remi::StorageService::open] or the
80    /// [`StorageService::blob`][remi::StorageService::blob] methods.
81    ///
82    /// The [`GetObjectError::NoSuchKey`] variant will never be reached since
83    /// it'll return `Ok(None)` if the key wasn't present in S3, this might
84    /// result in an invalid object state ([`GetObjectError::InvalidObjectState`])
85    /// or an unhandled variant that the Rust SDK doesn't support *yet*.
86    ///
87    /// * this would be thrown from the [`StorageService::open`][remi::StorageService::open]
88    ///   or the [`StorageService::blob`][remi::StorageService::blob] trait methods.
89    GetObject(Box<GetObjectError>),
90
91    /// Amazon S3 was unable to list objects from the specific requirements that
92    /// it was told to list objects from a [`ListBlobsRequest`][remi::ListBlobsRequest].
93    ///
94    /// This might be in a unhandled state as [`ListObjectsV2Error::NoSuchBucket`] should never
95    /// be matched since `remi-s3` handles creating buckets if they don't exist when
96    /// [`StorageService::init`][remi::StorageService::init] is called.
97    ///
98    /// * this would be thrown from the [`StorageService::open`][remi::StorageService::open]
99    ///   or the [`StorageService::blob`][remi::StorageService::blob] trait methods.
100    ListObjectsV2(ListObjectsV2Error),
101
102    /// Amazon S3 was unable to delete an object from the service.
103    ///
104    /// * this would be thrown from the [`StorageService::delete`][remi::StorageService::delete] trait method.
105    DeleteObject(DeleteObjectError),
106
107    /// Amazon S3 was unable to check the existence of an object. This will never
108    /// reach the [`HeadObjectError::NotFound`] state as it'll return `Ok(false)`.
109    ///
110    /// * this would be thrown from the [`StorageService::exists`][remi::StorageService::exists] trait method.
111    HeadObject(HeadObjectError),
112
113    /// Amazon S3 was unable to put an object into the service.
114    ///
115    /// * this would be thrown from the [`StorageService::upload`][remi::StorageService::upload] trait method.
116    PutObject(Box<PutObjectError>),
117
118    /// Occurs when an error occurred when transforming AWS S3's responses.
119    ByteStream(aws_sdk_s3::primitives::ByteStreamError),
120
121    /// Occurs when `remi-s3` cannot perform a HEAD request to the current bucket. This is mainly
122    /// used in healthchecks to determine if the storage service is ok.
123    HeadBucket(HeadBucketError),
124
125    /// Something that `remi-s3` has emitted on its own.
126    Library(Cow<'static, str>),
127}
128
129impl Display for Error {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        use Error as E;
132
133        match self {
134            E::ByteStream(err) => Display::fmt(err, f),
135            E::Response(_) => f.write_str("response was received but it was not parseable according to the protocol."),
136            E::TimeoutError(_) => {
137                f.write_str("request failed due to a timeout, the request MAY have been sent and received.")
138            }
139
140            E::ConstructionFailure(_) => {
141                f.write_str("request failed during construction, it was not dispatched over the network.")
142            }
143
144            E::DispatchFailure(_) => f.write_str(
145                "request failed during dispatch, an HTTP response was not received. the request MAY have been set.",
146            ),
147
148            E::CreateBucket(err) => Display::fmt(err, f),
149            E::DeleteObject(err) => Display::fmt(err, f),
150            E::GetObject(err) => Display::fmt(err, f),
151            E::HeadObject(err) => Display::fmt(err, f),
152            E::ListBuckets(err) => Display::fmt(err, f),
153            E::ListObjectsV2(err) => Display::fmt(err, f),
154            E::PutObject(err) => Display::fmt(err, f),
155            E::HeadBucket(err) => Display::fmt(err, f),
156            E::Library(msg) => f.write_str(msg),
157        }
158    }
159}
160
161impl std::error::Error for Error {}
162
163impl From<SdkError<ListBucketsError, Response<SdkBody>>> for Error {
164    fn from(error: SdkError<ListBucketsError, Response<SdkBody>>) -> Self {
165        match error {
166            SdkError::ConstructionFailure(err) => Self::ConstructionFailure(err),
167            SdkError::DispatchFailure(err) => Self::DispatchFailure(err),
168            SdkError::TimeoutError(err) => Self::TimeoutError(err),
169            SdkError::ResponseError(err) => Self::Response(err),
170            err => Error::ListBuckets(err.into_service_error()),
171        }
172    }
173}
174
175impl From<SdkError<CreateBucketError, Response<SdkBody>>> for Error {
176    fn from(error: SdkError<CreateBucketError, Response<SdkBody>>) -> Self {
177        match error {
178            SdkError::ConstructionFailure(err) => Self::ConstructionFailure(err),
179            SdkError::DispatchFailure(err) => Self::DispatchFailure(err),
180            SdkError::TimeoutError(err) => Self::TimeoutError(err),
181            SdkError::ResponseError(err) => Self::Response(err),
182            err => Error::CreateBucket(Box::new(err.into_service_error())),
183        }
184    }
185}
186
187impl From<SdkError<GetObjectError, Response<SdkBody>>> for Error {
188    fn from(error: SdkError<GetObjectError, Response<SdkBody>>) -> Self {
189        match error {
190            SdkError::ConstructionFailure(err) => Self::ConstructionFailure(err),
191            SdkError::DispatchFailure(err) => Self::DispatchFailure(err),
192            SdkError::TimeoutError(err) => Self::TimeoutError(err),
193            SdkError::ResponseError(err) => Self::Response(err),
194            err => Error::GetObject(Box::new(err.into_service_error())),
195        }
196    }
197}
198
199impl From<GetObjectError> for Error {
200    fn from(value: GetObjectError) -> Self {
201        Self::GetObject(Box::new(value))
202    }
203}
204
205impl From<SdkError<ListObjectsV2Error, Response<SdkBody>>> for Error {
206    fn from(error: SdkError<ListObjectsV2Error, Response<SdkBody>>) -> Self {
207        match error {
208            SdkError::ConstructionFailure(err) => Self::ConstructionFailure(err),
209            SdkError::DispatchFailure(err) => Self::DispatchFailure(err),
210            SdkError::TimeoutError(err) => Self::TimeoutError(err),
211            SdkError::ResponseError(err) => Self::Response(err),
212            err => Error::ListObjectsV2(err.into_service_error()),
213        }
214    }
215}
216
217impl From<SdkError<DeleteObjectError, Response<SdkBody>>> for Error {
218    fn from(error: SdkError<DeleteObjectError, Response<SdkBody>>) -> Self {
219        match error {
220            SdkError::ConstructionFailure(err) => Self::ConstructionFailure(err),
221            SdkError::DispatchFailure(err) => Self::DispatchFailure(err),
222            SdkError::TimeoutError(err) => Self::TimeoutError(err),
223            SdkError::ResponseError(err) => Self::Response(err),
224            err => Error::DeleteObject(err.into_service_error()),
225        }
226    }
227}
228
229impl From<SdkError<HeadObjectError, Response<SdkBody>>> for Error {
230    fn from(error: SdkError<HeadObjectError, Response<SdkBody>>) -> Self {
231        match error {
232            SdkError::ConstructionFailure(err) => Self::ConstructionFailure(err),
233            SdkError::DispatchFailure(err) => Self::DispatchFailure(err),
234            SdkError::TimeoutError(err) => Self::TimeoutError(err),
235            SdkError::ResponseError(err) => Self::Response(err),
236            err => Error::HeadObject(err.into_service_error()),
237        }
238    }
239}
240
241impl From<HeadObjectError> for Error {
242    fn from(value: HeadObjectError) -> Self {
243        Self::HeadObject(value)
244    }
245}
246
247impl From<SdkError<PutObjectError, Response<SdkBody>>> for Error {
248    fn from(error: SdkError<PutObjectError, Response<SdkBody>>) -> Self {
249        match error {
250            SdkError::ConstructionFailure(err) => Self::ConstructionFailure(err),
251            SdkError::DispatchFailure(err) => Self::DispatchFailure(err),
252            SdkError::TimeoutError(err) => Self::TimeoutError(err),
253            SdkError::ResponseError(err) => Self::Response(err),
254            err => Error::PutObject(Box::new(err.into_service_error())),
255        }
256    }
257}
258
259impl From<SdkError<HeadBucketError, Response<SdkBody>>> for Error {
260    fn from(value: SdkError<HeadBucketError, Response<SdkBody>>) -> Self {
261        match value {
262            SdkError::ConstructionFailure(err) => Self::ConstructionFailure(err),
263            SdkError::DispatchFailure(err) => Self::DispatchFailure(err),
264            SdkError::TimeoutError(err) => Self::TimeoutError(err),
265            SdkError::ResponseError(err) => Self::Response(err),
266            err => Error::HeadBucket(err.into_service_error()),
267        }
268    }
269}
270
271impl From<aws_sdk_s3::primitives::ByteStreamError> for Error {
272    fn from(value: aws_sdk_s3::primitives::ByteStreamError) -> Self {
273        Self::ByteStream(value)
274    }
275}