1use std::{collections::HashMap, fmt};
7
8use serde::{
9 Deserialize, Deserializer, Serialize, Serializer,
10 de::{self, MapAccess, Visitor},
11 ser::SerializeMap,
12};
13
14#[derive(Debug, Clone, PartialEq)]
19pub enum AttributeValue {
20 S(String),
22 N(String),
24 B(bytes::Bytes),
26 Ss(Vec<String>),
28 Ns(Vec<String>),
30 Bs(Vec<bytes::Bytes>),
32 Bool(bool),
34 Null(bool),
36 L(Vec<AttributeValue>),
38 M(HashMap<String, AttributeValue>),
40}
41
42impl AttributeValue {
43 #[must_use]
45 pub fn is_s(&self) -> bool {
46 matches!(self, Self::S(_))
47 }
48
49 #[must_use]
51 pub fn is_n(&self) -> bool {
52 matches!(self, Self::N(_))
53 }
54
55 #[must_use]
57 pub fn is_b(&self) -> bool {
58 matches!(self, Self::B(_))
59 }
60
61 #[must_use]
63 pub fn is_bool(&self) -> bool {
64 matches!(self, Self::Bool(_))
65 }
66
67 #[must_use]
69 pub fn is_null(&self) -> bool {
70 matches!(self, Self::Null(true))
71 }
72
73 #[must_use]
75 pub fn is_l(&self) -> bool {
76 matches!(self, Self::L(_))
77 }
78
79 #[must_use]
81 pub fn is_m(&self) -> bool {
82 matches!(self, Self::M(_))
83 }
84
85 #[must_use]
87 pub fn as_s(&self) -> Option<&str> {
88 match self {
89 Self::S(s) => Some(s),
90 _ => None,
91 }
92 }
93
94 #[must_use]
96 pub fn as_n(&self) -> Option<&str> {
97 match self {
98 Self::N(n) => Some(n),
99 _ => None,
100 }
101 }
102
103 #[must_use]
105 pub fn as_m(&self) -> Option<&HashMap<String, AttributeValue>> {
106 match self {
107 Self::M(m) => Some(m),
108 _ => None,
109 }
110 }
111
112 #[must_use]
114 pub fn as_l(&self) -> Option<&[AttributeValue]> {
115 match self {
116 Self::L(l) => Some(l),
117 _ => None,
118 }
119 }
120
121 #[must_use]
123 pub fn as_bool(&self) -> Option<bool> {
124 match self {
125 Self::Bool(b) => Some(*b),
126 _ => None,
127 }
128 }
129
130 #[must_use]
132 pub fn type_descriptor(&self) -> &'static str {
133 match self {
134 Self::S(_) => "S",
135 Self::N(_) => "N",
136 Self::B(_) => "B",
137 Self::Ss(_) => "SS",
138 Self::Ns(_) => "NS",
139 Self::Bs(_) => "BS",
140 Self::Bool(_) => "BOOL",
141 Self::Null(_) => "NULL",
142 Self::L(_) => "L",
143 Self::M(_) => "M",
144 }
145 }
146}
147
148impl Eq for AttributeValue {}
149
150impl std::hash::Hash for AttributeValue {
151 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
152 core::mem::discriminant(self).hash(state);
153 match self {
154 Self::S(s) => s.hash(state),
155 Self::N(n) => n.hash(state),
156 Self::B(b) => b.hash(state),
157 Self::Bool(b) | Self::Null(b) => b.hash(state),
158 Self::Ss(v) | Self::Ns(v) => v.hash(state),
159 Self::Bs(v) => {
160 for b in v {
161 b.hash(state);
162 }
163 }
164 Self::L(v) => v.hash(state),
165 Self::M(m) => {
166 let mut pairs: Vec<_> = m.iter().collect();
168 pairs.sort_by_key(|(k, _)| *k);
169 for (k, v) in pairs {
170 k.hash(state);
171 v.hash(state);
172 }
173 }
174 }
175 }
176}
177
178impl fmt::Display for AttributeValue {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 match self {
181 Self::S(s) => write!(f, "{{S: {s}}}"),
182 Self::N(n) => write!(f, "{{N: {n}}}"),
183 Self::B(b) => write!(f, "{{B: {} bytes}}", b.len()),
184 Self::Ss(v) => write!(f, "{{SS: {v:?}}}"),
185 Self::Ns(v) => write!(f, "{{NS: {v:?}}}"),
186 Self::Bs(v) => write!(f, "{{BS: {} items}}", v.len()),
187 Self::Bool(b) => write!(f, "{{BOOL: {b}}}"),
188 Self::Null(b) => write!(f, "{{NULL: {b}}}"),
189 Self::L(v) => write!(f, "{{L: {} items}}", v.len()),
190 Self::M(m) => write!(f, "{{M: {} keys}}", m.len()),
191 }
192 }
193}
194
195impl Serialize for AttributeValue {
196 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
197 let mut map = serializer.serialize_map(Some(1))?;
198 match self {
199 Self::S(s) => map.serialize_entry("S", s)?,
200 Self::N(n) => map.serialize_entry("N", n)?,
201 Self::B(b) => {
202 use base64::Engine;
203 let encoded = base64::engine::general_purpose::STANDARD.encode(b);
204 map.serialize_entry("B", &encoded)?;
205 }
206 Self::Ss(v) => map.serialize_entry("SS", v)?,
207 Self::Ns(v) => map.serialize_entry("NS", v)?,
208 Self::Bs(v) => {
209 use base64::Engine;
210 let encoded: Vec<String> = v
211 .iter()
212 .map(|b| base64::engine::general_purpose::STANDARD.encode(b))
213 .collect();
214 map.serialize_entry("BS", &encoded)?;
215 }
216 Self::Bool(b) => map.serialize_entry("BOOL", b)?,
217 Self::Null(b) => map.serialize_entry("NULL", b)?,
218 Self::L(list) => map.serialize_entry("L", list)?,
219 Self::M(m) => map.serialize_entry("M", m)?,
220 }
221 map.end()
222 }
223}
224
225impl<'de> Deserialize<'de> for AttributeValue {
226 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
227 deserializer.deserialize_map(AttributeValueVisitor)
228 }
229}
230
231struct AttributeValueVisitor;
232
233impl<'de> Visitor<'de> for AttributeValueVisitor {
234 type Value = AttributeValue;
235
236 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
237 formatter.write_str("a DynamoDB AttributeValue object with exactly one type key")
238 }
239
240 fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Self::Value, M::Error> {
241 let Some(key) = map.next_key::<String>()? else {
242 return Err(de::Error::custom(
243 "AttributeValue must have exactly one key",
244 ));
245 };
246
247 let value = match key.as_str() {
248 "S" => AttributeValue::S(map.next_value()?),
249 "N" => AttributeValue::N(map.next_value()?),
250 "B" => {
251 use base64::Engine;
252 let encoded: String = map.next_value()?;
253 let decoded = base64::engine::general_purpose::STANDARD
254 .decode(&encoded)
255 .map_err(de::Error::custom)?;
256 AttributeValue::B(bytes::Bytes::from(decoded))
257 }
258 "SS" => AttributeValue::Ss(map.next_value()?),
259 "NS" => AttributeValue::Ns(map.next_value()?),
260 "BS" => {
261 use base64::Engine;
262 let encoded: Vec<String> = map.next_value()?;
263 let decoded: Result<Vec<bytes::Bytes>, _> = encoded
264 .iter()
265 .map(|e| {
266 base64::engine::general_purpose::STANDARD
267 .decode(e)
268 .map(bytes::Bytes::from)
269 })
270 .collect();
271 AttributeValue::Bs(decoded.map_err(de::Error::custom)?)
272 }
273 "BOOL" => AttributeValue::Bool(map.next_value()?),
274 "NULL" => AttributeValue::Null(map.next_value()?),
275 "L" => AttributeValue::L(map.next_value()?),
276 "M" => AttributeValue::M(map.next_value()?),
277 other => {
278 return Err(de::Error::unknown_field(
279 other,
280 &["S", "N", "B", "SS", "NS", "BS", "BOOL", "NULL", "L", "M"],
281 ));
282 }
283 };
284
285 Ok(value)
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn test_should_serialize_string_value() {
295 let val = AttributeValue::S("hello".to_owned());
296 let json = serde_json::to_string(&val).unwrap();
297 assert_eq!(json, r#"{"S":"hello"}"#);
298 }
299
300 #[test]
301 fn test_should_serialize_number_value() {
302 let val = AttributeValue::N("42".to_owned());
303 let json = serde_json::to_string(&val).unwrap();
304 assert_eq!(json, r#"{"N":"42"}"#);
305 }
306
307 #[test]
308 fn test_should_serialize_bool_value() {
309 let val = AttributeValue::Bool(true);
310 let json = serde_json::to_string(&val).unwrap();
311 assert_eq!(json, r#"{"BOOL":true}"#);
312 }
313
314 #[test]
315 fn test_should_serialize_null_value() {
316 let val = AttributeValue::Null(true);
317 let json = serde_json::to_string(&val).unwrap();
318 assert_eq!(json, r#"{"NULL":true}"#);
319 }
320
321 #[test]
322 fn test_should_serialize_list_value() {
323 let val = AttributeValue::L(vec![
324 AttributeValue::S("a".to_owned()),
325 AttributeValue::N("1".to_owned()),
326 ]);
327 let json = serde_json::to_string(&val).unwrap();
328 assert_eq!(json, r#"{"L":[{"S":"a"},{"N":"1"}]}"#);
329 }
330
331 #[test]
332 fn test_should_roundtrip_map_value() {
333 let mut m = HashMap::new();
334 m.insert("key".to_owned(), AttributeValue::S("value".to_owned()));
335 let val = AttributeValue::M(m);
336 let json = serde_json::to_string(&val).unwrap();
337 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
338 assert_eq!(val, deserialized);
339 }
340
341 #[test]
342 fn test_should_roundtrip_binary_value() {
343 let val = AttributeValue::B(bytes::Bytes::from_static(b"test data"));
344 let json = serde_json::to_string(&val).unwrap();
345 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
346 assert_eq!(val, deserialized);
347 }
348
349 #[test]
350 fn test_should_deserialize_number_set() {
351 let json = r#"{"NS":["1","2","3"]}"#;
352 let val: AttributeValue = serde_json::from_str(json).unwrap();
353 assert!(matches!(val, AttributeValue::Ns(ref v) if v.len() == 3));
354 }
355
356 #[test]
357 fn test_should_deserialize_string_set() {
358 let json = r#"{"SS":["a","b"]}"#;
359 let val: AttributeValue = serde_json::from_str(json).unwrap();
360 assert!(matches!(val, AttributeValue::Ss(ref v) if v.len() == 2));
361 }
362}