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}