Crate dynamodel

source ·
Expand description

§dynamodel

This library provide a derive macro to implement conversion between your struct and HashMap<String, AttributeValue>.

§Usage

use dynamodel::Dynamodel;

// Using `Dynamodel` macro, you can implement both
// `From<your struct> for HashMap<String, AttributeValue>` and
// `TryFrom<HashMap<String, AttributeValue>> for your struct` traits.
#[derive(Dynamodel, Debug, Clone, PartialEq)]
struct Person {
    first_name: String,
    last_name: String,
    age: u8,
}

let person = Person {
    first_name: "Kanji".into(),
    last_name: "Tanaka".into(),
    age: 23,
};

let item: HashMap<String, AttributeValue> = [
    ("first_name".to_string(), AttributeValue::S("Kanji".into())),
    ("last_name".to_string(), AttributeValue::S("Tanaka".into())),
    ("age".to_string(), AttributeValue::N("23".into()))
].into();

// Convert from Person into HashMap<String, AttributeValue>.
let converted: HashMap<String, AttributeValue> = person.clone().into();
assert_eq!(converted, item);

// Convert from HashMap<String, AttributeValue> into Person.
// This conversion uses std::convert::TryFrom trait, so this returns a Result.
let converted: Person = item.try_into().unwrap();
assert_eq!(converted, person);

§Implicit conversion

This macro converts some types implicitly so you don’t have to add any code. These types are followings.

TypeAttributeValue variantmemo
StringAttributeValue::S
u8, u16, u32, u64, u128, usize
i8, i16, i32, i64, i128, isize
f32, f64
AttributeValue::N
boolAttributeValue::Bool
Any struct that implements Dynamodel macroAttributeValue::M
Vec<inner type>AttributeValue::LThe inner type must be one of the implicit conversion types.
Option<inner type>Depends on the inner typeThe inner type must be one of the implicit conversion types.

§Explicit conversion

Using field attribute, you can implement original conversion methods for any types like this.

use dynamodel::{Dynamodel, ConvertError};

// Vec<u8> is converted to AttributeValue::L by default,
// but this case, the `data` field is converted to AttributeValue::B.
#[derive(Dynamodel)]
struct BinaryData {
    #[dynamodel(into = "to_blob", try_from = "from_blob")]
    data: Vec<u8>
}

fn to_blob(value: Vec<u8>) -> AttributeValue {
    AttributeValue::B(Blob::new(value))
}

fn from_blob(value: &AttributeValue) -> Result<Vec<u8>, ConvertError> {
    value.as_b()
        .map(|b| b.clone().into_inner())
        .map_err(|err| ConvertError::AttributeValueUnmatched("B".to_string(), err.clone()))
}

The function definition must satisfy these conditions.

ConversionArgumentReturn
field type => AttributeValuefield typeAttributeValue
AttributeValue => field type&AttributeValueResult<field type,ConvertError>

§Rename HashMap key

Like serde crate, you can rename HashMap key from your struct field name.

§Container attribute rename_all

The allowed values for rename_all attribute are UPPERCASE, PascalCase, camelCase, SCREAMING_SNAKE_CASE, kebab-case and SCREAMING-KEBAB-CASE.

use dynamodel::Dynamodel;

// Use `rename_all` as container attribute.
#[derive(Dynamodel, Debug, Clone, PartialEq)]
#[dynamodel(rename_all = "PascalCase")]
struct Person {
    first_name: String,
    last_name: String,
}

let person = Person {
    first_name: "Kanji".into(),
    last_name: "Tanaka".into(),
};

let item: HashMap<String, AttributeValue> = [
    ("FirstName".to_string(), AttributeValue::S("Kanji".into())),
    ("LastName".to_string(), AttributeValue::S("Tanaka".into())),
].into();

let converted: HashMap<String, AttributeValue> = person.clone().into();
assert_eq!(converted, item);

let converted: Person = item.try_into().unwrap();
assert_eq!(converted, person);

§Field attribute rename

You can also rename key using field attribute rename.

use dynamodel::Dynamodel;

#[derive(Dynamodel, Debug, Clone, PartialEq)]
struct Person {
    // Use `rename` as field attribute.
    #[dynamodel(rename = "GivenName")]
    first_name: String,
    #[dynamodel(rename = "FamilyName")]
    last_name: String,
}

let person = Person {
    first_name: "Kanji".into(),
    last_name: "Tanaka".into(),
};

let item: HashMap<String, AttributeValue> = [
    ("GivenName".to_string(), AttributeValue::S("Kanji".into())),
    ("FamilyName".to_string(), AttributeValue::S("Tanaka".into())),
].into();

let converted: HashMap<String, AttributeValue> = person.clone().into();
assert_eq!(converted, item);

let converted: Person = item.try_into().unwrap();
assert_eq!(converted, person);

§Single-table design

When you design your struct according to the single-table design, you want separate data storing in the struct and its talbe key.

For example, the following diagram shows both Video and VideoStats are stored in the same table.

Videos table

Using container attribute table_key, you can implement this structure. The table_key value must be a path for function whose argument is a reference of the struct’s instance and its return type is HashMap<String, AttributeValue>.

use dynamodel::Dynamodel;

#[derive(Dynamodel, Debug, Clone, PartialEq)]
#[dynamodel(table_key = "VideoStats::key", rename_all = "PascalCase")]
struct VideoStats {
    id: String,
    view_count: u64,
}

impl VideoStats {
    fn key(&self) -> HashMap<String, AttributeValue> {
        [
            ("PK".to_string(), AttributeValue::S(self.id.clone())),
            ("SK".to_string(), AttributeValue::S("VideoStats".into())),
        ].into()
    }
}

let stats = VideoStats {
    id: "7cf27a02".into(),
    view_count: 147,
};

let item: HashMap<String, AttributeValue> = [
    ("PK".to_string(), AttributeValue::S("7cf27a02".into())),
    ("SK".to_string(), AttributeValue::S("VideoStats".into())),
    ("Id".to_string(), AttributeValue::S("7cf27a02".into())),
    ("ViewCount".to_string(), AttributeValue::N("147".into())),
].into();

let converted: HashMap<String, AttributeValue> = stats.into();
assert_eq!(converted, item);

Enums§

Derive Macros§