Skip to main content

rustack_s3_model/
error.rs

1//! Auto-generated from AWS S3 Smithy model. DO NOT EDIT.
2
3use std::fmt;
4
5/// Well-known S3 error codes.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
7#[non_exhaustive]
8pub enum S3ErrorCode {
9    /// Default error code.
10    #[default]
11    /// AccessDenied error.
12    AccessDenied,
13    /// AccountProblem error.
14    AccountProblem,
15    /// BucketAlreadyExists error.
16    BucketAlreadyExists,
17    /// BucketAlreadyOwnedByYou error.
18    BucketAlreadyOwnedByYou,
19    /// BucketNotEmpty error.
20    BucketNotEmpty,
21    /// EntityTooLarge error.
22    EntityTooLarge,
23    /// EntityTooSmall error.
24    EntityTooSmall,
25    /// InternalError error.
26    InternalError,
27    /// InvalidArgument error.
28    InvalidArgument,
29    /// InvalidBucketName error.
30    InvalidBucketName,
31    /// InvalidBucketState error.
32    InvalidBucketState,
33    /// InvalidDigest error.
34    InvalidDigest,
35    /// InvalidLocationConstraint error.
36    InvalidLocationConstraint,
37    /// InvalidObjectState error.
38    InvalidObjectState,
39    /// InvalidPart error.
40    InvalidPart,
41    /// InvalidPartOrder error.
42    InvalidPartOrder,
43    /// InvalidRange error.
44    InvalidRange,
45    /// InvalidRequest error.
46    InvalidRequest,
47    /// InvalidStorageClass error.
48    InvalidStorageClass,
49    /// KeyTooLongError error.
50    KeyTooLongError,
51    /// MalformedXML error.
52    MalformedXML,
53    /// MetadataTooLarge error.
54    MetadataTooLarge,
55    /// MethodNotAllowed error.
56    MethodNotAllowed,
57    /// MissingContentLength error.
58    MissingContentLength,
59    /// NoSuchBucket error.
60    NoSuchBucket,
61    /// NoSuchBucketPolicy error.
62    NoSuchBucketPolicy,
63    /// NoSuchCORSConfiguration error.
64    NoSuchCORSConfiguration,
65    /// NoSuchKey error.
66    NoSuchKey,
67    /// NoSuchLifecycleConfiguration error.
68    NoSuchLifecycleConfiguration,
69    /// NoSuchUpload error.
70    NoSuchUpload,
71    /// NoSuchVersion error.
72    NoSuchVersion,
73    /// NoSuchTagSet error.
74    NoSuchTagSet,
75    /// NoSuchWebsiteConfiguration error.
76    NoSuchWebsiteConfiguration,
77    /// NotImplemented error.
78    NotImplemented,
79    /// ObjectNotInActiveTierError error.
80    ObjectNotInActiveTierError,
81    /// PreconditionFailed error.
82    PreconditionFailed,
83    /// SignatureDoesNotMatch error.
84    SignatureDoesNotMatch,
85    /// TooManyBuckets error.
86    TooManyBuckets,
87    /// XAmzContentSHA256Mismatch error.
88    XAmzContentSHA256Mismatch,
89    /// BadDigest error.
90    BadDigest,
91    /// ConditionalRequestConflict error.
92    ConditionalRequestConflict,
93    /// MaxMessageLengthExceeded error.
94    MaxMessageLengthExceeded,
95    /// NoSuchObjectLockConfiguration error.
96    NoSuchObjectLockConfiguration,
97    /// NoSuchPublicAccessBlockConfiguration error.
98    NoSuchPublicAccessBlockConfiguration,
99    /// NotModified error (HTTP 304).
100    NotModified,
101    /// OwnershipControlsNotFoundError error.
102    OwnershipControlsNotFoundError,
103    /// ReplicationConfigurationNotFoundError error.
104    ReplicationConfigurationNotFoundError,
105    /// ServerSideEncryptionConfigurationNotFoundError error.
106    ServerSideEncryptionConfigurationNotFoundError,
107    /// A custom error code not in the standard set.
108    Custom(&'static str),
109}
110
111impl S3ErrorCode {
112    /// Returns the error code as a string.
113    #[must_use]
114    pub fn as_str(&self) -> &'static str {
115        match self {
116            Self::AccessDenied => "AccessDenied",
117            Self::AccountProblem => "AccountProblem",
118            Self::BucketAlreadyExists => "BucketAlreadyExists",
119            Self::BucketAlreadyOwnedByYou => "BucketAlreadyOwnedByYou",
120            Self::BucketNotEmpty => "BucketNotEmpty",
121            Self::EntityTooLarge => "EntityTooLarge",
122            Self::EntityTooSmall => "EntityTooSmall",
123            Self::InternalError => "InternalError",
124            Self::InvalidArgument => "InvalidArgument",
125            Self::InvalidBucketName => "InvalidBucketName",
126            Self::InvalidBucketState => "InvalidBucketState",
127            Self::InvalidDigest => "InvalidDigest",
128            Self::InvalidLocationConstraint => "InvalidLocationConstraint",
129            Self::InvalidObjectState => "InvalidObjectState",
130            Self::InvalidPart => "InvalidPart",
131            Self::InvalidPartOrder => "InvalidPartOrder",
132            Self::InvalidRange => "InvalidRange",
133            Self::InvalidRequest => "InvalidRequest",
134            Self::InvalidStorageClass => "InvalidStorageClass",
135            Self::KeyTooLongError => "KeyTooLongError",
136            Self::MalformedXML => "MalformedXML",
137            Self::MetadataTooLarge => "MetadataTooLarge",
138            Self::MethodNotAllowed => "MethodNotAllowed",
139            Self::MissingContentLength => "MissingContentLength",
140            Self::NoSuchBucket => "NoSuchBucket",
141            Self::NoSuchBucketPolicy => "NoSuchBucketPolicy",
142            Self::NoSuchCORSConfiguration => "NoSuchCORSConfiguration",
143            Self::NoSuchKey => "NoSuchKey",
144            Self::NoSuchLifecycleConfiguration => "NoSuchLifecycleConfiguration",
145            Self::NoSuchUpload => "NoSuchUpload",
146            Self::NoSuchVersion => "NoSuchVersion",
147            Self::NoSuchTagSet => "NoSuchTagSet",
148            Self::NoSuchWebsiteConfiguration => "NoSuchWebsiteConfiguration",
149            Self::NotImplemented => "NotImplemented",
150            Self::ObjectNotInActiveTierError => "ObjectNotInActiveTierError",
151            Self::PreconditionFailed => "PreconditionFailed",
152            Self::SignatureDoesNotMatch => "SignatureDoesNotMatch",
153            Self::TooManyBuckets => "TooManyBuckets",
154            Self::XAmzContentSHA256Mismatch => "XAmzContentSHA256Mismatch",
155            Self::BadDigest => "BadDigest",
156            Self::ConditionalRequestConflict => "ConditionalRequestConflict",
157            Self::MaxMessageLengthExceeded => "MaxMessageLengthExceeded",
158            Self::NoSuchObjectLockConfiguration => "NoSuchObjectLockConfiguration",
159            Self::NoSuchPublicAccessBlockConfiguration => "NoSuchPublicAccessBlockConfiguration",
160            Self::NotModified => "NotModified",
161            Self::OwnershipControlsNotFoundError => "OwnershipControlsNotFoundError",
162            Self::ReplicationConfigurationNotFoundError => "ReplicationConfigurationNotFoundError",
163            Self::ServerSideEncryptionConfigurationNotFoundError => {
164                "ServerSideEncryptionConfigurationNotFoundError"
165            }
166            Self::Custom(s) => s,
167        }
168    }
169
170    /// Returns the default HTTP status code for this error.
171    #[must_use]
172    #[allow(clippy::match_same_arms)]
173    pub fn default_status_code(&self) -> http::StatusCode {
174        match self {
175            Self::NotModified => http::StatusCode::NOT_MODIFIED,
176            Self::BadDigest
177            | Self::EntityTooLarge
178            | Self::EntityTooSmall
179            | Self::InvalidArgument
180            | Self::InvalidBucketName
181            | Self::InvalidDigest
182            | Self::InvalidLocationConstraint
183            | Self::InvalidPart
184            | Self::InvalidPartOrder
185            | Self::InvalidRequest
186            | Self::InvalidStorageClass
187            | Self::KeyTooLongError
188            | Self::MalformedXML
189            | Self::MaxMessageLengthExceeded
190            | Self::MetadataTooLarge
191            | Self::ServerSideEncryptionConfigurationNotFoundError
192            | Self::TooManyBuckets
193            | Self::XAmzContentSHA256Mismatch => http::StatusCode::BAD_REQUEST,
194            Self::AccessDenied
195            | Self::AccountProblem
196            | Self::InvalidObjectState
197            | Self::ObjectNotInActiveTierError
198            | Self::SignatureDoesNotMatch => http::StatusCode::FORBIDDEN,
199            Self::NoSuchBucket
200            | Self::NoSuchBucketPolicy
201            | Self::NoSuchCORSConfiguration
202            | Self::NoSuchKey
203            | Self::NoSuchLifecycleConfiguration
204            | Self::NoSuchObjectLockConfiguration
205            | Self::NoSuchPublicAccessBlockConfiguration
206            | Self::NoSuchUpload
207            | Self::NoSuchVersion
208            | Self::NoSuchTagSet
209            | Self::NoSuchWebsiteConfiguration
210            | Self::OwnershipControlsNotFoundError
211            | Self::ReplicationConfigurationNotFoundError => http::StatusCode::NOT_FOUND,
212            Self::MethodNotAllowed => http::StatusCode::METHOD_NOT_ALLOWED,
213            Self::BucketAlreadyExists
214            | Self::BucketAlreadyOwnedByYou
215            | Self::BucketNotEmpty
216            | Self::ConditionalRequestConflict
217            | Self::InvalidBucketState => http::StatusCode::CONFLICT,
218            Self::MissingContentLength => http::StatusCode::LENGTH_REQUIRED,
219            Self::PreconditionFailed => http::StatusCode::PRECONDITION_FAILED,
220            Self::InvalidRange => http::StatusCode::RANGE_NOT_SATISFIABLE,
221            Self::InternalError => http::StatusCode::INTERNAL_SERVER_ERROR,
222            Self::NotImplemented => http::StatusCode::NOT_IMPLEMENTED,
223            Self::Custom(_) => http::StatusCode::INTERNAL_SERVER_ERROR,
224        }
225    }
226
227    /// Returns the default message for this error.
228    #[must_use]
229    pub fn default_message(&self) -> &'static str {
230        match self {
231            Self::AccessDenied => "Access Denied",
232            Self::AccountProblem => "There is a problem with the account",
233            Self::BucketAlreadyExists => "The requested bucket name is not available",
234            Self::BucketAlreadyOwnedByYou => "The bucket is already owned by you",
235            Self::BucketNotEmpty => "The bucket you tried to delete is not empty",
236            Self::EntityTooLarge => "Your proposed upload exceeds the maximum allowed size",
237            Self::EntityTooSmall => "Your proposed upload is smaller than the minimum allowed size",
238            Self::InternalError => "Internal server error",
239            Self::InvalidArgument => "Invalid Argument",
240            Self::InvalidBucketName => "The specified bucket is not valid",
241            Self::InvalidBucketState => {
242                "The request is not valid with the current state of the bucket"
243            }
244            Self::InvalidDigest => "The Content-MD5 you specified is not valid",
245            Self::InvalidLocationConstraint => "The specified location constraint is not valid",
246            Self::InvalidObjectState => {
247                "The operation is not valid for the current state of the object"
248            }
249            Self::InvalidPart => "One or more of the specified parts could not be found",
250            Self::InvalidPartOrder => "The list of parts was not in ascending order",
251            Self::InvalidRange => "The requested range cannot be satisfied",
252            Self::InvalidRequest => "Invalid Request",
253            Self::InvalidStorageClass => "The storage class you specified is not valid",
254            Self::KeyTooLongError => "Your key is too long",
255            Self::MalformedXML => "The XML you provided was not well-formed",
256            Self::MetadataTooLarge => {
257                "Your metadata headers exceed the maximum allowed metadata size"
258            }
259            Self::MethodNotAllowed => "The specified method is not allowed against this resource",
260            Self::MissingContentLength => "You must provide the Content-Length HTTP header",
261            Self::NoSuchBucket => "The specified bucket does not exist",
262            Self::NoSuchBucketPolicy => "The specified bucket does not have a bucket policy",
263            Self::NoSuchCORSConfiguration => "The CORS configuration does not exist",
264            Self::NoSuchKey => "The specified key does not exist",
265            Self::NoSuchLifecycleConfiguration => "The lifecycle configuration does not exist",
266            Self::NoSuchUpload => "The specified multipart upload does not exist",
267            Self::NoSuchVersion => "The specified version does not exist",
268            Self::NoSuchTagSet => "The TagSet does not exist",
269            Self::NoSuchWebsiteConfiguration => "The website configuration does not exist",
270            Self::NotImplemented => "The functionality is not implemented",
271            Self::ObjectNotInActiveTierError => {
272                "The source object of the COPY operation is not in the active tier"
273            }
274            Self::PreconditionFailed => {
275                "At least one of the preconditions you specified did not hold"
276            }
277            Self::SignatureDoesNotMatch => "The request signature does not match",
278            Self::TooManyBuckets => "You have attempted to create more buckets than allowed",
279            Self::XAmzContentSHA256Mismatch => {
280                "The provided x-amz-content-sha256 header does not match"
281            }
282            Self::BadDigest => "The Content-MD5 you specified did not match what we received",
283            Self::ConditionalRequestConflict => "The conditional request cannot be processed",
284            Self::MaxMessageLengthExceeded => "Your request was too big",
285            Self::NoSuchObjectLockConfiguration => {
286                "Object Lock configuration does not exist for this bucket"
287            }
288            Self::NoSuchPublicAccessBlockConfiguration => {
289                "The public access block configuration was not found"
290            }
291            Self::NotModified => "Not Modified",
292            Self::OwnershipControlsNotFoundError => "The bucket ownership controls were not found",
293            Self::ReplicationConfigurationNotFoundError => {
294                "The replication configuration was not found"
295            }
296            Self::ServerSideEncryptionConfigurationNotFoundError => {
297                "The server-side encryption configuration was not found"
298            }
299            Self::Custom(s) => s,
300        }
301    }
302}
303
304impl fmt::Display for S3ErrorCode {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        f.write_str(self.as_str())
307    }
308}
309
310/// An S3 error response.
311#[derive(Debug)]
312pub struct S3Error {
313    /// The error code.
314    pub code: S3ErrorCode,
315    /// A human-readable error message.
316    pub message: String,
317    /// The resource that caused the error.
318    pub resource: Option<String>,
319    /// The request ID.
320    pub request_id: Option<String>,
321    /// The HTTP status code.
322    pub status_code: http::StatusCode,
323    /// The underlying source error, if any.
324    pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
325    /// Extra HTTP headers to include in the error response.
326    ///
327    /// Boxed to minimize the size of `S3Error` on the common (header-free) path.
328    pub headers: Option<Box<Vec<(String, String)>>>,
329}
330
331impl fmt::Display for S3Error {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        write!(f, "S3Error({}): {}", self.code, self.message)
334    }
335}
336
337impl std::error::Error for S3Error {
338    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
339        self.source
340            .as_ref()
341            .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
342    }
343}
344
345impl S3Error {
346    /// Create a new S3Error from an error code.
347    #[must_use]
348    pub fn new(code: S3ErrorCode) -> Self {
349        let status_code = code.default_status_code();
350        let message = code.default_message().to_owned();
351        Self {
352            code,
353            message,
354            resource: None,
355            request_id: None,
356            status_code,
357            source: None,
358            headers: None,
359        }
360    }
361
362    /// Create a new S3Error with a custom message.
363    #[must_use]
364    pub fn with_message(code: S3ErrorCode, message: impl Into<String>) -> Self {
365        Self {
366            status_code: code.default_status_code(),
367            message: message.into(),
368            code,
369            resource: None,
370            request_id: None,
371            source: None,
372            headers: None,
373        }
374    }
375
376    /// Set the resource that caused this error.
377    #[must_use]
378    pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
379        self.resource = Some(resource.into());
380        self
381    }
382
383    /// Set the request ID.
384    #[must_use]
385    pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
386        self.request_id = Some(request_id.into());
387        self
388    }
389
390    /// Set the source error.
391    #[must_use]
392    pub fn with_source(mut self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
393        self.source = Some(Box::new(source));
394        self
395    }
396
397    /// Add an extra HTTP header to the error response.
398    #[must_use]
399    pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
400        self.headers
401            .get_or_insert_with(|| Box::new(Vec::new()))
402            .push((name.into(), value.into()));
403        self
404    }
405
406    /// Create a NoSuchBucket error.
407    #[must_use]
408    pub fn no_such_bucket(bucket_name: impl Into<String>) -> Self {
409        Self::new(S3ErrorCode::NoSuchBucket).with_resource(bucket_name)
410    }
411
412    /// Create a NoSuchKey error.
413    #[must_use]
414    pub fn no_such_key(key: impl Into<String>) -> Self {
415        Self::new(S3ErrorCode::NoSuchKey).with_resource(key)
416    }
417
418    /// Create a NoSuchUpload error.
419    #[must_use]
420    pub fn no_such_upload(upload_id: impl Into<String>) -> Self {
421        Self::new(S3ErrorCode::NoSuchUpload).with_resource(upload_id)
422    }
423
424    /// Create a NoSuchVersion error.
425    #[must_use]
426    pub fn no_such_version(version_id: impl Into<String>) -> Self {
427        Self::new(S3ErrorCode::NoSuchVersion).with_resource(version_id)
428    }
429
430    /// Create a BucketAlreadyExists error.
431    #[must_use]
432    pub fn bucket_already_exists(bucket_name: impl Into<String>) -> Self {
433        Self::new(S3ErrorCode::BucketAlreadyExists).with_resource(bucket_name)
434    }
435
436    /// Create a BucketAlreadyOwnedByYou error.
437    #[must_use]
438    pub fn bucket_already_owned_by_you(bucket_name: impl Into<String>) -> Self {
439        Self::new(S3ErrorCode::BucketAlreadyOwnedByYou).with_resource(bucket_name)
440    }
441
442    /// Create a BucketNotEmpty error.
443    #[must_use]
444    pub fn bucket_not_empty(bucket_name: impl Into<String>) -> Self {
445        Self::new(S3ErrorCode::BucketNotEmpty).with_resource(bucket_name)
446    }
447
448    /// Create a AccessDenied error.
449    #[must_use]
450    pub fn access_denied(resource: impl Into<String>) -> Self {
451        Self::new(S3ErrorCode::AccessDenied).with_resource(resource)
452    }
453
454    /// Create a InternalError error.
455    #[must_use]
456    pub fn internal_error(message: impl Into<String>) -> Self {
457        Self::with_message(S3ErrorCode::InternalError, message)
458    }
459
460    /// Create a InvalidArgument error.
461    #[must_use]
462    pub fn invalid_argument(message: impl Into<String>) -> Self {
463        Self::with_message(S3ErrorCode::InvalidArgument, message)
464    }
465
466    /// Create a InvalidBucketName error.
467    #[must_use]
468    pub fn invalid_bucket_name(bucket_name: impl Into<String>) -> Self {
469        Self::new(S3ErrorCode::InvalidBucketName).with_resource(bucket_name)
470    }
471
472    /// Create a InvalidRange error.
473    #[must_use]
474    pub fn invalid_range(range: impl Into<String>) -> Self {
475        Self::new(S3ErrorCode::InvalidRange).with_resource(range)
476    }
477
478    /// Create a InvalidPart error.
479    #[must_use]
480    pub fn invalid_part(part_info: impl Into<String>) -> Self {
481        Self::new(S3ErrorCode::InvalidPart).with_resource(part_info)
482    }
483
484    /// Create a InvalidPartOrder error.
485    #[must_use]
486    pub fn invalid_part_order(detail: impl Into<String>) -> Self {
487        Self::new(S3ErrorCode::InvalidPartOrder).with_resource(detail)
488    }
489
490    /// Create a MalformedXML error.
491    #[must_use]
492    pub fn malformed_xml(detail: impl Into<String>) -> Self {
493        Self::new(S3ErrorCode::MalformedXML).with_resource(detail)
494    }
495
496    /// Create a MethodNotAllowed error.
497    #[must_use]
498    pub fn method_not_allowed(method: impl Into<String>) -> Self {
499        Self::new(S3ErrorCode::MethodNotAllowed).with_resource(method)
500    }
501
502    /// Create a NotImplemented error.
503    #[must_use]
504    pub fn not_implemented(detail: impl Into<String>) -> Self {
505        Self::new(S3ErrorCode::NotImplemented).with_resource(detail)
506    }
507
508    /// Create a PreconditionFailed error.
509    #[must_use]
510    pub fn precondition_failed(condition: impl Into<String>) -> Self {
511        Self::new(S3ErrorCode::PreconditionFailed).with_resource(condition)
512    }
513
514    /// Create a SignatureDoesNotMatch error.
515    #[must_use]
516    pub fn signature_does_not_match(detail: impl Into<String>) -> Self {
517        Self::new(S3ErrorCode::SignatureDoesNotMatch).with_resource(detail)
518    }
519}
520
521/// Create an S3Error from an error code.
522///
523/// # Examples
524///
525/// ```
526/// use rustack_s3_model::s3_error;
527/// use rustack_s3_model::error::S3ErrorCode;
528///
529/// let err = s3_error!(NoSuchBucket);
530/// assert_eq!(err.code, S3ErrorCode::NoSuchBucket);
531///
532/// let err = s3_error!(NoSuchKey, "The key does not exist");
533/// assert_eq!(err.message, "The key does not exist");
534/// ```
535#[macro_export]
536macro_rules! s3_error {
537    ($code:ident) => {
538        $crate::error::S3Error::new($crate::error::S3ErrorCode::$code)
539    };
540    ($code:ident, $msg:expr) => {
541        $crate::error::S3Error::with_message($crate::error::S3ErrorCode::$code, $msg)
542    };
543}