Skip to main content

Crate dynamodb_facade

Crate dynamodb_facade 

Source
Expand description

A typed facade over aws-sdk-dynamodb with composable expression builders and typestate operation builders.

dynamodb-facade eliminates the boilerplate of raw DynamoDB calls — manual key maps, expression strings, placeholder tracking, pagination loops, and batch-write chunking — while enforcing correct usage at compile time through Rust’s type system.

§Key Concepts

§Tables, items, and the TD parameter

The DynamoDBItem<TD> trait wires a Rust struct to a TableDefinition, declaring how its fields map to DynamoDB key attributes. The blanket traits DynamoDBItemOp<TD>, DynamoDBItemBatchOp<TD>, and DynamoDBItemTransactOp<TD> are automatically implemented for every type that implements DynamoDBItem, providing get, put, delete, update, query, scan, batch_put, batch_delete, transact_put, and friends as associated functions.

TD is deliberately a generic type parameter, not an associated type. A single Rust struct can implement DynamoDBItem for multiple tables, which is useful when:

  • Multiple tables share domain types — for example, a User struct that exists in both a primary table and an archive table, possibly with different key mappings.
  • Migration logic — reading items from one table and writing them to another, for one-shot migrations, compaction, or aggregation across tables.

§Mono-table (single-table) design

The crate has first-class support for the single-table pattern, where all entity types share one DynamoDB table with a composite PK + SK key and a type discriminator attribute (e.g. _TYPE). This is a natural fit because the trait system already enforces per-entity key mappings, type discriminators, and serialization — but it is not the only layout the crate supports.

§Schema definitions

Attributes, tables, and indexes are declared as zero-sized types using the attribute_definitions!, table_definitions!, and index_definitions! macros. These types serve as compile-time tokens that the library uses to build correct key maps and expression attribute name/value maps without any runtime string manipulation by the caller. They also encode key schema shape into the type system — for instance, attempting to supply a sort key for a table declared with a partition key only is a compile-time error.

§Expression builders

Condition<'a> and Update<'a> are composable value types that build DynamoDB condition and update expressions. They support the full DynamoDB expression language — comparisons, begins_with, contains, between, IN, size, if_not_exists, list_append, set ADD/DELETE — and compose with &, |, ! operators and .and() / .combine() methods. All placeholder names and values are managed internally; callers never touch #name or :value strings.

§Typestate operation builders

Every operation builder (GetItemRequest, PutItemRequest, DeleteItemRequest, UpdateItemRequest, QueryRequest, ScanRequest) uses compile-time typestate parameters to enforce correct usage:

  • OutputFormat (Typed / Raw) — whether the terminal method deserializes into T or returns Item<TD>.
  • ReturnValue (ReturnNothing / Return<Old> / Return<New>) — whether put/delete/update return item attributes.
  • Expression-set state (NoCondition / AlreadyHasCondition, etc.) — calling .condition() or .filter() twice is a compile-time error.

§Quick Start

Define the schema, wire a struct, then perform CRUD operations:

use dynamodb_facade::{
    attribute_definitions, table_definitions, index_definitions, dynamodb_item,
    Condition, Update, KeyId, DynamoDBItemOp, DynamoDBError, Error,
    StringAttribute, NumberAttribute, HasAttribute
};
use serde::{Deserialize, Serialize};

// 1. Declare attribute zero-sized types.
attribute_definitions! {
    PK { "PK": StringAttribute }
    SK { "SK": StringAttribute }
    ItemType { "_TYPE": StringAttribute }
    Email { "email": StringAttribute }
}

// 2. Declare the table.
table_definitions! {
    PlatformTable {
        type PartitionKey = PK;
        type SortKey = SK;
        fn table_name() -> String {
            std::env::var("TABLE_NAME").expect("TABLE_NAME must be set")
        }
    }
}

// 3. Define an item type and wire it to the table.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: String,
    pub name: String,
    pub email: String,
}

dynamodb_item! {
    #[table = PlatformTable]
    User {
        #[partition_key]
        PK {
            fn attribute_id(&self) -> &'id str { &self.id }
            fn attribute_value(id) -> String { format!("USER#{id}") }
        }
        #[sort_key]
        SK { const VALUE: &'static str = "PROFILE"; }
        ItemType { const VALUE: &'static str = "USER"; }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Enrollment {
    pub user_id: String,
    pub course_id: String,
    pub enrolled_at: u64,
    pub progress: f64,
}

dynamodb_item! {
    #[table = PlatformTable]
    Enrollment {
        #[partition_key]
        PK {
            fn attribute_id(&self) -> <User as HasAttribute<PK>>::Id<'id> {
                &self.user_id
            }
            fn attribute_value(id) -> <User as HasAttribute<PK>>::Value {
                <User as HasAttribute<PK>>::attribute_value(id)
            }
        }
        #[sort_key]
        SK {
            fn attribute_id(&self) -> &'id str { &self.course_id }
            fn attribute_value(id) -> String { format!("ENROLL#{id}") }
        }
        ItemType { const VALUE: &'static str = "ENROLLMENT"; }
    }
}

// 4. CRUD — no boilerplate.
let user = User {
    id: "u-1".to_owned(),
    name: "Alice".to_owned(),
    email: "alice@example.com".to_owned(),
};

// Put (create or overwrite):
user.put(client).await?;

// Put with create-only guard:
user.put(client).not_exists().await?;

// Get by ID:
let loaded /* : Option<User> */ = User::get(client, KeyId::pk("u-1")).await?;

// Update with condition:
User::update_by_id(
    client,
    KeyId::pk("u-1"),
    Update::set("name", "Alicia"),
)
.exists()
.await?;

// Delete:
User::delete_by_id(client, KeyId::pk("u-1")).await?;

§Feature Highlights

§Composable conditions

// Attribute-level existence checks:
let c = Condition::exists("email") & Condition::not_exists("deleted_at");

// Item-level existence (uses the table's PK attribute):
let c = User::exists() & Condition::eq("role", "student");

// OR / NOT:
let c = User::not_exists() | Condition::lt("expiration_timestamp", 9999999999u64);
let c = !Condition::eq("status", "archived");

// Variadic AND over a collection:
let c = Condition::and([
    Condition::eq("role", "instructor"),
    Condition::size_gt("bio", 0),
    Condition::exists("verified_at"),
]);

§Composable updates

// Simple set / remove:
let u = Update::set("name", "Alice").and(Update::remove("legacy_field"));

// Atomic counters:
let u = Update::increment("login_count", 1);
let u = Update::init_increment("enrollment_count", 0, 1); // if_not_exists + increment

// Merge optional updates from an iterator:
let new_name: Option<&str> = Some("Alice");
let new_role: Option<&str> = None;
let u = Update::combine(
    [
        new_name.map(|n| Update::set("name", n)),
        new_role.map(|r| Update::set("role", r)),
    ]
    .into_iter()
    .flatten(),
);

§Query and scan with automatic pagination

// Query all enrollments for a user (auto-paginates):
let enrollments: Vec<Enrollment> =
    Enrollment::query(client, Enrollment::key_condition("user-1"))
        .all()
        .await?;

// Query a GSI:
let users: Vec<User> =
    User::query_index::<EmailIndex>(client, KeyCondition::pk("alice@example.com"))
        .all()
        .await?;

// Scan with a filter (note: from a pure DynamoDB stand point you should never do that):
let instructors: Vec<User> = User::scan(client)
    .filter(Condition::eq("role", "instructor"))
    .all()
    .await?;

§Batch writes

// Automatically chunks into 25-item batches, runs in parallel,
// and retries unprocessed items:
let requests: Vec<_> = enrollments.iter().map(|e| e.batch_put()).collect();
dynamodb_batch_write::<PlatformTable>(client, requests).await?;

§Transactions

// Atomically create an enrollment and increment the user's enrollment count:
client
    .transact_write_items()
    .transact_items(enrollment.transact_put().not_exists().build())
    .transact_items(
        User::transact_update_by_id(
            KeyId::pk("user-1"),
            Update::init_increment("enrollment_count", 0, 1),
        )
        .condition(
            User::exists()
                & Condition::lt("enrollment_count", 10u32),
        )
        .build(),
    )
    .send()
    .await?;

§Logical Module Organization

All items are re-exported from the crate root. The internal modules are:

§Error Handling

All fallible operations return Result<T> (an alias for core::result::Result<T, Error>). The Error enum has five variants:

let user = sample_user();

// Override an existing item and retrieve the previous version.
// `.exists()` adds a condition that fails if the item is not already present.
match user.put(client).exists().return_old().await {
    Ok(Some(old)) => { /* found old value */ }
    Ok(None) => { unreachable!("condition fail if nothing to return") }
    Err(err)
        if matches!(
            err.as_dynamodb_error(),
            Some(DynamoDBError::ConditionalCheckFailedException(_))
        ) =>
    {
        println!("item did not exist yet — nothing was overwritten");
    }
    Err(err) => return Err(err),
}

§Feature Flags

  • test-fixtures — exposes the test_fixtures module outside of cfg(test) and cfg(doc). Useful for integration test crates that want to reuse the domain types defined there.
  • integration — gates integration tests that require a running DynamoDB Local instance (via testcontainers). Not needed for normal library use.

Re-exports§

pub use aws_sdk_dynamodb;

Modules§

test_fixtures
Shared domain types for doc examples and integration tests.

Macros§

attr_list
Builds the nested tuple type used to represent a list of AttributeDefinition types.
attribute_definitions
Declares one or more DynamoDB attribute definitions as zero-sized types.
dynamodb_item
Wires a Rust struct to a DynamoDB table by implementing DynamoDBItem and the attribute traits.
has_attributes
Manually implements HasAttribute or HasConstAttribute for a type.
index_definitions
Defines one or more DynamoDB Secondary Index (LSI or GSI) zero-sized types implementing IndexDefinition.
key_schema
Defines a key schema struct implementing KeySchema.
table_definitions
Defines one or more DynamoDB table zero-sized types implementing TableDefinition.

Structs§

AsNumber
A generic newtype wrapper that converts any T: Into<String> directly to a DynamoDB N (number) attribute value without parsing.
AsSet
A newtype wrapper around Vec<T> that serializes as a DynamoDB Set type (SS, NS, or BS) instead of a List (L).
BinaryAttribute
Marker type for DynamoDB Binary (B) attributes.
Client
Client for Amazon DynamoDB
CompositeKey
Marker type indicating a key schema with both a partition key and a sort key.
Condition
Composable DynamoDB condition expression builder.
DeleteItemRequest
Builder for a DynamoDB DeleteItem request.
GetItemRequest
Builder for a DynamoDB GetItem request.
Item
A type-safe wrapper around a DynamoDB item for a specific table.
Key
A type-safe wrapper for a DynamoDB key belonging to a specific table.
KeyCondition
Builder for DynamoDB key condition expressions.
KeyId
Type-safe builder for a DynamoDB partition key + sort key pair.
NoId
Zero-sized placeholder used when a key component is a compile-time constant.
NumberAttribute
Marker type for DynamoDB Number (N) attributes.
Projection
Projection expression builder that automatically includes the table’s key attributes.
PutItemRequest
Builder for a DynamoDB PutItem request.
QueryRequest
Builder for a DynamoDB Query request.
ScanRequest
Builder for a DynamoDB Scan request.
SimpleKey
Marker type indicating a key schema with a partition key only (no sort key).
StringAttribute
Marker type for DynamoDB String (S) attributes.
TransactConditionCheckRequest
Builder for a ConditionCheck operation inside a DynamoDB transaction.
TransactDeleteRequest
Builder for a Delete operation inside a DynamoDB transaction.
TransactPutRequest
Builder for a Put operation inside a DynamoDB transaction.
TransactUpdateRequest
Builder for an Update operation inside a DynamoDB transaction.
Update
Composable DynamoDB update expression builder.
UpdateItemRequest
Builder for a DynamoDB UpdateItem request.
UpdateSetRhs
Advanced right-hand-side expression builder for DynamoDB SET actions.

Enums§

AttributeValue

Represents the data for an attribute.

Each attribute value is described as a name-value pair. The name is the data type, and the value is the data itself.

For more information, see Data Types in the Amazon DynamoDB Developer Guide.

Comparison
Comparison operators for DynamoDB condition and filter expressions.
DynamoDBError
All possible error types for this service.
Error
The error type for all dynamodb-facade operations.

Traits§

AttributeDefinition
Defines the name and type of a single DynamoDB attribute at the type level.
AttributeList
A sealed, recursive tuple-list of additional DynamoDB attributes for an item.
AttributeType
Sealed marker trait for DynamoDB attribute types.
CompositeKeySchema
A KeySchema with both a partition key and a sort key.
DynamoDBItem
Core trait for types stored in a DynamoDB table.
DynamoDBItemBatchOp
Entry points for building DynamoDB BatchWriteItem write requests.
DynamoDBItemOp
Primary entry point for typed single-item and collection CRUD operations.
DynamoDBItemTransactOp
Entry points for building DynamoDB TransactWriteItems operations.
HasAttribute
Links an item type to a dynamic DynamoDB attribute.
HasConstAttribute
Links an item type to a compile-time constant DynamoDB attribute value.
IndexDefinition
Defines a DynamoDB Global Secondary Index (GSI) or Local Secondary Index (GSI) on a specific table.
IntoAttributeValue
Converts a Rust value into a DynamoDB AttributeValue.
IntoTypedAttributeValue
A type-safe variant of IntoAttributeValue that guarantees the produced AttributeValue matches a specific DynamoDB scalar type.
KeyBuilder
Builds DynamoDB keys from type-safe key IDs.
KeyConditionState
Sealed typestate marker for KeyCondition build stages.
KeySchema
Defines the key schema for a DynamoDB table or index.
KeySchemaKind
Sealed marker trait for key schema kinds.
SimpleKeySchema
A KeySchema with only a partition key (no sort key).
TableDefinition
Defines a DynamoDB table: its name and key schema.
ValidKeySchema
Ensures that a KeySchema implementation is internally consistent.

Functions§

batch_delete
Creates a DeleteRequest WriteRequest from a raw Key.
batch_put
Creates a PutRequest WriteRequest from a raw Item.
dynamodb_batch_write
Executes a batch of WriteRequests against a DynamoDB table.
dynamodb_execute_query
Executes a DynamoDB Query request, collecting all pages into a Vec.
dynamodb_execute_scan
Executes a DynamoDB Scan request, collecting all pages into a Vec.
dynamodb_stream_query
Creates a lazy async Stream of query results with automatic pagination.
dynamodb_stream_scan
Creates a lazy async Stream of scan results with automatic pagination.
to_attribute_value
Converts a serde::Serialize value into a DynamoDB AttributeValue using serde_dynamo.
try_to_attribute_value
Converts a serde::Serialize value into a DynamoDB AttributeValue using serde_dynamo, returning a Result on failure.

Type Aliases§

IndexSchema
Convenience alias for the KeySchema of an IndexDefinition.
Result
A specialized Result type for this crate.
TableSchema
Convenience alias for the KeySchema of a TableDefinition.