1use std::error::Error as StdError;
2use std::fmt::Display;
3use std::io;
4
5use aws_smithy_types::error::display::DisplayErrorContext;
6use aws_smithy_types::error::metadata::ProvideErrorMetadata;
7use thiserror::Error;
8
9pub type Result<T> = std::result::Result<T, Error>;
11
12#[derive(Debug, Error)]
14pub enum Error {
15 #[error("invalid S3 URI `{uri}`: {reason}")]
17 InvalidS3Uri {
18 uri: String,
20 reason: String,
22 },
23 #[error("invalid local path `{path}`: {reason}")]
25 InvalidLocalPath {
26 path: String,
28 reason: String,
30 },
31 #[error("invalid option: {0}")]
34 InvalidOption(String),
35 #[error("invalid ZIP entry `{path}`: {reason}")]
37 InvalidZipEntry {
38 path: String,
40 reason: String,
42 },
43 #[error("duplicate ZIP file path `{0}`")]
45 DuplicateZipPath(String),
46 #[error("entry `{path}` is {size} bytes, larger than the S3 single PutObject limit")]
48 EntryTooLarge {
49 path: String,
51 size: u64,
53 },
54 #[error("S3 {operation} failed for s3://{bucket}/{key}: {message}")]
56 S3 {
57 operation: &'static str,
59 bucket: String,
61 key: String,
63 message: String,
65 },
66 #[error("conditional write failed for s3://{bucket}/{key}: {message}")]
68 ConditionalConflict {
69 bucket: String,
71 key: String,
73 message: String,
75 },
76 #[error("{original}; additionally failed to abort multipart upload: {abort}")]
78 MultipartAbort {
79 original: Box<Error>,
81 abort: Box<Error>,
83 },
84 #[error("ZIP operation failed: {0}")]
86 Zip(#[from] async_zip::error::ZipError),
87 #[error("I/O failed: {0}")]
89 Io(#[from] io::Error),
90 #[error("worker task failed: {0}")]
92 Join(#[from] tokio::task::JoinError),
93 #[error("AWS SDK build failed: {0}")]
95 Build(String),
96}
97
98pub(crate) fn aws_error_message(error: &(impl ProvideErrorMetadata + Display)) -> String {
99 match (error.code(), error.message()) {
100 (Some(code), Some(message)) if !message.is_empty() && message != code => {
101 format!("{code}: {message}")
102 }
103 (Some(code), _) => code.to_string(),
104 (None, Some(message)) if !message.is_empty() => message.to_string(),
105 _ => error.to_string(),
106 }
107}
108
109pub(crate) fn aws_error_context(error: &(impl ProvideErrorMetadata + StdError)) -> String {
110 format!("{}", DisplayErrorContext(error))
111}
112
113#[cfg(test)]
114mod tests {
115 use aws_smithy_types::error::ErrorMetadata;
116
117 use super::*;
118
119 #[test]
120 fn aws_error_message_prefers_code_and_message() {
121 let metadata = ErrorMetadata::builder()
122 .code("NoSuchKey")
123 .message("The specified key does not exist.")
124 .build();
125
126 assert_eq!(
127 aws_error_message(&metadata),
128 "NoSuchKey: The specified key does not exist."
129 );
130 }
131
132 #[test]
133 fn aws_error_message_falls_back_to_display() {
134 let metadata = ErrorMetadata::builder().build();
135
136 assert_eq!(aws_error_message(&metadata), "Error");
137 }
138
139 #[test]
140 fn aws_error_context_includes_error_chain() {
141 let metadata = ErrorMetadata::builder()
142 .code("NoSuchKey")
143 .message("The specified key does not exist.")
144 .build();
145
146 let context = aws_error_context(&metadata);
147
148 assert!(context.contains("NoSuchKey"));
149 assert!(context.contains("The specified key does not exist."));
150 }
151}