use std::{collections::HashMap, fmt};
use crate::{attribute_value::AttributeValue, types::CancellationReason};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum DynamoDBErrorCode {
ResourceInUseException,
ResourceNotFoundException,
ConditionalCheckFailedException,
TransactionCanceledException,
TransactionConflictException,
TransactionInProgressException,
IdempotentParameterMismatchException,
ItemCollectionSizeLimitExceededException,
ProvisionedThroughputExceededException,
RequestLimitExceeded,
#[default]
ValidationException,
SerializationException,
InternalServerError,
MissingAction,
AccessDeniedException,
UnrecognizedClientException,
}
impl DynamoDBErrorCode {
#[must_use]
pub fn error_type(&self) -> &'static str {
match self {
Self::ResourceInUseException => {
"com.amazonaws.dynamodb.v20120810#ResourceInUseException"
}
Self::ResourceNotFoundException => {
"com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
}
Self::ConditionalCheckFailedException => {
"com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException"
}
Self::TransactionCanceledException => {
"com.amazonaws.dynamodb.v20120810#TransactionCanceledException"
}
Self::TransactionConflictException => {
"com.amazonaws.dynamodb.v20120810#TransactionConflictException"
}
Self::TransactionInProgressException => {
"com.amazonaws.dynamodb.v20120810#TransactionInProgressException"
}
Self::IdempotentParameterMismatchException => {
"com.amazonaws.dynamodb.v20120810#IdempotentParameterMismatchException"
}
Self::ItemCollectionSizeLimitExceededException => {
"com.amazonaws.dynamodb.v20120810#ItemCollectionSizeLimitExceededException"
}
Self::ProvisionedThroughputExceededException => {
"com.amazonaws.dynamodb.v20120810#ProvisionedThroughputExceededException"
}
Self::RequestLimitExceeded => "com.amazonaws.dynamodb.v20120810#RequestLimitExceeded",
Self::ValidationException => "com.amazon.coral.validate#ValidationException",
Self::SerializationException => {
"com.amazonaws.dynamodb.v20120810#SerializationException"
}
Self::InternalServerError => "com.amazonaws.dynamodb.v20120810#InternalServerError",
Self::MissingAction => "com.amazonaws.dynamodb.v20120810#MissingAction",
Self::AccessDeniedException => "com.amazonaws.dynamodb.v20120810#AccessDeniedException",
Self::UnrecognizedClientException => {
"com.amazonaws.dynamodb.v20120810#UnrecognizedClientException"
}
}
}
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::ResourceInUseException => "ResourceInUseException",
Self::ResourceNotFoundException => "ResourceNotFoundException",
Self::ConditionalCheckFailedException => "ConditionalCheckFailedException",
Self::TransactionCanceledException => "TransactionCanceledException",
Self::TransactionConflictException => "TransactionConflictException",
Self::TransactionInProgressException => "TransactionInProgressException",
Self::IdempotentParameterMismatchException => "IdempotentParameterMismatchException",
Self::ItemCollectionSizeLimitExceededException => {
"ItemCollectionSizeLimitExceededException"
}
Self::ProvisionedThroughputExceededException => {
"ProvisionedThroughputExceededException"
}
Self::RequestLimitExceeded => "RequestLimitExceeded",
Self::ValidationException => "ValidationException",
Self::SerializationException => "SerializationException",
Self::InternalServerError => "InternalServerError",
Self::MissingAction => "MissingAction",
Self::AccessDeniedException => "AccessDeniedException",
Self::UnrecognizedClientException => "UnrecognizedClientException",
}
}
#[must_use]
pub fn default_status_code(&self) -> http::StatusCode {
match self {
Self::InternalServerError => http::StatusCode::INTERNAL_SERVER_ERROR,
_ => http::StatusCode::BAD_REQUEST,
}
}
}
impl fmt::Display for DynamoDBErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug)]
pub struct DynamoDBError {
pub code: DynamoDBErrorCode,
pub message: String,
pub status_code: http::StatusCode,
pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
pub item: Option<HashMap<String, AttributeValue>>,
pub cancellation_reasons: Vec<CancellationReason>,
}
impl fmt::Display for DynamoDBError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DynamoDBError({}): {}", self.code, self.message)
}
}
impl std::error::Error for DynamoDBError {
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 DynamoDBError {
#[must_use]
pub fn new(code: DynamoDBErrorCode) -> Self {
Self {
status_code: code.default_status_code(),
message: code.as_str().to_owned(),
code,
source: None,
item: None,
cancellation_reasons: Vec::new(),
}
}
#[must_use]
pub fn with_message(code: DynamoDBErrorCode, message: impl Into<String>) -> Self {
Self {
status_code: code.default_status_code(),
message: message.into(),
code,
source: None,
item: None,
cancellation_reasons: Vec::new(),
}
}
#[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_item(mut self, item: HashMap<String, AttributeValue>) -> Self {
self.item = Some(item);
self
}
#[must_use]
pub fn with_cancellation_reasons(mut self, reasons: Vec<CancellationReason>) -> Self {
self.cancellation_reasons = reasons;
self
}
#[must_use]
pub fn error_type(&self) -> &'static str {
self.code.error_type()
}
#[must_use]
pub fn resource_in_use(message: impl Into<String>) -> Self {
Self::with_message(DynamoDBErrorCode::ResourceInUseException, message)
}
#[must_use]
pub fn resource_not_found(message: impl Into<String>) -> Self {
Self::with_message(DynamoDBErrorCode::ResourceNotFoundException, message)
}
#[must_use]
pub fn conditional_check_failed(message: impl Into<String>) -> Self {
Self::with_message(DynamoDBErrorCode::ConditionalCheckFailedException, message)
}
#[must_use]
pub fn validation(message: impl Into<String>) -> Self {
Self::with_message(DynamoDBErrorCode::ValidationException, message)
}
#[must_use]
pub fn serialization_exception(message: impl Into<String>) -> Self {
Self::with_message(DynamoDBErrorCode::SerializationException, message)
}
#[must_use]
pub fn internal_error(message: impl Into<String>) -> Self {
Self::with_message(DynamoDBErrorCode::InternalServerError, message)
}
#[must_use]
pub fn missing_action() -> Self {
Self::with_message(
DynamoDBErrorCode::MissingAction,
"Missing required header: X-Amz-Target",
)
}
#[must_use]
pub fn transaction_cancelled(reasons: Vec<CancellationReason>) -> Self {
Self::with_message(
DynamoDBErrorCode::TransactionCanceledException,
"Transaction cancelled, please refer cancellation reasons for specific reasons [See \
the CancellationReasons field]",
)
.with_cancellation_reasons(reasons)
}
#[must_use]
pub fn unknown_operation(target: &str) -> Self {
Self::with_message(
DynamoDBErrorCode::UnrecognizedClientException,
format!("Unrecognized operation: {target}"),
)
}
}
#[macro_export]
macro_rules! dynamodb_error {
($code:ident) => {
$crate::error::DynamoDBError::new($crate::error::DynamoDBErrorCode::$code)
};
($code:ident, $msg:expr) => {
$crate::error::DynamoDBError::with_message($crate::error::DynamoDBErrorCode::$code, $msg)
};
}