Skip to main content

deltalake_aws/
errors.rs

1//! Errors for S3 log store backed by DynamoDb
2
3use std::num::ParseIntError;
4
5use aws_credential_types::provider::error::CredentialsError;
6use aws_sdk_dynamodb::{
7    error::SdkError,
8    operation::{
9        create_table::CreateTableError, delete_item::DeleteItemError, get_item::GetItemError,
10        put_item::PutItemError, query::QueryError, update_item::UpdateItemError,
11    },
12};
13use aws_smithy_runtime_api::client::result::ServiceError;
14use deltalake_core::kernel::Version;
15
16macro_rules! impl_from_service_error {
17    ($error_type:ty) => {
18        impl<R> From<SdkError<$error_type, R>> for LockClientError
19        where
20            R: Send + Sync + std::fmt::Debug + 'static,
21        {
22            fn from(err: SdkError<$error_type, R>) -> Self {
23                match err {
24                    SdkError::ServiceError(e) => e.into(),
25                    _ => LockClientError::GenericDynamoDb {
26                        source: Box::new(err),
27                    },
28                }
29            }
30        }
31
32        impl<R> From<ServiceError<$error_type, R>> for LockClientError
33        where
34            R: Send + Sync + std::fmt::Debug + 'static,
35        {
36            fn from(value: ServiceError<$error_type, R>) -> Self {
37                value.into_err().into()
38            }
39        }
40    };
41}
42
43#[derive(thiserror::Error, Debug)]
44pub enum DynamoDbConfigError {
45    /// Billing mode string invalid
46    #[error("Invalid billing mode : {0}, supported values : ['provided', 'pay_per_request']")]
47    InvalidBillingMode(String),
48
49    /// Cannot parse max_elapsed_request_time value into u64
50    #[error("Cannot parse max elapsed request time into u64: {source}")]
51    ParseMaxElapsedRequestTime {
52        // config_value: String,
53        source: ParseIntError,
54    },
55    /// Cannot initialize DynamoDbConfiguration due to some sort of threading issue
56    #[error("Cannot initialize dynamodb lock configuration")]
57    InitializationError,
58}
59
60/// Errors produced by `DynamoDbLockClient`
61#[derive(thiserror::Error, Debug)]
62pub enum LockClientError {
63    #[error("Log item has invalid content: '{description}'")]
64    InconsistentData { description: String },
65
66    #[error("Lock table '{name}': creation failed: {source}")]
67    LockTableCreateFailure {
68        name: String,
69        source: Box<CreateTableError>,
70    },
71
72    #[error("Log entry for table '{table_path}' and version '{version}' already exists")]
73    VersionAlreadyExists {
74        table_path: String,
75        version: Version,
76    },
77
78    #[error("Provisioned table throughput exceeded")]
79    ProvisionedThroughputExceeded,
80
81    #[error("Lock table not found")]
82    LockTableNotFound,
83
84    #[error("error in DynamoDb")]
85    GenericDynamoDb {
86        source: Box<dyn std::error::Error + Send + Sync + 'static>,
87    },
88    #[error("configuration error: {source}")]
89    Credentials { source: CredentialsError },
90    #[error(
91        "Atomic rename requires a LockClient for S3 backends. \
92         Either configure the LockClient, or set AWS_S3_ALLOW_UNSAFE_RENAME=true \
93         to opt out of support for concurrent writers."
94    )]
95    LockClientRequired,
96
97    #[error("Log entry for table '{table_path}' and version '{version}' is already complete")]
98    VersionAlreadyCompleted {
99        table_path: String,
100        version: Version,
101    },
102}
103
104impl From<GetItemError> for LockClientError {
105    fn from(err: GetItemError) -> Self {
106        match err {
107            GetItemError::ProvisionedThroughputExceededException(_) => {
108                LockClientError::ProvisionedThroughputExceeded
109            }
110            GetItemError::RequestLimitExceeded(_) => LockClientError::ProvisionedThroughputExceeded,
111            GetItemError::ResourceNotFoundException(_) => LockClientError::LockTableNotFound,
112            _ => LockClientError::GenericDynamoDb {
113                source: Box::new(err),
114            },
115        }
116    }
117}
118
119impl From<QueryError> for LockClientError {
120    fn from(err: QueryError) -> Self {
121        match err {
122            QueryError::ProvisionedThroughputExceededException(_) => {
123                LockClientError::ProvisionedThroughputExceeded
124            }
125            QueryError::RequestLimitExceeded(_) => LockClientError::ProvisionedThroughputExceeded,
126            QueryError::ResourceNotFoundException(_) => LockClientError::LockTableNotFound,
127            _ => LockClientError::GenericDynamoDb {
128                source: Box::new(err),
129            },
130        }
131    }
132}
133
134impl From<PutItemError> for LockClientError {
135    fn from(err: PutItemError) -> Self {
136        match err {
137            PutItemError::ConditionalCheckFailedException(_) => {
138                unreachable!("error must be handled explicitly")
139            }
140            PutItemError::ProvisionedThroughputExceededException(_) => {
141                LockClientError::ProvisionedThroughputExceeded
142            }
143            PutItemError::RequestLimitExceeded(_) => LockClientError::ProvisionedThroughputExceeded,
144            PutItemError::ResourceNotFoundException(_) => LockClientError::LockTableNotFound,
145            PutItemError::ItemCollectionSizeLimitExceededException(_) => err.into(),
146            PutItemError::TransactionConflictException(_) => err.into(),
147            _ => LockClientError::GenericDynamoDb {
148                source: Box::new(err),
149            },
150        }
151    }
152}
153
154impl From<UpdateItemError> for LockClientError {
155    fn from(err: UpdateItemError) -> Self {
156        match err {
157            UpdateItemError::ConditionalCheckFailedException(_) => {
158                unreachable!("condition check failure in update is not an error")
159            }
160            UpdateItemError::InternalServerError(_) => err.into(),
161            UpdateItemError::ProvisionedThroughputExceededException(_) => {
162                LockClientError::ProvisionedThroughputExceeded
163            }
164            UpdateItemError::RequestLimitExceeded(_) => {
165                LockClientError::ProvisionedThroughputExceeded
166            }
167            UpdateItemError::ResourceNotFoundException(_) => LockClientError::LockTableNotFound,
168            UpdateItemError::ItemCollectionSizeLimitExceededException(_) => err.into(),
169            UpdateItemError::TransactionConflictException(_) => err.into(),
170            _ => LockClientError::GenericDynamoDb {
171                source: Box::new(err),
172            },
173        }
174    }
175}
176
177impl From<DeleteItemError> for LockClientError {
178    fn from(err: DeleteItemError) -> Self {
179        match err {
180            DeleteItemError::ConditionalCheckFailedException(_) => {
181                unreachable!("error must be handled explicitly")
182            }
183            DeleteItemError::InternalServerError(_) => err.into(),
184            DeleteItemError::ProvisionedThroughputExceededException(_) => {
185                LockClientError::ProvisionedThroughputExceeded
186            }
187            DeleteItemError::RequestLimitExceeded(_) => {
188                LockClientError::ProvisionedThroughputExceeded
189            }
190            DeleteItemError::ResourceNotFoundException(_) => LockClientError::LockTableNotFound,
191            DeleteItemError::ItemCollectionSizeLimitExceededException(_) => err.into(),
192            DeleteItemError::TransactionConflictException(_) => err.into(),
193            _ => LockClientError::GenericDynamoDb {
194                source: Box::new(err),
195            },
196        }
197    }
198}
199
200impl_from_service_error!(GetItemError);
201impl_from_service_error!(PutItemError);
202impl_from_service_error!(QueryError);
203impl_from_service_error!(UpdateItemError);
204impl_from_service_error!(DeleteItemError);