Skip to main content

dynamodb_expression/value/
map.rs

1use core::{
2    fmt::{self, Write},
3    hash,
4};
5
6use aws_sdk_dynamodb::types::AttributeValue;
7use itermap::IterMap;
8
9use crate::path::Name;
10
11use super::Value;
12
13type MapType<K, V> = std::collections::BTreeMap<K, V>;
14// TODO: Allow this to be configured via feature to switch between HashMap and BTreeMap
15//       Using BTreeMap for stable testing.
16// type MapType<K, V> = std::collections::HashMap<K, V>;
17
18/// Represents a [DynamoDB map][1].
19///
20/// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Document.Map
21#[derive(Clone, Default, PartialEq, Eq)]
22pub struct Map {
23    map: MapType<Name, Value>,
24}
25
26impl Map {
27    /// Creates a value to use as a [DynamoDB map][1].
28    ///
29    /// See also: [`Value::new_map`]
30    ///
31    /// # Examples
32    ///
33    /// ```rust
34    /// # use std::collections::HashMap;
35    /// #
36    /// # use pretty_assertions::assert_eq;
37    /// #
38    /// use dynamodb_expression::value::{Map, Num};
39    ///
40    /// let map = HashMap::from([("a", Num::from(1)), ("b", Num::from(2)), ("c", Num::from(3))]);
41    ///
42    /// assert_eq!(
43    ///     Map::new(map),
44    ///     Map::new([("a", Num::from(1)), ("b", Num::from(2)), ("c", Num::from(3))]),
45    /// );
46    /// ```
47    ///
48    /// [1]: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes.Document.Map
49    pub fn new<T>(map: T) -> Self
50    where
51        T: Into<Map>,
52    {
53        map.into()
54    }
55
56    // Intentionally not using `impl From<ScalarValue> for AttributeValue` because
57    // I don't want to make this a public API people rely on. The purpose of this
58    // crate is not to make creating `AttributeValues` easier. They should try
59    // `serde_dynamo`.
60    pub(super) fn into_attribute_value(self) -> AttributeValue {
61        AttributeValue::M(
62            self.map
63                .into_iter()
64                .map_keys(|name| name.name)
65                .map_values(Value::into_attribute_value)
66                .collect(),
67        )
68    }
69}
70
71impl hash::Hash for Map {
72    fn hash<H>(&self, state: &mut H)
73    where
74        H: hash::Hasher,
75    {
76        self.map.iter().for_each(|(k, v)| {
77            k.hash(state);
78            v.hash(state);
79        })
80    }
81}
82
83impl fmt::Debug for Map {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        f.debug_map().entries(self.map.iter()).finish()
86    }
87}
88
89impl fmt::Display for Map {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.write_char('{')?;
92
93        let mut first = true;
94        self.map.iter().try_for_each(|(k, v)| {
95            if first {
96                first = false;
97            } else {
98                f.write_str(", ")?;
99            }
100
101            k.fmt(f)?;
102            f.write_str(": ")?;
103            v.fmt(f)
104        })?;
105
106        f.write_char('}')
107    }
108}
109
110impl<K, V> FromIterator<(K, V)> for Map
111where
112    K: Into<Name>,
113    V: Into<Value>,
114{
115    fn from_iter<I>(iter: I) -> Self
116    where
117        I: IntoIterator<Item = (K, V)>,
118    {
119        Self {
120            map: iter
121                .into_iter()
122                .map_keys(Into::into)
123                .map_values(Into::into)
124                .collect(),
125        }
126    }
127}
128
129impl<I, K, V> From<I> for Map
130where
131    I: IntoIterator<Item = (K, V)>,
132    K: Into<Name>,
133    V: Into<Value>,
134{
135    fn from(iter: I) -> Self {
136        Self::from_iter(iter)
137    }
138}