Skip to main content

dynoxide/
macros.rs

1/// Trait for inserting values into an item map, with `Option<T>` support.
2///
3/// When the value is `None`, the key is omitted from the map.
4/// This is not public API — it exists to support the `item!` macro.
5#[doc(hidden)]
6pub trait ItemInsert {
7    fn __item_insert(
8        self,
9        map: &mut std::collections::HashMap<String, crate::AttributeValue>,
10        key: &str,
11    );
12}
13
14impl<T: Into<crate::AttributeValue>> ItemInsert for T {
15    fn __item_insert(
16        self,
17        map: &mut std::collections::HashMap<String, crate::AttributeValue>,
18        key: &str,
19    ) {
20        map.insert(key.to_string(), self.into());
21    }
22}
23
24impl<T: Into<crate::AttributeValue>> ItemInsert for Option<T> {
25    fn __item_insert(
26        self,
27        map: &mut std::collections::HashMap<String, crate::AttributeValue>,
28        key: &str,
29    ) {
30        if let Some(v) = self {
31            map.insert(key.to_string(), v.into());
32        }
33    }
34}
35
36/// Construct a `HashMap<String, AttributeValue>` from key-value pairs.
37///
38/// Values are converted via `Into<AttributeValue>`. `Option` values that are
39/// `None` are omitted from the map (the key is not inserted).
40///
41/// # Examples
42///
43/// ```
44/// use dynoxide::item;
45/// use dynoxide::AttributeValue;
46///
47/// let item = item! {
48///     "pk" => "user#1",
49///     "age" => 30i64,
50///     "active" => true,
51/// };
52/// assert_eq!(item["pk"], AttributeValue::S("user#1".to_string()));
53/// assert_eq!(item["age"], AttributeValue::N("30".to_string()));
54/// ```
55///
56/// Nested maps:
57/// ```
58/// use dynoxide::item;
59///
60/// let item = item! {
61///     "pk" => "user#1",
62///     "metadata" => {
63///         "count" => 5i64,
64///     },
65/// };
66/// ```
67///
68/// Option handling (None values are omitted):
69/// ```
70/// use dynoxide::item;
71///
72/// let email: Option<&str> = None;
73/// let item = item! {
74///     "pk" => "user#1",
75///     "email" => email,
76/// };
77/// assert!(!item.contains_key("email"));
78/// ```
79#[macro_export]
80macro_rules! item {
81    // Entry point: empty
82    () => {{
83        ::std::collections::HashMap::<String, $crate::AttributeValue>::new()
84    }};
85    // Entry point: key-value pairs (delegates to internal muncher)
86    ( $($rest:tt)+ ) => {{
87        #[allow(unused_mut)]
88        let mut map = ::std::collections::HashMap::<String, $crate::AttributeValue>::new();
89        $crate::__item_internal!(map, $($rest)+);
90        map
91    }};
92}
93
94#[doc(hidden)]
95#[macro_export]
96macro_rules! __item_internal {
97    // Nested map value, more pairs follow
98    ($map:ident, $key:expr => { $($inner:tt)* }, $($rest:tt)+) => {
99        $map.insert($key.to_string(), $crate::AttributeValue::M($crate::item! { $($inner)* }));
100        $crate::__item_internal!($map, $($rest)+);
101    };
102    // Nested map value, last pair
103    ($map:ident, $key:expr => { $($inner:tt)* } $(,)?) => {
104        $map.insert($key.to_string(), $crate::AttributeValue::M($crate::item! { $($inner)* }));
105    };
106    // List value, more pairs follow
107    ($map:ident, $key:expr => [ $($elem:expr),* $(,)? ], $($rest:tt)+) => {
108        $map.insert($key.to_string(), $crate::AttributeValue::L(vec![$($elem),*]));
109        $crate::__item_internal!($map, $($rest)+);
110    };
111    // List value, last pair
112    ($map:ident, $key:expr => [ $($elem:expr),* $(,)? ] $(,)?) => {
113        $map.insert($key.to_string(), $crate::AttributeValue::L(vec![$($elem),*]));
114    };
115    // Expression value, more pairs follow
116    ($map:ident, $key:expr => $val:expr, $($rest:tt)+) => {
117        $crate::ItemInsert::__item_insert($val, &mut $map, $key);
118        $crate::__item_internal!($map, $($rest)+);
119    };
120    // Expression value, last pair
121    ($map:ident, $key:expr => $val:expr $(,)?) => {
122        $crate::ItemInsert::__item_insert($val, &mut $map, $key);
123    };
124}