diff --git a/services/backup/src/database.rs b/services/backup/src/database.rs --- a/services/backup/src/database.rs +++ b/services/backup/src/database.rs @@ -1,5 +1,9 @@ +use aws_sdk_dynamodb::{model::AttributeValue, Error as DynamoDBError}; use chrono::{DateTime, Utc}; -use std::sync::Arc; +use std::{ + fmt::{Display, Formatter}, + sync::Arc, +}; #[derive(Clone, Debug)] pub struct BackupItem { @@ -85,5 +89,109 @@ } } -// TODO: Replace this with dedicated DB error -type Error = anyhow::Error; +#[derive( + Debug, derive_more::Display, derive_more::From, derive_more::Error, +)] +pub enum Error { + #[display(...)] + AwsSdk(DynamoDBError), + #[display(...)] + Attribute(DBItemError), +} + +#[derive(Debug, derive_more::Error, derive_more::Constructor)] +pub struct DBItemError { + attribute_name: &'static str, + attribute_value: Option, + attribute_error: DBItemAttributeError, +} + +impl Display for DBItemError { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match &self.attribute_error { + DBItemAttributeError::Missing => { + write!(f, "Attribute {} is missing", self.attribute_name) + } + DBItemAttributeError::IncorrectType => write!( + f, + "Value for attribute {} has incorrect type: {:?}", + self.attribute_name, self.attribute_value + ), + error => write!( + f, + "Error regarding attribute {} with value {:?}: {}", + self.attribute_name, self.attribute_value, error + ), + } + } +} + +#[derive(Debug, derive_more::Display, derive_more::Error)] +pub enum DBItemAttributeError { + #[display(...)] + Missing, + #[display(...)] + IncorrectType, + #[display(...)] + InvalidTimestamp(chrono::ParseError), +} + +fn parse_string_attribute( + attribute_name: &'static str, + attribute_value: Option, +) -> Result { + match attribute_value { + Some(AttributeValue::S(value)) => Ok(value), + Some(_) => Err(DBItemError::new( + attribute_name, + attribute_value, + DBItemAttributeError::IncorrectType, + )), + None => Err(DBItemError::new( + attribute_name, + attribute_value, + DBItemAttributeError::Missing, + )), + } +} + +fn parse_bool_attribute( + attribute_name: &'static str, + attribute_value: Option, +) -> Result { + match attribute_value { + Some(AttributeValue::Bool(value)) => Ok(value), + Some(_) => Err(DBItemError::new( + attribute_name, + attribute_value, + DBItemAttributeError::IncorrectType, + )), + None => Err(DBItemError::new( + attribute_name, + attribute_value, + DBItemAttributeError::Missing, + )), + } +} + +fn parse_datetime_attribute( + attribute_name: &'static str, + attribute_value: Option, +) -> Result, DBItemError> { + if let Some(AttributeValue::S(datetime)) = &attribute_value { + // parse() accepts a relaxed RFC3339 string + datetime.parse().map_err(|e| { + DBItemError::new( + attribute_name, + attribute_value, + DBItemAttributeError::InvalidTimestamp(e), + ) + }) + } else { + Err(DBItemError::new( + attribute_name, + attribute_value, + DBItemAttributeError::Missing, + )) + } +}