1use std::{collections::HashMap, fmt};
7
8use crate::{attribute_value::AttributeValue, types::CancellationReason};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
12#[non_exhaustive]
13pub enum DynamoDBErrorCode {
14 ResourceInUseException,
16 ResourceNotFoundException,
18 ConditionalCheckFailedException,
20 TransactionCanceledException,
22 TransactionConflictException,
24 TransactionInProgressException,
26 IdempotentParameterMismatchException,
28 ItemCollectionSizeLimitExceededException,
30 ProvisionedThroughputExceededException,
32 RequestLimitExceeded,
34 #[default]
36 ValidationException,
37 SerializationException,
39 InternalServerError,
41 MissingAction,
43 AccessDeniedException,
45 UnrecognizedClientException,
47}
48
49impl DynamoDBErrorCode {
50 #[must_use]
52 pub fn error_type(&self) -> &'static str {
53 match self {
54 Self::ResourceInUseException => {
55 "com.amazonaws.dynamodb.v20120810#ResourceInUseException"
56 }
57 Self::ResourceNotFoundException => {
58 "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
59 }
60 Self::ConditionalCheckFailedException => {
61 "com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException"
62 }
63 Self::TransactionCanceledException => {
64 "com.amazonaws.dynamodb.v20120810#TransactionCanceledException"
65 }
66 Self::TransactionConflictException => {
67 "com.amazonaws.dynamodb.v20120810#TransactionConflictException"
68 }
69 Self::TransactionInProgressException => {
70 "com.amazonaws.dynamodb.v20120810#TransactionInProgressException"
71 }
72 Self::IdempotentParameterMismatchException => {
73 "com.amazonaws.dynamodb.v20120810#IdempotentParameterMismatchException"
74 }
75 Self::ItemCollectionSizeLimitExceededException => {
76 "com.amazonaws.dynamodb.v20120810#ItemCollectionSizeLimitExceededException"
77 }
78 Self::ProvisionedThroughputExceededException => {
79 "com.amazonaws.dynamodb.v20120810#ProvisionedThroughputExceededException"
80 }
81 Self::RequestLimitExceeded => "com.amazonaws.dynamodb.v20120810#RequestLimitExceeded",
82 Self::ValidationException => "com.amazon.coral.validate#ValidationException",
83 Self::SerializationException => {
84 "com.amazonaws.dynamodb.v20120810#SerializationException"
85 }
86 Self::InternalServerError => "com.amazonaws.dynamodb.v20120810#InternalServerError",
87 Self::MissingAction => "com.amazonaws.dynamodb.v20120810#MissingAction",
88 Self::AccessDeniedException => "com.amazonaws.dynamodb.v20120810#AccessDeniedException",
89 Self::UnrecognizedClientException => {
90 "com.amazonaws.dynamodb.v20120810#UnrecognizedClientException"
91 }
92 }
93 }
94
95 #[must_use]
97 pub fn as_str(&self) -> &'static str {
98 match self {
99 Self::ResourceInUseException => "ResourceInUseException",
100 Self::ResourceNotFoundException => "ResourceNotFoundException",
101 Self::ConditionalCheckFailedException => "ConditionalCheckFailedException",
102 Self::TransactionCanceledException => "TransactionCanceledException",
103 Self::TransactionConflictException => "TransactionConflictException",
104 Self::TransactionInProgressException => "TransactionInProgressException",
105 Self::IdempotentParameterMismatchException => "IdempotentParameterMismatchException",
106 Self::ItemCollectionSizeLimitExceededException => {
107 "ItemCollectionSizeLimitExceededException"
108 }
109 Self::ProvisionedThroughputExceededException => {
110 "ProvisionedThroughputExceededException"
111 }
112 Self::RequestLimitExceeded => "RequestLimitExceeded",
113 Self::ValidationException => "ValidationException",
114 Self::SerializationException => "SerializationException",
115 Self::InternalServerError => "InternalServerError",
116 Self::MissingAction => "MissingAction",
117 Self::AccessDeniedException => "AccessDeniedException",
118 Self::UnrecognizedClientException => "UnrecognizedClientException",
119 }
120 }
121
122 #[must_use]
124 pub fn default_status_code(&self) -> http::StatusCode {
125 match self {
126 Self::InternalServerError => http::StatusCode::INTERNAL_SERVER_ERROR,
127 _ => http::StatusCode::BAD_REQUEST,
128 }
129 }
130}
131
132impl fmt::Display for DynamoDBErrorCode {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 f.write_str(self.as_str())
135 }
136}
137
138#[derive(Debug)]
140pub struct DynamoDBError {
141 pub code: DynamoDBErrorCode,
143 pub message: String,
145 pub status_code: http::StatusCode,
147 pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
149 pub item: Option<HashMap<String, AttributeValue>>,
152 pub cancellation_reasons: Vec<CancellationReason>,
154}
155
156impl fmt::Display for DynamoDBError {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 write!(f, "DynamoDBError({}): {}", self.code, self.message)
159 }
160}
161
162impl std::error::Error for DynamoDBError {
163 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
164 self.source
165 .as_ref()
166 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
167 }
168}
169
170impl DynamoDBError {
171 #[must_use]
173 pub fn new(code: DynamoDBErrorCode) -> Self {
174 Self {
175 status_code: code.default_status_code(),
176 message: code.as_str().to_owned(),
177 code,
178 source: None,
179 item: None,
180 cancellation_reasons: Vec::new(),
181 }
182 }
183
184 #[must_use]
186 pub fn with_message(code: DynamoDBErrorCode, message: impl Into<String>) -> Self {
187 Self {
188 status_code: code.default_status_code(),
189 message: message.into(),
190 code,
191 source: None,
192 item: None,
193 cancellation_reasons: Vec::new(),
194 }
195 }
196
197 #[must_use]
199 pub fn with_source(mut self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
200 self.source = Some(Box::new(source));
201 self
202 }
203
204 #[must_use]
207 pub fn with_item(mut self, item: HashMap<String, AttributeValue>) -> Self {
208 self.item = Some(item);
209 self
210 }
211
212 #[must_use]
214 pub fn with_cancellation_reasons(mut self, reasons: Vec<CancellationReason>) -> Self {
215 self.cancellation_reasons = reasons;
216 self
217 }
218
219 #[must_use]
221 pub fn error_type(&self) -> &'static str {
222 self.code.error_type()
223 }
224
225 #[must_use]
229 pub fn resource_in_use(message: impl Into<String>) -> Self {
230 Self::with_message(DynamoDBErrorCode::ResourceInUseException, message)
231 }
232
233 #[must_use]
235 pub fn resource_not_found(message: impl Into<String>) -> Self {
236 Self::with_message(DynamoDBErrorCode::ResourceNotFoundException, message)
237 }
238
239 #[must_use]
241 pub fn conditional_check_failed(message: impl Into<String>) -> Self {
242 Self::with_message(DynamoDBErrorCode::ConditionalCheckFailedException, message)
243 }
244
245 #[must_use]
247 pub fn validation(message: impl Into<String>) -> Self {
248 Self::with_message(DynamoDBErrorCode::ValidationException, message)
249 }
250
251 #[must_use]
253 pub fn serialization_exception(message: impl Into<String>) -> Self {
254 Self::with_message(DynamoDBErrorCode::SerializationException, message)
255 }
256
257 #[must_use]
259 pub fn internal_error(message: impl Into<String>) -> Self {
260 Self::with_message(DynamoDBErrorCode::InternalServerError, message)
261 }
262
263 #[must_use]
265 pub fn missing_action() -> Self {
266 Self::with_message(
267 DynamoDBErrorCode::MissingAction,
268 "Missing required header: X-Amz-Target",
269 )
270 }
271
272 #[must_use]
274 pub fn transaction_cancelled(reasons: Vec<CancellationReason>) -> Self {
275 Self::with_message(
276 DynamoDBErrorCode::TransactionCanceledException,
277 "Transaction cancelled, please refer cancellation reasons for specific reasons [See \
278 the CancellationReasons field]",
279 )
280 .with_cancellation_reasons(reasons)
281 }
282
283 #[must_use]
285 pub fn unknown_operation(target: &str) -> Self {
286 Self::with_message(
287 DynamoDBErrorCode::UnrecognizedClientException,
288 format!("Unrecognized operation: {target}"),
289 )
290 }
291}
292
293#[macro_export]
308macro_rules! dynamodb_error {
309 ($code:ident) => {
310 $crate::error::DynamoDBError::new($crate::error::DynamoDBErrorCode::$code)
311 };
312 ($code:ident, $msg:expr) => {
313 $crate::error::DynamoDBError::with_message($crate::error::DynamoDBErrorCode::$code, $msg)
314 };
315}