1use rustack_s3_model::error::{S3Error, S3ErrorCode};
23
24#[derive(Debug, thiserror::Error)]
31pub enum S3ServiceError {
32 #[error("The specified bucket does not exist: {bucket}")]
37 NoSuchBucket {
38 bucket: String,
40 },
41
42 #[error("The requested bucket name is not available: {bucket}")]
44 BucketAlreadyExists {
45 bucket: String,
47 },
48
49 #[error(
51 "Your previous request to create the named bucket succeeded and you already own it: \
52 {bucket}"
53 )]
54 BucketAlreadyOwnedByYou {
55 bucket: String,
57 },
58
59 #[error("The bucket you tried to delete is not empty: {bucket}")]
61 BucketNotEmpty {
62 bucket: String,
64 },
65
66 #[error("The specified key does not exist: {key}")]
71 NoSuchKey {
72 key: String,
74 },
75
76 #[error("The specified version does not exist: key={key}, version_id={version_id}")]
78 NoSuchVersion {
79 key: String,
81 version_id: String,
83 },
84
85 #[error("The specified upload does not exist: {upload_id}")]
90 NoSuchUpload {
91 upload_id: String,
93 },
94
95 #[error("The list of parts was not in ascending order")]
97 InvalidPartOrder,
98
99 #[error("One or more of the specified parts could not be found")]
101 InvalidPart,
102
103 #[error("Your proposed upload is smaller than the minimum allowed object size")]
105 EntityTooSmall,
106
107 #[error("Your proposed upload exceeds the maximum allowed object size")]
109 EntityTooLarge,
110
111 #[error("Invalid bucket name: {name}: {reason}")]
116 InvalidBucketName {
117 name: String,
119 reason: String,
121 },
122
123 #[error("Invalid argument: {message}")]
125 InvalidArgument {
126 message: String,
128 },
129
130 #[error("The requested range is not satisfiable")]
132 InvalidRange,
133
134 #[error("Invalid tag: {message}")]
136 InvalidTag {
137 message: String,
139 },
140
141 #[error("The XML you provided was not well-formed")]
143 MalformedXml,
144
145 #[error("Access Denied")]
150 AccessDenied,
151
152 #[error("The specified method is not allowed against this resource")]
154 MethodNotAllowed,
155
156 #[error("A header you provided implies functionality that is not implemented")]
161 NotImplemented,
162
163 #[error("At least one of the preconditions you specified did not hold")]
168 PreconditionFailed,
169
170 #[error("The conditional request cannot be processed")]
172 ConditionalRequestConflict,
173
174 #[error("Not Modified")]
176 NotModified,
177
178 #[error("The operation is not valid for the object's storage class")]
183 InvalidObjectState,
184
185 #[error("The source object of the COPY action is not in the active tier")]
187 ObjectNotInActiveTierError,
188
189 #[error("The Content-MD5 you specified is not valid")]
194 InvalidDigest,
195
196 #[error("The Content-MD5 you specified did not match what we received")]
198 BadDigest,
199
200 #[error("You must provide the Content-Length HTTP header")]
202 MissingContentLength,
203
204 #[error("Your key is too long")]
206 KeyTooLong,
207
208 #[error("Your request was too big")]
210 MaxMessageLengthExceeded,
211
212 #[error("The CORS configuration does not exist")]
217 NoSuchCorsConfiguration,
218
219 #[error("The TagSet does not exist")]
221 NoSuchTagSet,
222
223 #[error("The lifecycle configuration does not exist")]
225 NoSuchLifecycleConfiguration,
226
227 #[error("The bucket policy does not exist")]
229 NoSuchBucketPolicy,
230
231 #[error("The specified bucket does not have a website configuration")]
233 NoSuchWebsiteConfiguration,
234
235 #[error("The public access block configuration was not found")]
237 NoSuchPublicAccessBlockConfiguration,
238
239 #[error("The server-side encryption configuration was not found")]
241 ServerSideEncryptionConfigurationNotFoundError,
242
243 #[error("Object Lock configuration does not exist for this bucket")]
245 ObjectLockConfigurationNotFoundError,
246
247 #[error("The bucket ownership controls were not found")]
249 OwnershipControlsNotFoundError,
250
251 #[error("The replication configuration was not found")]
253 ReplicationConfigurationNotFoundError,
254
255 #[error(transparent)]
260 Internal(#[from] anyhow::Error),
261}
262
263impl S3ServiceError {
264 #[must_use]
269 pub fn into_s3_error(self) -> S3Error {
270 S3Error::from(self)
271 }
272}
273
274impl From<S3ServiceError> for S3Error {
275 fn from(err: S3ServiceError) -> Self {
276 let code = error_code(&err);
277 let message = match &err {
282 S3ServiceError::InvalidArgument { message }
283 | S3ServiceError::InvalidTag { message } => message.clone(),
284 S3ServiceError::InvalidBucketName { name, reason } => {
285 format!("Invalid bucket name: {name}: {reason}")
286 }
287 S3ServiceError::Internal(e) => e.to_string(),
288 _ => code.default_message().to_owned(),
289 };
290 S3Error::with_message(code, message)
291 }
292}
293
294fn error_code(err: &S3ServiceError) -> S3ErrorCode {
296 match err {
297 S3ServiceError::NoSuchBucket { .. } => S3ErrorCode::NoSuchBucket,
298 S3ServiceError::BucketAlreadyExists { .. } => S3ErrorCode::BucketAlreadyExists,
299 S3ServiceError::BucketAlreadyOwnedByYou { .. } => S3ErrorCode::BucketAlreadyOwnedByYou,
300 S3ServiceError::BucketNotEmpty { .. } => S3ErrorCode::BucketNotEmpty,
301 S3ServiceError::NoSuchKey { .. } => S3ErrorCode::NoSuchKey,
302 S3ServiceError::NoSuchVersion { .. } => S3ErrorCode::NoSuchVersion,
303 S3ServiceError::NoSuchUpload { .. } => S3ErrorCode::NoSuchUpload,
304 S3ServiceError::InvalidPartOrder => S3ErrorCode::InvalidPartOrder,
305 S3ServiceError::InvalidPart => S3ErrorCode::InvalidPart,
306 S3ServiceError::EntityTooSmall => S3ErrorCode::EntityTooSmall,
307 S3ServiceError::EntityTooLarge => S3ErrorCode::EntityTooLarge,
308 S3ServiceError::InvalidBucketName { .. } => S3ErrorCode::InvalidBucketName,
309 S3ServiceError::InvalidArgument { .. } | S3ServiceError::InvalidTag { .. } => {
310 S3ErrorCode::InvalidArgument
311 }
312 S3ServiceError::InvalidRange => S3ErrorCode::InvalidRange,
313 S3ServiceError::MalformedXml => S3ErrorCode::MalformedXML,
314 S3ServiceError::AccessDenied => S3ErrorCode::AccessDenied,
315 S3ServiceError::MethodNotAllowed => S3ErrorCode::MethodNotAllowed,
316 S3ServiceError::NotImplemented => S3ErrorCode::NotImplemented,
317 S3ServiceError::PreconditionFailed => S3ErrorCode::PreconditionFailed,
318 S3ServiceError::ConditionalRequestConflict => S3ErrorCode::ConditionalRequestConflict,
319 S3ServiceError::NotModified => S3ErrorCode::NotModified,
320 S3ServiceError::InvalidObjectState => S3ErrorCode::InvalidObjectState,
321 S3ServiceError::ObjectNotInActiveTierError => S3ErrorCode::ObjectNotInActiveTierError,
322 S3ServiceError::InvalidDigest => S3ErrorCode::InvalidDigest,
323 S3ServiceError::BadDigest => S3ErrorCode::BadDigest,
324 S3ServiceError::MissingContentLength => S3ErrorCode::MissingContentLength,
325 S3ServiceError::KeyTooLong => S3ErrorCode::KeyTooLongError,
326 S3ServiceError::MaxMessageLengthExceeded => S3ErrorCode::MaxMessageLengthExceeded,
327 S3ServiceError::NoSuchCorsConfiguration => S3ErrorCode::NoSuchCORSConfiguration,
328 S3ServiceError::NoSuchTagSet => S3ErrorCode::NoSuchTagSet,
329 S3ServiceError::NoSuchLifecycleConfiguration => S3ErrorCode::NoSuchLifecycleConfiguration,
330 S3ServiceError::NoSuchBucketPolicy => S3ErrorCode::NoSuchBucketPolicy,
331 S3ServiceError::NoSuchWebsiteConfiguration => S3ErrorCode::NoSuchWebsiteConfiguration,
332 S3ServiceError::NoSuchPublicAccessBlockConfiguration => {
333 S3ErrorCode::NoSuchPublicAccessBlockConfiguration
334 }
335 S3ServiceError::ServerSideEncryptionConfigurationNotFoundError => {
336 S3ErrorCode::ServerSideEncryptionConfigurationNotFoundError
337 }
338 S3ServiceError::ObjectLockConfigurationNotFoundError => {
339 S3ErrorCode::ObjectLockConfigurationNotFoundError
340 }
341 S3ServiceError::OwnershipControlsNotFoundError => {
342 S3ErrorCode::OwnershipControlsNotFoundError
343 }
344 S3ServiceError::ReplicationConfigurationNotFoundError => {
345 S3ErrorCode::ReplicationConfigurationNotFoundError
346 }
347 S3ServiceError::Internal(_) => S3ErrorCode::InternalError,
348 }
349}
350
351pub type S3ServiceResult<T> = Result<T, S3ServiceError>;
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn test_should_convert_no_such_bucket_to_s3_error() {
360 let err = S3ServiceError::NoSuchBucket {
361 bucket: "my-bucket".to_owned(),
362 };
363 let s3_err: S3Error = err.into();
364 assert_eq!(s3_err.code, S3ErrorCode::NoSuchBucket);
365 assert_eq!(s3_err.message, "The specified bucket does not exist");
367 }
368
369 #[test]
370 fn test_should_convert_no_such_key_to_s3_error() {
371 let err = S3ServiceError::NoSuchKey {
372 key: "path/to/obj".to_owned(),
373 };
374 let s3_err: S3Error = err.into();
375 assert_eq!(s3_err.code, S3ErrorCode::NoSuchKey);
376 }
377
378 #[test]
379 fn test_should_convert_bucket_already_exists_to_s3_error() {
380 let err = S3ServiceError::BucketAlreadyExists {
381 bucket: "taken".to_owned(),
382 };
383 let s3_err: S3Error = err.into();
384 assert_eq!(s3_err.code, S3ErrorCode::BucketAlreadyExists);
385 }
386
387 #[test]
388 fn test_should_convert_bucket_already_owned_to_s3_error() {
389 let err = S3ServiceError::BucketAlreadyOwnedByYou {
390 bucket: "mine".to_owned(),
391 };
392 let s3_err: S3Error = err.into();
393 assert_eq!(s3_err.code, S3ErrorCode::BucketAlreadyOwnedByYou);
394 }
395
396 #[test]
397 fn test_should_convert_bucket_not_empty_to_s3_error() {
398 let err = S3ServiceError::BucketNotEmpty {
399 bucket: "full".to_owned(),
400 };
401 let s3_err: S3Error = err.into();
402 assert_eq!(s3_err.code, S3ErrorCode::BucketNotEmpty);
403 }
404
405 #[test]
406 fn test_should_convert_invalid_bucket_name_to_s3_error() {
407 let err = S3ServiceError::InvalidBucketName {
408 name: "BAD".to_owned(),
409 reason: "uppercase".to_owned(),
410 };
411 let s3_err: S3Error = err.into();
412 assert_eq!(s3_err.code, S3ErrorCode::InvalidBucketName);
413 }
414
415 #[test]
416 fn test_should_convert_entity_too_small_to_s3_error() {
417 let err = S3ServiceError::EntityTooSmall;
418 let s3_err: S3Error = err.into();
419 assert_eq!(s3_err.code, S3ErrorCode::EntityTooSmall);
420 }
421
422 #[test]
423 fn test_should_convert_access_denied_to_s3_error() {
424 let err = S3ServiceError::AccessDenied;
425 let s3_err: S3Error = err.into();
426 assert_eq!(s3_err.code, S3ErrorCode::AccessDenied);
427 }
428
429 #[test]
430 fn test_should_convert_internal_error_to_s3_error() {
431 let err = S3ServiceError::Internal(anyhow::anyhow!("disk I/O failure"));
432 let s3_err: S3Error = err.into();
433 assert_eq!(s3_err.code, S3ErrorCode::InternalError);
434 }
435
436 #[test]
437 fn test_should_use_into_s3_error_method() {
438 let err = S3ServiceError::InvalidRange;
439 let s3_err = err.into_s3_error();
440 assert_eq!(s3_err.code, S3ErrorCode::InvalidRange);
441 }
442
443 #[test]
444 fn test_should_convert_no_such_upload_to_s3_error() {
445 let err = S3ServiceError::NoSuchUpload {
446 upload_id: "abc123".to_owned(),
447 };
448 let s3_err: S3Error = err.into();
449 assert_eq!(s3_err.code, S3ErrorCode::NoSuchUpload);
450 }
451
452 #[test]
453 fn test_should_convert_precondition_failed_to_s3_error() {
454 let err = S3ServiceError::PreconditionFailed;
455 let s3_err: S3Error = err.into();
456 assert_eq!(s3_err.code, S3ErrorCode::PreconditionFailed);
457 }
458
459 #[test]
460 fn test_should_convert_config_not_found_errors() {
461 let cases: Vec<(S3ServiceError, S3ErrorCode)> = vec![
462 (
463 S3ServiceError::NoSuchCorsConfiguration,
464 S3ErrorCode::NoSuchCORSConfiguration,
465 ),
466 (S3ServiceError::NoSuchTagSet, S3ErrorCode::NoSuchTagSet),
467 (
468 S3ServiceError::NoSuchLifecycleConfiguration,
469 S3ErrorCode::NoSuchLifecycleConfiguration,
470 ),
471 (
472 S3ServiceError::NoSuchBucketPolicy,
473 S3ErrorCode::NoSuchBucketPolicy,
474 ),
475 (
476 S3ServiceError::NoSuchWebsiteConfiguration,
477 S3ErrorCode::NoSuchWebsiteConfiguration,
478 ),
479 (
480 S3ServiceError::NoSuchPublicAccessBlockConfiguration,
481 S3ErrorCode::NoSuchPublicAccessBlockConfiguration,
482 ),
483 (
484 S3ServiceError::ObjectLockConfigurationNotFoundError,
485 S3ErrorCode::ObjectLockConfigurationNotFoundError,
486 ),
487 (
488 S3ServiceError::OwnershipControlsNotFoundError,
489 S3ErrorCode::OwnershipControlsNotFoundError,
490 ),
491 (
492 S3ServiceError::ReplicationConfigurationNotFoundError,
493 S3ErrorCode::ReplicationConfigurationNotFoundError,
494 ),
495 (
496 S3ServiceError::ServerSideEncryptionConfigurationNotFoundError,
497 S3ErrorCode::ServerSideEncryptionConfigurationNotFoundError,
498 ),
499 ];
500
501 for (err, expected_code) in cases {
502 let s3_err: S3Error = err.into();
503 assert_eq!(s3_err.code, expected_code);
504 }
505 }
506
507 #[test]
508 fn test_should_convert_not_modified_to_304() {
509 let err = S3ServiceError::NotModified;
510 let s3_err: S3Error = err.into();
511 assert_eq!(s3_err.code, S3ErrorCode::NotModified);
512 assert_eq!(s3_err.status_code, http::StatusCode::NOT_MODIFIED);
513 }
514
515 #[test]
516 fn test_should_convert_conditional_request_conflict_to_409() {
517 let err = S3ServiceError::ConditionalRequestConflict;
518 let s3_err: S3Error = err.into();
519 assert_eq!(s3_err.code, S3ErrorCode::ConditionalRequestConflict);
520 assert_eq!(s3_err.status_code, http::StatusCode::CONFLICT);
521 }
522
523 #[test]
524 fn test_should_convert_bad_digest_to_400() {
525 let err = S3ServiceError::BadDigest;
526 let s3_err: S3Error = err.into();
527 assert_eq!(s3_err.code, S3ErrorCode::BadDigest);
528 assert_eq!(s3_err.status_code, http::StatusCode::BAD_REQUEST);
529 }
530
531 #[test]
532 fn test_should_convert_max_message_length_exceeded_to_400() {
533 let err = S3ServiceError::MaxMessageLengthExceeded;
534 let s3_err: S3Error = err.into();
535 assert_eq!(s3_err.code, S3ErrorCode::MaxMessageLengthExceeded);
536 assert_eq!(s3_err.status_code, http::StatusCode::BAD_REQUEST);
537 }
538}