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