pub use dynamodel_derive::Dynamodel;
use aws_sdk_dynamodb::types::AttributeValue;
use std::num::{ParseFloatError, ParseIntError};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ConvertError {
#[error("`{0}` field is not set")]
FieldNotSet(String),
#[error("expect `{0}` type, but got `{1:?}`")]
AttributeValueUnmatched(String, AttributeValue),
#[error("{0}")]
ParseInt(#[from] ParseIntError),
#[error("{0}")]
ParseFloat(#[from] ParseFloatError),
#[error("not found any variant in hashmap")]
VariantNotFound,
#[error(transparent)]
Other(Box<dyn std::error::Error + Send + Sync>),
}
fn unmatch_err(expected: &str) -> impl Fn(&AttributeValue) -> ConvertError + '_ {
|value: &AttributeValue| {
ConvertError::AttributeValueUnmatched(expected.to_string(), value.to_owned())
}
}
pub trait AttributeValueConvertible: Sized {
fn into_attribute_value(self) -> AttributeValue;
fn try_from_attribute_value(value: &AttributeValue) -> Result<Self, ConvertError>;
}
impl AttributeValueConvertible for String {
fn into_attribute_value(self) -> AttributeValue {
AttributeValue::S(self)
}
fn try_from_attribute_value(value: &AttributeValue) -> Result<Self, ConvertError> {
value
.as_s()
.map(|v| v.to_string())
.map_err(unmatch_err("S"))
}
}
impl AttributeValueConvertible for bool {
fn into_attribute_value(self) -> AttributeValue {
AttributeValue::Bool(self)
}
fn try_from_attribute_value(value: &AttributeValue) -> Result<Self, ConvertError> {
value.as_bool().copied().map_err(unmatch_err("Bool"))
}
}
macro_rules! impl_to_nums {
($($ty:ty),*) => {
$(
impl AttributeValueConvertible for $ty {
fn into_attribute_value(self) -> AttributeValue {
AttributeValue::N(self.to_string())
}
fn try_from_attribute_value(value: &AttributeValue) -> Result<Self, ConvertError> {
value.as_n()
.map_err(unmatch_err("N"))
.and_then(|v| v.parse::<$ty>().map_err(|e| e.into()))
}
}
)*
}
}
impl_to_nums! {
u8, u16, u32, u64, u128, usize,
i8, i16, i32, i64, i128, isize,
f32, f64
}
impl<T: AttributeValueConvertible> AttributeValueConvertible for Vec<T> {
fn into_attribute_value(self) -> AttributeValue {
AttributeValue::L(
self.into_iter()
.map(AttributeValueConvertible::into_attribute_value)
.collect(),
)
}
fn try_from_attribute_value(value: &AttributeValue) -> Result<Self, ConvertError> {
let mut values: Vec<T> = vec![];
for v in value.as_l().map_err(unmatch_err("L"))?.iter() {
let v: T = AttributeValueConvertible::try_from_attribute_value(v)?;
values.push(v);
}
Ok(values)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn string_can_be_converted_into_attribute_value() {
let value = "Hello".to_string();
assert_eq!(
value.into_attribute_value(),
AttributeValue::S("Hello".into())
);
}
#[test]
fn string_can_be_converted_from_attribute_value() {
let value = AttributeValue::S("Hello".into());
let result: Result<String, ConvertError> =
AttributeValueConvertible::try_from_attribute_value(&value);
assert_eq!(result.unwrap(), "Hello".to_string());
}
#[test]
fn boolean_can_be_converted_into_attribute_value() {
let value = true;
assert_eq!(value.into_attribute_value(), AttributeValue::Bool(true));
}
#[test]
fn boolean_can_be_converted_from_attribute_value() {
let value = AttributeValue::Bool(false);
let result: Result<bool, ConvertError> =
AttributeValueConvertible::try_from_attribute_value(&value);
assert!(!result.unwrap());
}
#[test]
fn string_vector_can_be_converted_into_attribute_value() {
let value = vec!["Hello".to_string(), "World".to_string()];
assert_eq!(
value.into_attribute_value(),
AttributeValue::L(vec![
AttributeValue::S("Hello".into()),
AttributeValue::S("World".into())
]),
);
}
#[test]
fn string_vector_can_be_converted_from_attribute_value() {
let expected = vec!["Hello".to_string(), "World".to_string()];
let value = AttributeValue::L(vec![
AttributeValue::S("Hello".into()),
AttributeValue::S("World".into()),
]);
let result: Result<Vec<String>, ConvertError> =
AttributeValueConvertible::try_from_attribute_value(&value);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn boolean_vector_can_be_converted_into_attribute_value() {
let value = vec![true, false];
assert_eq!(
value.into_attribute_value(),
AttributeValue::L(vec![
AttributeValue::Bool(true),
AttributeValue::Bool(false)
]),
);
}
#[test]
fn boolean_vector_can_be_converted_from_attribute_value() {
let expected = vec![true, false];
let value = AttributeValue::L(vec![
AttributeValue::Bool(true),
AttributeValue::Bool(false),
]);
let result: Result<Vec<bool>, ConvertError> =
AttributeValueConvertible::try_from_attribute_value(&value);
assert_eq!(result.unwrap(), expected);
}
macro_rules! test_int {
($($ty:ty),*) => {
$(
paste::item! {
#[test]
fn [<$ty _can_be_converted_into_attribute_value>]() {
let value: $ty = 10;
assert_eq!(value.into_attribute_value(), AttributeValue::N("10".into()));
}
#[test]
fn [<$ty _can_be_converted_from_attribute_value>]() {
let expected: $ty = 10;
let value = AttributeValue::N("10".into());
let result: Result<$ty, ConvertError> = AttributeValueConvertible::try_from_attribute_value(&value);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn [<$ty _vector_can_be_converted_into_attribute_value>]() {
let value: Vec<$ty> = vec![10, 20];
assert_eq!(
value.into_attribute_value(),
AttributeValue::L(vec![AttributeValue::N("10".into()), AttributeValue::N("20".into())]),
);
}
#[test]
fn [<$ty _vector_can_be_converted_from_attribute_value>]() {
let expected: Vec<$ty> = vec![10, 20];
let value = AttributeValue::L(vec![AttributeValue::N("10".into()), AttributeValue::N("20".into())]);
let result: Result<Vec<$ty>, ConvertError> = AttributeValueConvertible::try_from_attribute_value(&value);
assert_eq!(result.unwrap(), expected);
}
}
)*
}
}
test_int! { u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize }
macro_rules! test_float {
($($ty:ty),*) => {
$(
paste::item! {
#[test]
fn [<$ty _can_be_converted_into_attribute_value>]() {
let value: $ty = 1.2;
assert_eq!(value.into_attribute_value(), AttributeValue::N("1.2".into()));
}
#[test]
fn [<$ty _can_be_converted_from_attribute_value>]() {
let expected: $ty = 1.2;
let value = AttributeValue::N("1.2".into());
let result: Result<$ty, ConvertError> = AttributeValueConvertible::try_from_attribute_value(&value);
assert_eq!(result.unwrap(), expected);
}
#[test]
fn [<$ty _vector_can_be_converted_into_attribute_value>]() {
let value: Vec<$ty> = vec![1.2, 3.45];
assert_eq!(
value.into_attribute_value(),
AttributeValue::L(vec![AttributeValue::N("1.2".into()), AttributeValue::N("3.45".into())]),
);
}
#[test]
fn [<$ty _vector_can_be_converted_from_attribute_value>]() {
let expected: Vec<$ty> = vec![1.2, 3.45];
let value = AttributeValue::L(vec![AttributeValue::N("1.2".into()), AttributeValue::N("3.45".into())]);
let result: Result<Vec<$ty>, ConvertError> = AttributeValueConvertible::try_from_attribute_value(&value);
assert_eq!(result.unwrap(), expected);
}
}
)*
}
}
test_float! { f32, f64 }
}