use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum S3ErrorCode {
#[default]
AccessDenied,
AccountProblem,
BucketAlreadyExists,
BucketAlreadyOwnedByYou,
BucketNotEmpty,
EntityTooLarge,
EntityTooSmall,
InternalError,
InvalidArgument,
InvalidBucketName,
InvalidBucketState,
InvalidDigest,
InvalidLocationConstraint,
InvalidObjectState,
InvalidPart,
InvalidPartOrder,
InvalidRange,
InvalidRequest,
InvalidStorageClass,
KeyTooLongError,
MalformedXML,
MetadataTooLarge,
MethodNotAllowed,
MissingContentLength,
NoSuchBucket,
NoSuchBucketPolicy,
NoSuchCORSConfiguration,
NoSuchKey,
NoSuchLifecycleConfiguration,
NoSuchUpload,
NoSuchVersion,
NoSuchTagSet,
NoSuchWebsiteConfiguration,
NotImplemented,
ObjectNotInActiveTierError,
ObjectLockConfigurationNotFoundError,
PreconditionFailed,
SignatureDoesNotMatch,
TooManyBuckets,
XAmzContentSHA256Mismatch,
BadDigest,
ConditionalRequestConflict,
MaxMessageLengthExceeded,
NoSuchObjectLockConfiguration,
NoSuchPublicAccessBlockConfiguration,
NotModified,
OwnershipControlsNotFoundError,
ReplicationConfigurationNotFoundError,
ServerSideEncryptionConfigurationNotFoundError,
Custom(&'static str),
}
impl S3ErrorCode {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::AccessDenied => "AccessDenied",
Self::AccountProblem => "AccountProblem",
Self::BucketAlreadyExists => "BucketAlreadyExists",
Self::BucketAlreadyOwnedByYou => "BucketAlreadyOwnedByYou",
Self::BucketNotEmpty => "BucketNotEmpty",
Self::EntityTooLarge => "EntityTooLarge",
Self::EntityTooSmall => "EntityTooSmall",
Self::InternalError => "InternalError",
Self::InvalidArgument => "InvalidArgument",
Self::InvalidBucketName => "InvalidBucketName",
Self::InvalidBucketState => "InvalidBucketState",
Self::InvalidDigest => "InvalidDigest",
Self::InvalidLocationConstraint => "InvalidLocationConstraint",
Self::InvalidObjectState => "InvalidObjectState",
Self::InvalidPart => "InvalidPart",
Self::InvalidPartOrder => "InvalidPartOrder",
Self::InvalidRange => "InvalidRange",
Self::InvalidRequest => "InvalidRequest",
Self::InvalidStorageClass => "InvalidStorageClass",
Self::KeyTooLongError => "KeyTooLongError",
Self::MalformedXML => "MalformedXML",
Self::MetadataTooLarge => "MetadataTooLarge",
Self::MethodNotAllowed => "MethodNotAllowed",
Self::MissingContentLength => "MissingContentLength",
Self::NoSuchBucket => "NoSuchBucket",
Self::NoSuchBucketPolicy => "NoSuchBucketPolicy",
Self::NoSuchCORSConfiguration => "NoSuchCORSConfiguration",
Self::NoSuchKey => "NoSuchKey",
Self::NoSuchLifecycleConfiguration => "NoSuchLifecycleConfiguration",
Self::NoSuchUpload => "NoSuchUpload",
Self::NoSuchVersion => "NoSuchVersion",
Self::NoSuchTagSet => "NoSuchTagSet",
Self::NoSuchWebsiteConfiguration => "NoSuchWebsiteConfiguration",
Self::NotImplemented => "NotImplemented",
Self::ObjectNotInActiveTierError => "ObjectNotInActiveTierError",
Self::ObjectLockConfigurationNotFoundError => "ObjectLockConfigurationNotFoundError",
Self::PreconditionFailed => "PreconditionFailed",
Self::SignatureDoesNotMatch => "SignatureDoesNotMatch",
Self::TooManyBuckets => "TooManyBuckets",
Self::XAmzContentSHA256Mismatch => "XAmzContentSHA256Mismatch",
Self::BadDigest => "BadDigest",
Self::ConditionalRequestConflict => "ConditionalRequestConflict",
Self::MaxMessageLengthExceeded => "MaxMessageLengthExceeded",
Self::NoSuchObjectLockConfiguration => "NoSuchObjectLockConfiguration",
Self::NoSuchPublicAccessBlockConfiguration => "NoSuchPublicAccessBlockConfiguration",
Self::NotModified => "NotModified",
Self::OwnershipControlsNotFoundError => "OwnershipControlsNotFoundError",
Self::ReplicationConfigurationNotFoundError => "ReplicationConfigurationNotFoundError",
Self::ServerSideEncryptionConfigurationNotFoundError => {
"ServerSideEncryptionConfigurationNotFoundError"
}
Self::Custom(s) => s,
}
}
#[must_use]
#[allow(clippy::match_same_arms)]
pub fn default_status_code(&self) -> http::StatusCode {
match self {
Self::NotModified => http::StatusCode::NOT_MODIFIED,
Self::BadDigest
| Self::EntityTooLarge
| Self::EntityTooSmall
| Self::InvalidArgument
| Self::InvalidBucketName
| Self::InvalidDigest
| Self::InvalidLocationConstraint
| Self::InvalidPart
| Self::InvalidPartOrder
| Self::InvalidRequest
| Self::InvalidStorageClass
| Self::KeyTooLongError
| Self::MalformedXML
| Self::MaxMessageLengthExceeded
| Self::MetadataTooLarge
| Self::ServerSideEncryptionConfigurationNotFoundError
| Self::TooManyBuckets
| Self::XAmzContentSHA256Mismatch => http::StatusCode::BAD_REQUEST,
Self::AccessDenied
| Self::AccountProblem
| Self::InvalidObjectState
| Self::ObjectNotInActiveTierError
| Self::SignatureDoesNotMatch => http::StatusCode::FORBIDDEN,
Self::NoSuchBucket
| Self::NoSuchBucketPolicy
| Self::NoSuchCORSConfiguration
| Self::NoSuchKey
| Self::NoSuchLifecycleConfiguration
| Self::ObjectLockConfigurationNotFoundError
| Self::NoSuchObjectLockConfiguration
| Self::NoSuchPublicAccessBlockConfiguration
| Self::NoSuchUpload
| Self::NoSuchVersion
| Self::NoSuchTagSet
| Self::NoSuchWebsiteConfiguration
| Self::OwnershipControlsNotFoundError
| Self::ReplicationConfigurationNotFoundError => http::StatusCode::NOT_FOUND,
Self::MethodNotAllowed => http::StatusCode::METHOD_NOT_ALLOWED,
Self::BucketAlreadyExists
| Self::BucketAlreadyOwnedByYou
| Self::BucketNotEmpty
| Self::ConditionalRequestConflict
| Self::InvalidBucketState => http::StatusCode::CONFLICT,
Self::MissingContentLength => http::StatusCode::LENGTH_REQUIRED,
Self::PreconditionFailed => http::StatusCode::PRECONDITION_FAILED,
Self::InvalidRange => http::StatusCode::RANGE_NOT_SATISFIABLE,
Self::InternalError => http::StatusCode::INTERNAL_SERVER_ERROR,
Self::NotImplemented => http::StatusCode::NOT_IMPLEMENTED,
Self::Custom(_) => http::StatusCode::INTERNAL_SERVER_ERROR,
}
}
#[must_use]
pub fn default_message(&self) -> &'static str {
match self {
Self::AccessDenied => "Access Denied",
Self::AccountProblem => "There is a problem with the account",
Self::BucketAlreadyExists => "The requested bucket name is not available",
Self::BucketAlreadyOwnedByYou => "The bucket is already owned by you",
Self::BucketNotEmpty => "The bucket you tried to delete is not empty",
Self::EntityTooLarge => "Your proposed upload exceeds the maximum allowed size",
Self::EntityTooSmall => "Your proposed upload is smaller than the minimum allowed size",
Self::InternalError => "Internal server error",
Self::InvalidArgument => "Invalid Argument",
Self::InvalidBucketName => "The specified bucket is not valid",
Self::InvalidBucketState => {
"The request is not valid with the current state of the bucket"
}
Self::InvalidDigest => "The Content-MD5 you specified is not valid",
Self::InvalidLocationConstraint => "The specified location constraint is not valid",
Self::InvalidObjectState => {
"The operation is not valid for the current state of the object"
}
Self::InvalidPart => "One or more of the specified parts could not be found",
Self::InvalidPartOrder => "The list of parts was not in ascending order",
Self::InvalidRange => "The requested range cannot be satisfied",
Self::InvalidRequest => "Invalid Request",
Self::InvalidStorageClass => "The storage class you specified is not valid",
Self::KeyTooLongError => "Your key is too long",
Self::MalformedXML => "The XML you provided was not well-formed",
Self::MetadataTooLarge => {
"Your metadata headers exceed the maximum allowed metadata size"
}
Self::MethodNotAllowed => "The specified method is not allowed against this resource",
Self::MissingContentLength => "You must provide the Content-Length HTTP header",
Self::NoSuchBucket => "The specified bucket does not exist",
Self::NoSuchBucketPolicy => "The specified bucket does not have a bucket policy",
Self::NoSuchCORSConfiguration => "The CORS configuration does not exist",
Self::NoSuchKey => "The specified key does not exist",
Self::NoSuchLifecycleConfiguration => "The lifecycle configuration does not exist",
Self::NoSuchUpload => "The specified multipart upload does not exist",
Self::NoSuchVersion => "The specified version does not exist",
Self::NoSuchTagSet => "The TagSet does not exist",
Self::NoSuchWebsiteConfiguration => "The website configuration does not exist",
Self::NotImplemented => "The functionality is not implemented",
Self::ObjectNotInActiveTierError => {
"The source object of the COPY operation is not in the active tier"
}
Self::ObjectLockConfigurationNotFoundError | Self::NoSuchObjectLockConfiguration => {
"Object Lock configuration does not exist for this bucket"
}
Self::PreconditionFailed => {
"At least one of the preconditions you specified did not hold"
}
Self::SignatureDoesNotMatch => "The request signature does not match",
Self::TooManyBuckets => "You have attempted to create more buckets than allowed",
Self::XAmzContentSHA256Mismatch => {
"The provided x-amz-content-sha256 header does not match"
}
Self::BadDigest => "The Content-MD5 you specified did not match what we received",
Self::ConditionalRequestConflict => "The conditional request cannot be processed",
Self::MaxMessageLengthExceeded => "Your request was too big",
Self::NoSuchPublicAccessBlockConfiguration => {
"The public access block configuration was not found"
}
Self::NotModified => "Not Modified",
Self::OwnershipControlsNotFoundError => "The bucket ownership controls were not found",
Self::ReplicationConfigurationNotFoundError => {
"The replication configuration was not found"
}
Self::ServerSideEncryptionConfigurationNotFoundError => {
"The server-side encryption configuration was not found"
}
Self::Custom(s) => s,
}
}
}
impl fmt::Display for S3ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug)]
pub struct S3Error {
pub code: S3ErrorCode,
pub message: String,
pub resource: Option<String>,
pub request_id: Option<String>,
pub status_code: http::StatusCode,
pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
pub headers: Option<Box<Vec<(String, String)>>>,
}
impl fmt::Display for S3Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "S3Error({}): {}", self.code, self.message)
}
}
impl std::error::Error for S3Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
}
}
impl S3Error {
#[must_use]
pub fn new(code: S3ErrorCode) -> Self {
let status_code = code.default_status_code();
let message = code.default_message().to_owned();
Self {
code,
message,
resource: None,
request_id: None,
status_code,
source: None,
headers: None,
}
}
#[must_use]
pub fn with_message(code: S3ErrorCode, message: impl Into<String>) -> Self {
Self {
status_code: code.default_status_code(),
message: message.into(),
code,
resource: None,
request_id: None,
source: None,
headers: None,
}
}
#[must_use]
pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
self.resource = Some(resource.into());
self
}
#[must_use]
pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
self.request_id = Some(request_id.into());
self
}
#[must_use]
pub fn with_source(mut self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
self.source = Some(Box::new(source));
self
}
#[must_use]
pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers
.get_or_insert_with(|| Box::new(Vec::new()))
.push((name.into(), value.into()));
self
}
#[must_use]
pub fn no_such_bucket(bucket_name: impl Into<String>) -> Self {
Self::new(S3ErrorCode::NoSuchBucket).with_resource(bucket_name)
}
#[must_use]
pub fn no_such_key(key: impl Into<String>) -> Self {
Self::new(S3ErrorCode::NoSuchKey).with_resource(key)
}
#[must_use]
pub fn no_such_upload(upload_id: impl Into<String>) -> Self {
Self::new(S3ErrorCode::NoSuchUpload).with_resource(upload_id)
}
#[must_use]
pub fn no_such_version(version_id: impl Into<String>) -> Self {
Self::new(S3ErrorCode::NoSuchVersion).with_resource(version_id)
}
#[must_use]
pub fn bucket_already_exists(bucket_name: impl Into<String>) -> Self {
Self::new(S3ErrorCode::BucketAlreadyExists).with_resource(bucket_name)
}
#[must_use]
pub fn bucket_already_owned_by_you(bucket_name: impl Into<String>) -> Self {
Self::new(S3ErrorCode::BucketAlreadyOwnedByYou).with_resource(bucket_name)
}
#[must_use]
pub fn bucket_not_empty(bucket_name: impl Into<String>) -> Self {
Self::new(S3ErrorCode::BucketNotEmpty).with_resource(bucket_name)
}
#[must_use]
pub fn access_denied(resource: impl Into<String>) -> Self {
Self::new(S3ErrorCode::AccessDenied).with_resource(resource)
}
#[must_use]
pub fn internal_error(message: impl Into<String>) -> Self {
Self::with_message(S3ErrorCode::InternalError, message)
}
#[must_use]
pub fn invalid_argument(message: impl Into<String>) -> Self {
Self::with_message(S3ErrorCode::InvalidArgument, message)
}
#[must_use]
pub fn invalid_bucket_name(bucket_name: impl Into<String>) -> Self {
Self::new(S3ErrorCode::InvalidBucketName).with_resource(bucket_name)
}
#[must_use]
pub fn invalid_range(range: impl Into<String>) -> Self {
Self::new(S3ErrorCode::InvalidRange).with_resource(range)
}
#[must_use]
pub fn invalid_part(part_info: impl Into<String>) -> Self {
Self::new(S3ErrorCode::InvalidPart).with_resource(part_info)
}
#[must_use]
pub fn invalid_part_order(detail: impl Into<String>) -> Self {
Self::new(S3ErrorCode::InvalidPartOrder).with_resource(detail)
}
#[must_use]
pub fn malformed_xml(detail: impl Into<String>) -> Self {
Self::new(S3ErrorCode::MalformedXML).with_resource(detail)
}
#[must_use]
pub fn method_not_allowed(method: impl Into<String>) -> Self {
Self::new(S3ErrorCode::MethodNotAllowed).with_resource(method)
}
#[must_use]
pub fn not_implemented(detail: impl Into<String>) -> Self {
Self::new(S3ErrorCode::NotImplemented).with_resource(detail)
}
#[must_use]
pub fn precondition_failed(condition: impl Into<String>) -> Self {
Self::new(S3ErrorCode::PreconditionFailed).with_resource(condition)
}
#[must_use]
pub fn signature_does_not_match(detail: impl Into<String>) -> Self {
Self::new(S3ErrorCode::SignatureDoesNotMatch).with_resource(detail)
}
}
#[macro_export]
macro_rules! s3_error {
($code:ident) => {
$crate::error::S3Error::new($crate::error::S3ErrorCode::$code)
};
($code:ident, $msg:expr) => {
$crate::error::S3Error::with_message($crate::error::S3ErrorCode::$code, $msg)
};
}