Skip to main content

dynamodb_facade/schema/
attribute_list.rs

1use std::collections::HashMap;
2
3use super::{AttributeDefinition, HasAttribute, HasTableKeyAttributes, TableDefinition};
4use crate::{AttributeValue, IntoAttributeValue};
5
6mod sealed_traits {
7    use super::*;
8    /// Seals [`AttributeList`] and provides the recursive attribute-collection machinery.
9    pub trait AttributeListSeal<TD: TableDefinition, T: HasTableKeyAttributes<TD>> {
10        const ATTRIBUTE_LIST_LEN: usize;
11        fn enrich(source: &T, item: &mut HashMap<String, AttributeValue>);
12    }
13}
14
15/// A sealed, recursive tuple-list of additional DynamoDB attributes for an item.
16///
17/// This trait is sealed and automatically implemented for right-nested tuples of
18/// [`AttributeDefinition`] types — for example `(ItemType, (Expiration, ()))`.
19/// It is the type-level representation of
20/// [`DynamoDBItem::AdditionalAttributes`](crate::DynamoDBItem::AdditionalAttributes):
21/// the set of attributes that are written to DynamoDB in addition to the
22/// primary key attributes and the serialization of the underlying type.
23///
24/// The [`attr_list!`](crate::attr_list) macro builds these nested tuple types
25/// conveniently: `attr_list![ItemType, Expiration]` expands to
26/// `(ItemType, (Expiration, ()))`.
27///
28/// # Examples
29///
30/// ```
31/// # use dynamodb_facade::test_fixtures::*;
32/// use dynamodb_facade::{attr_list, AttributeList, DynamoDBItem};
33///
34/// // The AdditionalAttributes type for PlatformConfig is (ItemType, ()).
35/// type ConfigAttrs = <PlatformConfig as DynamoDBItem<PlatformTable>>::AdditionalAttributes;
36///
37/// // AttributeList::get_attributes collects all additional attributes into a HashMap.
38/// let config = sample_config();
39/// let attrs = <ConfigAttrs as AttributeList<PlatformTable, PlatformConfig>>::get_attributes(&config);
40/// // Only 1 additional attribute in this case
41/// assert_eq!(attrs.len(), 1);
42/// // It is "_TYPE" and its value for PlatformConfig is "PLATFORM_CONFIG"
43/// assert!(attrs.get("_TYPE").is_some_and(|v| v.as_s().unwrap() == "PLATFORM_CONFIG"));
44/// ```
45pub trait AttributeList<TD: TableDefinition, T: HasTableKeyAttributes<TD>>:
46    sealed_traits::AttributeListSeal<TD, T>
47{
48    /// Collects all attributes in this list from `source` into a
49    /// `HashMap<String, AttributeValue>`.
50    fn get_attributes(source: &T) -> HashMap<String, AttributeValue>;
51}
52
53// Base case: empty list
54impl<TD: TableDefinition, T: HasTableKeyAttributes<TD>> sealed_traits::AttributeListSeal<TD, T>
55    for ()
56{
57    const ATTRIBUTE_LIST_LEN: usize = 0;
58    fn enrich(_source: &T, _item: &mut HashMap<String, AttributeValue>) {}
59}
60
61// Recursive case: head + tail
62impl<TD, A, Rest, T> sealed_traits::AttributeListSeal<TD, T> for (A, Rest)
63where
64    TD: TableDefinition,
65    A: AttributeDefinition,
66    T: HasTableKeyAttributes<TD> + HasAttribute<A>,
67    Rest: sealed_traits::AttributeListSeal<TD, T>,
68{
69    const ATTRIBUTE_LIST_LEN: usize = Rest::ATTRIBUTE_LIST_LEN + 1;
70
71    fn enrich(source: &T, item: &mut HashMap<String, AttributeValue>) {
72        item.insert(
73            A::NAME.to_owned(),
74            <T as HasAttribute<A>>::attribute(source).into_attribute_value(),
75        );
76        Rest::enrich(source, item);
77    }
78}
79impl<TD, T, AL> AttributeList<TD, T> for AL
80where
81    TD: TableDefinition,
82    T: HasTableKeyAttributes<TD>,
83    AL: sealed_traits::AttributeListSeal<TD, T>,
84{
85    fn get_attributes(source: &T) -> HashMap<String, AttributeValue> {
86        let mut attributes = HashMap::with_capacity(AL::ATTRIBUTE_LIST_LEN);
87        Self::enrich(source, &mut attributes);
88        attributes
89    }
90}