Crate dynomite[][src]

Expand description

Dynomite is the set of high-level interfaces making interacting with AWS DynamoDB more productive.

💡To learn more about DynamoDB, see this helpful guide.

Data Modeling

Dynomite adapts Rust’s native types to DynamoDB’s core components to form a coherent interface.

The Attribute type provides conversion interfaces to and from Rust’s native scalar types which represent DynamoDB’s notion of “attributes”. The goal of this type is to make representing AWS typed values feel more natural and ergonomic in Rust. Where a conversion is not available you can implement Attribute for your own types to leverage higher level functionality.

The Item trait provides conversion interfaces for complex types which represent DynamoDB’s notion of “items”.

💡 A cargo feature named "derive" makes it easy to derive Item instances for your custom types. This feature is enabled by default.

 use dynomite::{Item, Attributes};
 use uuid::Uuid;

#[derive(Item)]
struct Order {
  #[dynomite(partition_key)]
  user: Uuid,
  #[dynomite(sort_key)]
  order_id: Uuid,
  color: Option<String>,
}

Attributes

#[derive(Item)]

Used to define a top-level DynamoDB item. Generates a <ItemName>Key struct with only partition_key/sort_key fields to be used for type-safe primary key construction. This automatically derives Attributes too.

For the Order struct from the example higher this will generate an OrderKey struct like this:

#[derive(Attributes)]
struct OrderKey {
    user: Uuid,
    order_id: Uuid,
}

Use it to safely and conveniently construct the primary key:

use dynomite::{
    dynamodb::{DynamoDb, GetItemInput},
    Attributes, FromAttributes,
};
use std::{convert::TryFrom, error::Error};
use uuid::Uuid;

async fn get_order(
    client: impl DynamoDb,
    user: Uuid,
    order_id: Uuid,
) -> Result<Option<Order>, Box<dyn Error>> {
    // Use the generated `OrderKey` struct to create a primary key
    let key = OrderKey { user, order_id };
    // Convert stronly-typed `OrderKey` to a map of `rusoto_dynamodb::AttributeValue`
    let key: Attributes = key.into();

    let result = client
        .get_item(GetItemInput {
            table_name: "orders".into(),
            key,
            ..Default::default()
        })
        .await?;

    Ok(result
        .item
        .map(|item| Order::try_from(item).expect("Invalid order, db corruption?")))
}
  • #[dynomite(partition_key)] - required attribute, expected to be applied the target partition attribute field with a derivable DynamoDB attribute value of String, Number or Binary

  • #[dynomite(sort_key)] - optional attribute, may be applied to one target sort attribute field with an derivable DynamoDB attribute value of String, Number or Binary

  • All other attributes are the same as for #[derive(Attributes)]

#[derive(Attributes)]

Used to derive an implementation of From/IntoAttributes trait to allow for serializing/deserializing map-like types into AttributeValue. This also generates TryFrom<Attributes> and Into<Attributes> implementations.

  • #[dynomite(rename = "actualName")] - optional attribute, may be applied to any item attribute field, useful when the DynamoDB table you’re interfacing with has attributes whose names don’t following Rust’s naming conventions

  • #[dynomite(skip_serializing_if = "expr_that_returns_function")] - place this on a field that should be skipped in the output map entirely if the given function returns true. The value of this attribute must be a path to a function that satisfies the signature FnOnce(&T) -> bool, where T is the field type (possibly after some auto-deref coertions).

    This is is inspired by #[serde(skip_serializing_if = "...")].

    This attribute may be used to skip serializing the empty set for example (which is not supported by current DynamoDB version, but it may be in future).

    use dynomite::Attributes;
    use std::collections::HashSet;
    
    #[derive(Attributes)]
    struct UniqueStrings {
        #[dynomite(skip_serializing_if = "HashSet::is_empty")]
        strings: HashSet<String>,
    
        #[dynomite(skip_serializing_if = "is_99")]
        skip_if_99: u32,
    }
    
    fn is_99(&num: &u32) -> bool {
        num == 99
    }
  • #[dynomite(default)] - use Default::default implementation of the field type if the attribute is absent when deserializing from Attributes

    use dynomite::Attributes;
    
    #[derive(Attributes)]
    struct Todos {
        // use Default value of the field if it is absent in DynamoDb (empty vector)
        #[dynomite(default)]
        items: Vec<String>,
        list_name: String,
    }
  • #[dynomite(flatten)] - flattens the fields of other struct that also derives Attributes into the current struct.

    💡 If this attribute is placed onto a field, no other dynomite attributes are alowed on this field (this restriction may be relaxed in future).

    This is reminiscent of #[serde(flatten)]. The order of declaration of flattened fields matters, if the struct has to fields with #[dynomite(flatten)] attribute the one that appears higher in code will be evaluated before the other one. This is crucial when you want to collect additional properties into a map:

    use dynomite::{Attributes, Item};
    
    #[derive(Item)]
    struct ShoppingCart {
        #[dynomite(partition_key)]
        id: String,
        // A separate struct to store data without any id
        #[dynomite(flatten)]
        data: ShoppingCartData,
        // Collect all other additional attributes into a map
        // Beware that the order of declaration will affect the order of
        // evaluation, so this "wildcard" flatten clause should be the last member
        #[dynomite(flatten)]
        remaining_props: Attributes,
    }
    
    // `Attributes` doesn't require neither of #[dynomite(partition_key/sort_key)]
    #[derive(Attributes)]
    struct ShoppingCartData {
        name: String,
        total_price: u32,
    }

Fat enums

Fat enums are naturally supported by #[derive(Attribute)]. As for now, there is a limitation that the members of the enum must be either unit or one-element tuple variants. This restriction will be relaxed in future versions of dynomite.

Deriving Attributes on fat enums currently uses internally tagged enum pattern (inspired by serde). Thus, you have to explicitly specify the field name of enum tag via the tag attribute on an enum.

For example, the following definition:

use dynomite::Attributes;

#[derive(Attributes)]
// Name of the field where to store the discriminant in DynamoDB
#[dynomite(tag = "kind")]
enum Shape {
    Rectangle(Rectangle),
    // Use `rename` to change the **value** of the tag for a particular variant
    // by default the tag for a particular variant is the name of the variant verbatim
    #[dynomite(rename = "my_circle")]
    Circle(Circle),
    Unknown,
}

#[derive(Attributes)]
struct Circle {
    radius: u32,
}

#[derive(Attributes)]
struct Rectangle {
    width: u32,
    height: u32,
}

corresponds to the following representation in DynamoDB for each enum variant:

  • Rectangle:
    {
        "kind": "Rectangle",
        "width": 42,
        "height": 64
    }
    
  • Circle:
    {
        "kind": "my_circle",
        "radius": 54
    }
    
  • Unknown:
    {
        "kind": "Unknown"
    }
    

If you have a plain old enum (without any data fields), you should use #[derive(Attribute)] instead.

#[derive(Attribute)]

Derives an implementation of Attribute for the plain enum. If you want to use a fat enum see this paragraph instead.

The enum istelf will be represented as a string with the name of the variant it represents. In contrast, having #[derive(Attributes)] on an enum makes it to be represented as an object with a tag field, which implies an additional layer of indirection.

use dynomite::{Attribute, Item};

#[derive(Attribute)]
enum UserRole {
    Admin,
    Moderator,
    Regular,
}

#[derive(Item)]
struct User {
    #[dynomite(partition_key)]
    id: String,
    role: UserRole,
}

This data model will have the following representation in DynamoDB:

{
    "id": "d97de525-c81d-46d4-b945-d01b3a0f9165",
    "role": "Admin"
}

role field here may be any of Admin, Moderator, or Regular strings.

Rusoto extensions

By importing the dynomite::DynamoDbExt trait, dynomite adds client interfaces for creating async Stream-based auto pagination interfaces.

Robust retries

By importing the dynomite::Retries trait, dynomite provides an interface for adding configuration retry policies so your rusoto DynamoDb clients.

Errors

Some operations which require coercion from AWS to Rust types may fail which results in an AttributeError.

Cargo Features

This crate has a few cargo features of note.

uuid

Enabled by default, the uuid feature adds support for implementing Attribute for the uuid crate’s type Uuid, a useful type for producing and representing unique identifiers for items that satisfy effective characteristics for partition keys

chrono

Enabled by default, the chrono feature adds an implementation of Attribute for the std’s SystemTime and chrono DateTime types which internally use rfc3339 timestamps.

derive

Enabled by default, the derive feature enables the use of the dynomite derive feature which allows you to simply add #[derive(Item)] to your structs.

rustls

Disabled by default, the rustls feature overrides Rusoto’s default tls dependency on OpenSSL, replacing it with a rustls based tls implementation. When you enable this feature. It will also enable uuid and derive by default.

To disable any of these features

[dependencies.dynomite]
version = "xxx"
default-features = false
features = ["feature-you-want"]

Re-exports

pub use rusoto_dynamodb as dynamodb;
pub use crate::retry::Retries;
pub use crate::error::AttributeError;

Modules

error

Dynomite error types

retry

Retry functionality

Macros

attr_map

Creates a HashMap<String, AttributeValue> from a list of key-value pairs

Traits

Attribute

A type capable of being converted into an or from and AWS AttributeValue

DynamoDbExt

Extension methods for DynamoDb client types

FromAttributes

A type capable of being produced from a set of string keys and AttributeValues. Generally, you should not implement this trait manually. Use #[derive(Attributes/Item)] to generate the proper implementation instead.

IntoAttributes

A type capable of being serialized into a set of string keys and AttributeValues Generally, you should not implement this trait manually. Use #[derive(Attributes/Item)] to generate the proper implementation instead.

Item

A type which can be converted to and from a set of String keys and AttributeValues.

Type Definitions

Attributes

Type alias for map of named attribute values