1use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
2use serde::de;
3use serde::ser::SerializeMap;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use std::collections::{BTreeSet, HashMap, HashSet};
6use std::fmt;
7
8#[derive(Debug, Clone, PartialEq)]
15pub enum AttributeValue {
16 S(String),
18 N(String),
20 B(Vec<u8>),
22 BOOL(bool),
24 NULL(bool),
26 SS(Vec<String>),
28 NS(Vec<String>),
30 BS(Vec<Vec<u8>>),
32 L(Vec<AttributeValue>),
34 M(HashMap<String, AttributeValue>),
36}
37
38impl AttributeValue {
39 pub fn size(&self) -> usize {
45 match self {
46 AttributeValue::S(s) => s.len(),
47 AttributeValue::N(n) => {
48 let significant = n.chars().filter(|c| c.is_ascii_digit()).count();
50 let significant = significant.max(1);
51 (significant / 2) + 1
52 }
53 AttributeValue::B(b) => b.len(),
54 AttributeValue::BOOL(_) => 1,
55 AttributeValue::NULL(_) => 1,
56 AttributeValue::SS(ss) => ss.iter().map(|s| s.len()).sum(),
57 AttributeValue::NS(ns) => ns
58 .iter()
59 .map(|n| {
60 let significant = n.chars().filter(|c| c.is_ascii_digit()).count().max(1);
61 (significant / 2) + 1
62 })
63 .sum(),
64 AttributeValue::BS(bs) => bs.iter().map(|b| b.len()).sum(),
65 AttributeValue::L(items) => {
66 3 + items.len() + items.iter().map(|v| v.size()).sum::<usize>()
68 }
69 AttributeValue::M(map) => {
70 3 + map
72 .iter()
73 .map(|(k, v)| k.len() + 1 + v.size())
74 .sum::<usize>()
75 }
76 }
77 }
78
79 pub fn type_name(&self) -> &'static str {
81 match self {
82 AttributeValue::S(_) => "S",
83 AttributeValue::N(_) => "N",
84 AttributeValue::B(_) => "B",
85 AttributeValue::BOOL(_) => "BOOL",
86 AttributeValue::NULL(_) => "NULL",
87 AttributeValue::SS(_) => "SS",
88 AttributeValue::NS(_) => "NS",
89 AttributeValue::BS(_) => "BS",
90 AttributeValue::L(_) => "L",
91 AttributeValue::M(_) => "M",
92 }
93 }
94
95 pub fn is_scalar(&self) -> bool {
97 matches!(
98 self,
99 AttributeValue::S(_)
100 | AttributeValue::N(_)
101 | AttributeValue::B(_)
102 | AttributeValue::BOOL(_)
103 | AttributeValue::NULL(_)
104 )
105 }
106
107 pub fn is_set(&self) -> bool {
109 matches!(
110 self,
111 AttributeValue::SS(_) | AttributeValue::NS(_) | AttributeValue::BS(_)
112 )
113 }
114
115 pub fn to_key_string(&self) -> Option<String> {
122 match self {
123 AttributeValue::S(s) => Some(format!("S:{s}")),
124 AttributeValue::N(n) => Some(format!("N:{}", normalize_number_for_sort(n))),
125 AttributeValue::B(b) => Some(format!("B:{}", hex_encode(b))),
126 _ => None, }
128 }
129}
130
131impl fmt::Display for AttributeValue {
132 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133 match self {
134 AttributeValue::S(s) => write!(f, "\"{s}\""),
135 AttributeValue::N(n) => write!(f, "{n}"),
136 AttributeValue::B(b) => write!(f, "<binary {} bytes>", b.len()),
137 AttributeValue::BOOL(b) => write!(f, "{b}"),
138 AttributeValue::NULL(_) => write!(f, "null"),
139 AttributeValue::SS(ss) => write!(f, "{ss:?}"),
140 AttributeValue::NS(ns) => write!(f, "{ns:?}"),
141 AttributeValue::BS(bs) => write!(f, "<binary set {} items>", bs.len()),
142 AttributeValue::L(items) => write!(f, "<list {} items>", items.len()),
143 AttributeValue::M(map) => write!(f, "<map {} keys>", map.len()),
144 }
145 }
146}
147
148impl Serialize for AttributeValue {
153 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
154 where
155 S: Serializer,
156 {
157 let mut map = serializer.serialize_map(Some(1))?;
158 match self {
159 AttributeValue::S(s) => map.serialize_entry("S", s)?,
160 AttributeValue::N(n) => map.serialize_entry("N", n)?,
161 AttributeValue::B(b) => {
162 map.serialize_entry("B", &BASE64.encode(b))?;
163 }
164 AttributeValue::BOOL(b) => map.serialize_entry("BOOL", b)?,
165 AttributeValue::NULL(n) => map.serialize_entry("NULL", n)?,
166 AttributeValue::SS(ss) => map.serialize_entry("SS", ss)?,
167 AttributeValue::NS(ns) => map.serialize_entry("NS", ns)?,
168 AttributeValue::BS(bs) => {
169 let encoded: Vec<String> = bs.iter().map(|b| BASE64.encode(b)).collect();
170 map.serialize_entry("BS", &encoded)?;
171 }
172 AttributeValue::L(items) => map.serialize_entry("L", items)?,
173 AttributeValue::M(m) => map.serialize_entry("M", m)?,
174 }
175 map.end()
176 }
177}
178
179impl<'de> Deserialize<'de> for AttributeValue {
180 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
181 where
182 D: Deserializer<'de>,
183 {
184 let raw = serde_json::Value::deserialize(deserializer)?;
186
187 let obj = raw
188 .as_object()
189 .ok_or_else(|| de::Error::custom("empty AttributeValue object"))?;
190
191 if obj.is_empty() {
192 return Err(de::Error::custom("empty AttributeValue object"));
193 }
194
195 let known_types = ["S", "N", "B", "BOOL", "NULL", "SS", "NS", "BS", "L", "M"];
197 let present: Vec<&str> = obj
198 .keys()
199 .filter(|k| known_types.contains(&k.as_str()))
200 .map(|k| k.as_str())
201 .collect();
202
203 if present.is_empty() {
204 return Err(de::Error::custom(
205 "Supplied AttributeValue is empty, must contain exactly one of the supported datatypes",
206 ));
207 }
208
209 for &type_key in &present {
212 match type_key {
213 "N" => {
214 if let Some(n) = obj.get("N").and_then(|v| v.as_str()) {
215 validate_number_in_deser(n).map_err(de::Error::custom)?;
216 }
217 }
218 "NS" => {
219 if let Some(arr) = obj.get("NS").and_then(|v| v.as_array()) {
220 for item in arr {
221 if let Some(n) = item.as_str() {
222 validate_number_in_deser(n).map_err(de::Error::custom)?;
223 }
224 }
225 }
226 }
227 _ => {}
228 }
229 }
230
231 if present.len() > 1 {
233 return Err(de::Error::custom(
234 "VALIDATION:Supplied AttributeValue has more than one datatypes set, \
235 must contain exactly one of the supported datatypes",
236 ));
237 }
238
239 let type_key = present[0];
240 let val = &obj[type_key];
241
242 match type_key {
243 "S" => {
244 let s = val
245 .as_str()
246 .ok_or_else(|| de::Error::custom("expected string for S"))?;
247 Ok(AttributeValue::S(s.to_string()))
248 }
249 "N" => {
250 let n = val
251 .as_str()
252 .ok_or_else(|| de::Error::custom("expected string for N"))?;
253 Ok(AttributeValue::N(n.to_string()))
254 }
255 "B" => {
256 let encoded = val
257 .as_str()
258 .ok_or_else(|| de::Error::custom("expected string for B"))?;
259 let bytes = BASE64
260 .decode(encoded)
261 .map_err(|e| de::Error::custom(format!("invalid base64: {e}")))?;
262 Ok(AttributeValue::B(bytes))
263 }
264 "BOOL" => {
265 let b = val
266 .as_bool()
267 .ok_or_else(|| de::Error::custom("expected boolean for BOOL"))?;
268 Ok(AttributeValue::BOOL(b))
269 }
270 "NULL" => {
271 if val.as_bool().is_none() {
276 return Err(de::Error::custom(
277 "VALIDATION:One or more parameter values were invalid: \
278 Null attribute value types must have the value of true",
279 ));
280 }
281 Ok(AttributeValue::NULL(true))
282 }
283 "SS" => {
284 let arr = val
285 .as_array()
286 .ok_or_else(|| de::Error::custom("expected array for SS"))?;
287 let ss: Result<Vec<String>, _> = arr
288 .iter()
289 .map(|v| {
290 v.as_str()
291 .map(|s| s.to_string())
292 .ok_or_else(|| de::Error::custom("expected string in SS"))
293 })
294 .collect();
295 Ok(AttributeValue::SS(ss?))
296 }
297 "NS" => {
298 let arr = val
299 .as_array()
300 .ok_or_else(|| de::Error::custom("expected array for NS"))?;
301 let ns: Result<Vec<String>, _> = arr
302 .iter()
303 .map(|v| {
304 v.as_str()
305 .map(|s| s.to_string())
306 .ok_or_else(|| de::Error::custom("expected string in NS"))
307 })
308 .collect();
309 Ok(AttributeValue::NS(ns?))
310 }
311 "BS" => {
312 let arr = val
313 .as_array()
314 .ok_or_else(|| de::Error::custom("expected array for BS"))?;
315 let mut decoded = Vec::with_capacity(arr.len());
316 for item in arr {
317 let encoded = item
318 .as_str()
319 .ok_or_else(|| de::Error::custom("expected string in BS"))?;
320 decoded.push(
321 BASE64
322 .decode(encoded)
323 .map_err(|e| de::Error::custom(format!("invalid base64: {e}")))?,
324 );
325 }
326 Ok(AttributeValue::BS(decoded))
327 }
328 "L" => {
329 let arr = val
330 .as_array()
331 .ok_or_else(|| de::Error::custom("expected array for L"))?;
332 let list: Result<Vec<AttributeValue>, _> = arr
333 .iter()
334 .map(|v| serde_json::from_value(v.clone()).map_err(de::Error::custom))
335 .collect();
336 Ok(AttributeValue::L(list?))
337 }
338 "M" => {
339 let map_val = val
340 .as_object()
341 .ok_or_else(|| de::Error::custom("expected object for M"))?;
342 let mut result = std::collections::HashMap::new();
343 for (k, v) in map_val {
344 let av: AttributeValue =
345 serde_json::from_value(v.clone()).map_err(de::Error::custom)?;
346 result.insert(k.clone(), av);
347 }
348 Ok(AttributeValue::M(result))
349 }
350 _ => unreachable!(),
351 }
352 }
353}
354
355fn validate_number_in_deser(n: &str) -> Result<(), String> {
362 match validate_dynamo_number(n) {
365 Ok(()) => Ok(()),
366 Err(crate::errors::DynoxideError::ValidationException(m)) => Err(format!("VALIDATION:{m}")),
367 Err(e) => Err(format!("VALIDATION:{e}")),
368 }
369}
370
371pub fn normalize_number_for_sort(num_str: &str) -> String {
385 let trimmed = num_str.trim();
386
387 if trimmed.is_empty() || trimmed == "0" || trimmed == "-0" || trimmed == "0.0" {
388 return zero_encoding();
389 }
390
391 let negative = trimmed.starts_with('-');
392 let abs_str = if negative { &trimmed[1..] } else { trimmed };
393
394 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
396
397 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
398 return zero_encoding();
399 }
400
401 if negative {
402 encode_negative(&mantissa_digits, exponent)
403 } else {
404 encode_positive(&mantissa_digits, exponent)
405 }
406}
407
408pub fn validate_dynamo_number(
414 num_str: &str,
415) -> std::result::Result<(), crate::errors::DynoxideError> {
416 if num_str.is_empty() {
417 return Err(crate::errors::DynoxideError::ValidationException(
418 "The parameter cannot be converted to a numeric value".to_string(),
419 ));
420 }
421
422 if !is_well_formed_dynamo_number(num_str) {
431 return Err(crate::errors::DynoxideError::ValidationException(format!(
432 "The parameter cannot be converted to a numeric value: {num_str}"
433 )));
434 }
435
436 let (mantissa_digits, exponent) = parse_number_parts(num_str);
439
440 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
442 return Ok(());
443 }
444
445 if mantissa_digits.len() > 38 {
447 return Err(crate::errors::DynoxideError::ValidationException(
448 "Attempting to store more than 38 significant digits in a Number".to_string(),
449 ));
450 }
451
452 if exponent > 126 {
455 return Err(crate::errors::DynoxideError::ValidationException(
456 "Number overflow. Attempting to store a number with magnitude larger than supported range"
457 .to_string(),
458 ));
459 }
460
461 if exponent < -129 {
468 return Err(crate::errors::DynoxideError::ValidationException(
469 "Number underflow. Attempting to store a number with magnitude smaller than supported range"
470 .to_string(),
471 ));
472 }
473
474 Ok(())
475}
476
477fn is_well_formed_dynamo_number(s: &str) -> bool {
483 let bytes = s.as_bytes();
484 let n = bytes.len();
485 let mut i = 0;
486
487 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
489 i += 1;
490 }
491
492 let mut coeff_digits = 0usize;
494 let mut dots = 0usize;
495 while i < n && (bytes[i].is_ascii_digit() || bytes[i] == b'.') {
496 if bytes[i] == b'.' {
497 dots += 1;
498 if dots > 1 {
499 return false;
500 }
501 } else {
502 coeff_digits += 1;
503 }
504 i += 1;
505 }
506 if coeff_digits == 0 {
507 return false;
508 }
509
510 if i < n && (bytes[i] == b'e' || bytes[i] == b'E') {
512 i += 1;
513 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
514 i += 1;
515 }
516 let mut exp_digits = 0usize;
517 while i < n && bytes[i].is_ascii_digit() {
518 exp_digits += 1;
519 i += 1;
520 }
521 if exp_digits == 0 {
522 return false;
523 }
524 }
525
526 i == n
528}
529
530pub fn normalize_dynamo_number(num_str: &str) -> String {
538 let trimmed = num_str.trim();
539 if trimmed.is_empty() {
540 return "0".to_string();
541 }
542
543 let negative = trimmed.starts_with('-');
544 let abs_str = if negative {
545 &trimmed[1..]
546 } else {
547 trimmed.trim_start_matches('+')
548 };
549
550 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
551
552 if mantissa_digits.is_empty() {
554 return "0".to_string();
555 }
556
557 let num_digits = mantissa_digits.len() as i32;
562 let int_digits = exponent; let mut result = String::new();
565 if negative {
566 result.push('-');
567 }
568
569 if int_digits <= 0 {
570 result.push_str("0.");
572 for _ in 0..(-int_digits) {
573 result.push('0');
574 }
575 for &d in &mantissa_digits {
576 result.push((b'0' + d) as char);
577 }
578 } else if int_digits >= num_digits {
579 for &d in &mantissa_digits {
581 result.push((b'0' + d) as char);
582 }
583 for _ in 0..(int_digits - num_digits) {
584 result.push('0');
585 }
586 } else {
587 let int_part = int_digits as usize;
589 for &d in &mantissa_digits[..int_part] {
590 result.push((b'0' + d) as char);
591 }
592 result.push('.');
593 for &d in &mantissa_digits[int_part..] {
594 result.push((b'0' + d) as char);
595 }
596 }
597
598 result
599}
600
601fn zero_encoding() -> String {
602 format!("1{}{}", "0".repeat(4), "0".repeat(40))
604}
605
606fn encode_positive(mantissa: &[u8], exponent: i32) -> String {
607 let exp_encoded = (exponent + 5000) as u16;
608 let mantissa_str = mantissa_to_string(mantissa, 40);
609 format!("2{exp_encoded:04}{mantissa_str}")
610}
611
612fn encode_negative(mantissa: &[u8], exponent: i32) -> String {
613 let exp_encoded = 9999 - (exponent + 5000) as u16;
615 let mantissa_str = complement_mantissa(mantissa, 40);
616 format!("0{exp_encoded:04}{mantissa_str}")
617}
618
619pub(crate) fn parse_number_parts(s: &str) -> (Vec<u8>, i32) {
623 let (coeff, exp_part) = if let Some(pos) = s.to_ascii_lowercase().find('e') {
625 let coeff = &s[..pos];
626 let exp: i32 = s[pos + 1..].parse().unwrap_or(0);
627 (coeff, exp)
628 } else {
629 (s, 0)
630 };
631
632 let (int_part, frac_part) = if let Some(dot) = coeff.find('.') {
634 (&coeff[..dot], &coeff[dot + 1..])
635 } else {
636 (coeff, "")
637 };
638
639 let mut digits: Vec<u8> = Vec::new();
641 for ch in int_part.chars().chain(frac_part.chars()) {
642 if ch.is_ascii_digit() {
643 digits.push(ch as u8 - b'0');
644 }
645 }
646
647 if digits.is_empty() {
648 return (vec![], 0);
649 }
650
651 let int_len = int_part.chars().filter(|c| c.is_ascii_digit()).count() as i32;
653
654 let leading_zeros = digits.iter().take_while(|&&d| d == 0).count();
656 digits.drain(..leading_zeros);
657
658 while digits.last() == Some(&0) {
660 digits.pop();
661 }
662
663 if digits.is_empty() {
664 return (vec![], 0);
665 }
666
667 let exponent = int_len - leading_zeros as i32 + exp_part;
670
671 (digits, exponent)
672}
673
674fn mantissa_to_string(digits: &[u8], width: usize) -> String {
675 let mut s = String::with_capacity(width);
676 for &d in digits.iter().take(width) {
677 s.push((b'0' + d) as char);
678 }
679 while s.len() < width {
680 s.push('0');
681 }
682 s
683}
684
685fn complement_mantissa(digits: &[u8], width: usize) -> String {
686 let mut s = String::with_capacity(width);
687 for i in 0..width {
688 let d = if i < digits.len() { digits[i] } else { 0 };
689 s.push((b'0' + (9 - d)) as char);
690 }
691 s
692}
693
694fn hex_encode(bytes: &[u8]) -> String {
696 let mut s = String::with_capacity(bytes.len() * 2);
697 for &b in bytes {
698 s.push_str(&format!("{b:02x}"));
699 }
700 s
701}
702
703pub type Item = HashMap<String, AttributeValue>;
709
710#[derive(Debug, Clone, Default, Serialize, Deserialize)]
712pub struct SseSpecification {
713 #[serde(rename = "Enabled", default)]
714 pub enabled: Option<bool>,
715 #[serde(rename = "SSEType", default)]
716 pub sse_type: Option<String>,
717 #[serde(rename = "KMSMasterKeyId", default)]
718 pub kms_master_key_id: Option<String>,
719}
720
721#[derive(Debug, Clone, Default, Serialize, Deserialize)]
723pub struct Tag {
724 #[serde(rename = "Key")]
725 pub key: String,
726 #[serde(rename = "Value")]
727 pub value: String,
728}
729
730pub fn item_size(item: &Item) -> usize {
732 item.iter()
733 .map(|(name, value)| name.len() + value.size())
734 .sum()
735}
736
737pub const MAX_ITEM_SIZE: usize = 400 * 1024;
739
740#[derive(Debug, Clone, Serialize, Deserialize)]
743pub struct ItemCollectionMetrics {
744 #[serde(rename = "ItemCollectionKey")]
745 pub item_collection_key: HashMap<String, AttributeValue>,
746 #[serde(rename = "SizeEstimateRangeGB")]
747 pub size_estimate_range_gb: Vec<f64>,
748}
749
750#[derive(Debug, Clone, Default, Serialize, Deserialize)]
752pub struct ConsumedCapacity {
753 #[serde(rename = "TableName")]
754 pub table_name: String,
755 #[serde(rename = "CapacityUnits")]
756 pub capacity_units: f64,
757 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
758 pub read_capacity_units: Option<f64>,
759 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
760 pub write_capacity_units: Option<f64>,
761 #[serde(rename = "Table", skip_serializing_if = "Option::is_none")]
762 pub table: Option<CapacityDetail>,
763 #[serde(
764 rename = "GlobalSecondaryIndexes",
765 skip_serializing_if = "Option::is_none"
766 )]
767 pub global_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
768 #[serde(
769 rename = "LocalSecondaryIndexes",
770 skip_serializing_if = "Option::is_none"
771 )]
772 pub local_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
773}
774
775#[derive(Debug, Clone, Default, Serialize, Deserialize)]
777pub struct CapacityDetail {
778 #[serde(rename = "CapacityUnits")]
779 pub capacity_units: f64,
780 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
781 pub read_capacity_units: Option<f64>,
782 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
783 pub write_capacity_units: Option<f64>,
784}
785
786pub const TRANSACTIONAL_CAPACITY_FACTOR: f64 = 2.0;
791
792pub fn write_capacity_units(item_size_bytes: usize) -> f64 {
794 ((item_size_bytes as f64) / 1024.0).ceil().max(1.0)
795}
796
797pub fn read_capacity_units(item_size_bytes: usize) -> f64 {
801 ((item_size_bytes as f64) / 4096.0).ceil().max(1.0)
802}
803
804pub fn read_capacity_units_with_consistency(item_size_bytes: usize, consistent: bool) -> f64 {
809 let strongly = read_capacity_units(item_size_bytes);
810 if consistent { strongly } else { strongly / 2.0 }
811}
812
813pub fn consumed_capacity(
815 table_name: &str,
816 capacity_units: f64,
817 mode: &Option<String>,
818) -> Option<ConsumedCapacity> {
819 let mode = mode.as_deref().unwrap_or("NONE");
820 match mode {
821 "TOTAL" => Some(ConsumedCapacity {
822 table_name: table_name.to_string(),
823 capacity_units,
824 table: None,
825 global_secondary_indexes: None,
826 local_secondary_indexes: None,
827 ..Default::default()
828 }),
829 "INDEXES" => Some(ConsumedCapacity {
830 table_name: table_name.to_string(),
831 capacity_units,
832 table: Some(CapacityDetail {
833 capacity_units,
834 ..Default::default()
835 }),
836 global_secondary_indexes: None,
837 local_secondary_indexes: None,
838 ..Default::default()
839 }),
840 _ => None,
841 }
842}
843
844pub fn consumed_capacity_with_indexes(
846 table_name: &str,
847 table_units: f64,
848 gsi_units: &HashMap<String, f64>,
849 mode: &Option<String>,
850) -> Option<ConsumedCapacity> {
851 consumed_capacity_with_secondary_indexes(
852 table_name,
853 table_units,
854 gsi_units,
855 &HashMap::new(),
856 mode,
857 )
858}
859
860pub fn consumed_capacity_with_secondary_indexes(
862 table_name: &str,
863 table_units: f64,
864 gsi_units: &HashMap<String, f64>,
865 lsi_units: &HashMap<String, f64>,
866 mode: &Option<String>,
867) -> Option<ConsumedCapacity> {
868 let units_to_map = |units: &HashMap<String, f64>| -> Option<HashMap<String, CapacityDetail>> {
869 if units.is_empty() {
870 None
871 } else {
872 Some(
873 units
874 .iter()
875 .map(|(name, &u)| {
876 (
877 name.clone(),
878 CapacityDetail {
879 capacity_units: u,
880 ..Default::default()
881 },
882 )
883 })
884 .collect(),
885 )
886 }
887 };
888
889 match mode.as_deref().unwrap_or("NONE") {
890 "INDEXES" => {
891 let gsi_total: f64 = gsi_units.values().sum();
892 let lsi_total: f64 = lsi_units.values().sum();
893 Some(ConsumedCapacity {
894 table_name: table_name.to_string(),
895 capacity_units: table_units + gsi_total + lsi_total,
896 table: Some(CapacityDetail {
897 capacity_units: table_units,
898 ..Default::default()
899 }),
900 global_secondary_indexes: units_to_map(gsi_units),
901 local_secondary_indexes: units_to_map(lsi_units),
902 ..Default::default()
903 })
904 }
905 "TOTAL" => {
906 let gsi_total: f64 = gsi_units.values().sum();
907 let lsi_total: f64 = lsi_units.values().sum();
908 Some(ConsumedCapacity {
909 table_name: table_name.to_string(),
910 capacity_units: table_units + gsi_total + lsi_total,
911 table: None,
912 global_secondary_indexes: None,
913 local_secondary_indexes: None,
914 ..Default::default()
915 })
916 }
917 _ => None,
918 }
919}
920
921pub fn transactional_read_capacity(
926 table_name: &str,
927 units: f64,
928 mode: &Option<String>,
929) -> Option<ConsumedCapacity> {
930 match mode.as_deref().unwrap_or("NONE") {
931 "TOTAL" => Some(ConsumedCapacity {
932 table_name: table_name.to_string(),
933 capacity_units: units,
934 read_capacity_units: Some(units),
935 ..Default::default()
936 }),
937 "INDEXES" => Some(ConsumedCapacity {
938 table_name: table_name.to_string(),
939 capacity_units: units,
940 read_capacity_units: Some(units),
941 table: Some(CapacityDetail {
942 capacity_units: units,
943 read_capacity_units: Some(units),
944 ..Default::default()
945 }),
946 ..Default::default()
947 }),
948 _ => None,
949 }
950}
951
952pub fn build_transactional_capacity(
959 table_units: &HashMap<String, f64>,
960 mode: &Option<String>,
961 builder: fn(&str, f64, &Option<String>) -> Option<ConsumedCapacity>,
962) -> Option<Vec<ConsumedCapacity>> {
963 if matches!(mode.as_deref(), Some("TOTAL") | Some("INDEXES")) {
964 Some(
965 table_units
966 .iter()
967 .filter_map(|(table, &units)| builder(table, units, mode))
968 .collect(),
969 )
970 } else {
971 None
972 }
973}
974
975pub fn transactional_write_capacity(
980 table_name: &str,
981 units: f64,
982 mode: &Option<String>,
983) -> Option<ConsumedCapacity> {
984 match mode.as_deref().unwrap_or("NONE") {
985 "TOTAL" => Some(ConsumedCapacity {
986 table_name: table_name.to_string(),
987 capacity_units: units,
988 write_capacity_units: Some(units),
989 ..Default::default()
990 }),
991 "INDEXES" => Some(ConsumedCapacity {
992 table_name: table_name.to_string(),
993 capacity_units: units,
994 write_capacity_units: Some(units),
995 table: Some(CapacityDetail {
996 capacity_units: units,
997 write_capacity_units: Some(units),
998 ..Default::default()
999 }),
1000 ..Default::default()
1001 }),
1002 _ => None,
1003 }
1004}
1005
1006#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1008pub struct KeySchemaElement {
1009 #[serde(rename = "AttributeName", alias = "attribute_name")]
1010 pub attribute_name: String,
1011 #[serde(rename = "KeyType", alias = "key_type")]
1012 pub key_type: KeyType,
1013}
1014
1015#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1017pub enum KeyType {
1018 #[default]
1019 HASH,
1020 RANGE,
1021}
1022
1023#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1025pub struct AttributeDefinition {
1026 #[serde(rename = "AttributeName", alias = "attribute_name")]
1027 pub attribute_name: String,
1028 #[serde(rename = "AttributeType", alias = "attribute_type")]
1029 pub attribute_type: ScalarAttributeType,
1030}
1031
1032#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1034pub enum ScalarAttributeType {
1035 #[default]
1036 S,
1037 N,
1038 B,
1039}
1040
1041#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1043pub struct Projection {
1044 #[serde(
1045 rename = "ProjectionType",
1046 alias = "projection_type",
1047 default,
1048 skip_serializing_if = "Option::is_none"
1049 )]
1050 pub projection_type: Option<ProjectionType>,
1051 #[serde(
1052 rename = "NonKeyAttributes",
1053 alias = "non_key_attributes",
1054 skip_serializing_if = "Option::is_none"
1055 )]
1056 pub non_key_attributes: Option<Vec<String>>,
1057}
1058
1059#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1061#[allow(non_camel_case_types)]
1062pub enum ProjectionType {
1063 #[default]
1064 ALL,
1065 KEYS_ONLY,
1066 INCLUDE,
1067}
1068
1069#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1071pub struct GlobalSecondaryIndex {
1072 #[serde(rename = "IndexName", alias = "index_name")]
1073 pub index_name: String,
1074 #[serde(rename = "KeySchema", alias = "key_schema")]
1075 pub key_schema: Vec<KeySchemaElement>,
1076 #[serde(rename = "Projection", alias = "projection")]
1077 pub projection: Projection,
1078 #[serde(
1079 rename = "ProvisionedThroughput",
1080 alias = "provisioned_throughput",
1081 skip_serializing_if = "Option::is_none"
1082 )]
1083 pub provisioned_throughput: Option<ProvisionedThroughput>,
1084}
1085
1086#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1088pub struct LocalSecondaryIndex {
1089 #[serde(rename = "IndexName", alias = "index_name")]
1090 pub index_name: String,
1091 #[serde(rename = "KeySchema", alias = "key_schema")]
1092 pub key_schema: Vec<KeySchemaElement>,
1093 #[serde(rename = "Projection", alias = "projection")]
1094 pub projection: Projection,
1095}
1096
1097#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1099pub struct ProvisionedThroughput {
1100 #[serde(rename = "ReadCapacityUnits", default)]
1101 pub read_capacity_units: Option<i64>,
1102 #[serde(rename = "WriteCapacityUnits", default)]
1103 pub write_capacity_units: Option<i64>,
1104}
1105
1106#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1108pub struct OnDemandThroughput {
1109 #[serde(
1110 rename = "MaxReadRequestUnits",
1111 default,
1112 skip_serializing_if = "Option::is_none"
1113 )]
1114 pub max_read_request_units: Option<i64>,
1115 #[serde(
1116 rename = "MaxWriteRequestUnits",
1117 default,
1118 skip_serializing_if = "Option::is_none"
1119 )]
1120 pub max_write_request_units: Option<i64>,
1121}
1122
1123#[derive(Debug, Clone, PartialEq)]
1129pub struct ConversionError {
1130 pub expected: &'static str,
1132 pub actual: &'static str,
1134}
1135
1136impl fmt::Display for ConversionError {
1137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1138 write!(f, "expected {}, got {}", self.expected, self.actual)
1139 }
1140}
1141
1142impl std::error::Error for ConversionError {}
1143
1144impl From<String> for AttributeValue {
1147 fn from(value: String) -> Self {
1148 AttributeValue::S(value)
1149 }
1150}
1151
1152impl From<&str> for AttributeValue {
1153 fn from(value: &str) -> Self {
1154 AttributeValue::S(value.to_string())
1155 }
1156}
1157
1158impl From<bool> for AttributeValue {
1159 fn from(value: bool) -> Self {
1160 AttributeValue::BOOL(value)
1161 }
1162}
1163
1164impl From<Vec<u8>> for AttributeValue {
1165 fn from(value: Vec<u8>) -> Self {
1166 AttributeValue::B(value)
1167 }
1168}
1169
1170impl From<&[u8]> for AttributeValue {
1171 fn from(value: &[u8]) -> Self {
1172 AttributeValue::B(value.to_vec())
1173 }
1174}
1175
1176macro_rules! impl_from_integer {
1178 ($($t:ty),+) => {
1179 $(
1180 impl From<$t> for AttributeValue {
1181 fn from(value: $t) -> Self {
1182 AttributeValue::N(value.to_string())
1183 }
1184 }
1185 )+
1186 };
1187}
1188
1189impl_from_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1190
1191impl From<HashMap<String, AttributeValue>> for AttributeValue {
1193 fn from(value: HashMap<String, AttributeValue>) -> Self {
1194 AttributeValue::M(value)
1195 }
1196}
1197
1198impl From<Vec<AttributeValue>> for AttributeValue {
1199 fn from(value: Vec<AttributeValue>) -> Self {
1200 AttributeValue::L(value)
1201 }
1202}
1203
1204impl From<HashSet<String>> for AttributeValue {
1205 fn from(value: HashSet<String>) -> Self {
1206 AttributeValue::SS(value.into_iter().collect())
1207 }
1208}
1209
1210impl From<BTreeSet<String>> for AttributeValue {
1211 fn from(value: BTreeSet<String>) -> Self {
1212 AttributeValue::SS(value.into_iter().collect())
1213 }
1214}
1215
1216impl TryFrom<f64> for AttributeValue {
1219 type Error = ConversionError;
1220
1221 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
1222 if value.is_finite() {
1223 Ok(AttributeValue::N(value.to_string()))
1224 } else {
1225 Err(ConversionError {
1226 expected: "finite f64",
1227 actual: "NaN or Infinity",
1228 })
1229 }
1230 }
1231}
1232
1233impl TryFrom<f32> for AttributeValue {
1234 type Error = ConversionError;
1235
1236 fn try_from(value: f32) -> std::result::Result<Self, Self::Error> {
1237 if value.is_finite() {
1238 Ok(AttributeValue::N(value.to_string()))
1239 } else {
1240 Err(ConversionError {
1241 expected: "finite f32",
1242 actual: "NaN or Infinity",
1243 })
1244 }
1245 }
1246}
1247
1248impl TryFrom<AttributeValue> for String {
1251 type Error = ConversionError;
1252
1253 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1254 match value {
1255 AttributeValue::S(s) => Ok(s),
1256 other => Err(ConversionError {
1257 expected: "S",
1258 actual: other.type_name(),
1259 }),
1260 }
1261 }
1262}
1263
1264impl TryFrom<AttributeValue> for bool {
1265 type Error = ConversionError;
1266
1267 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1268 match value {
1269 AttributeValue::BOOL(b) => Ok(b),
1270 other => Err(ConversionError {
1271 expected: "BOOL",
1272 actual: other.type_name(),
1273 }),
1274 }
1275 }
1276}
1277
1278impl TryFrom<AttributeValue> for Vec<u8> {
1279 type Error = ConversionError;
1280
1281 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1282 match value {
1283 AttributeValue::B(b) => Ok(b),
1284 other => Err(ConversionError {
1285 expected: "B",
1286 actual: other.type_name(),
1287 }),
1288 }
1289 }
1290}
1291
1292macro_rules! impl_try_from_av_integer {
1293 ($($t:ty),+) => {
1294 $(
1295 impl TryFrom<AttributeValue> for $t {
1296 type Error = ConversionError;
1297
1298 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1299 match value {
1300 AttributeValue::N(n) => n.parse::<$t>().map_err(|_| ConversionError {
1301 expected: stringify!($t),
1302 actual: "N (parse failed)",
1303 }),
1304 other => Err(ConversionError {
1305 expected: "N",
1306 actual: other.type_name(),
1307 }),
1308 }
1309 }
1310 }
1311 )+
1312 };
1313}
1314
1315impl_try_from_av_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1316
1317impl TryFrom<AttributeValue> for f64 {
1318 type Error = ConversionError;
1319
1320 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1321 match value {
1322 AttributeValue::N(n) => n.parse::<f64>().map_err(|_| ConversionError {
1323 expected: "f64",
1324 actual: "N (parse failed)",
1325 }),
1326 other => Err(ConversionError {
1327 expected: "N",
1328 actual: other.type_name(),
1329 }),
1330 }
1331 }
1332}
1333
1334impl TryFrom<AttributeValue> for f32 {
1335 type Error = ConversionError;
1336
1337 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1338 match value {
1339 AttributeValue::N(n) => n.parse::<f32>().map_err(|_| ConversionError {
1340 expected: "f32",
1341 actual: "N (parse failed)",
1342 }),
1343 other => Err(ConversionError {
1344 expected: "N",
1345 actual: other.type_name(),
1346 }),
1347 }
1348 }
1349}
1350
1351impl TryFrom<AttributeValue> for HashMap<String, AttributeValue> {
1352 type Error = ConversionError;
1353
1354 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1355 match value {
1356 AttributeValue::M(m) => Ok(m),
1357 other => Err(ConversionError {
1358 expected: "M",
1359 actual: other.type_name(),
1360 }),
1361 }
1362 }
1363}
1364
1365impl TryFrom<AttributeValue> for Vec<AttributeValue> {
1366 type Error = ConversionError;
1367
1368 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1369 match value {
1370 AttributeValue::L(l) => Ok(l),
1371 other => Err(ConversionError {
1372 expected: "L",
1373 actual: other.type_name(),
1374 }),
1375 }
1376 }
1377}
1378
1379impl TryFrom<AttributeValue> for Vec<String> {
1380 type Error = ConversionError;
1381
1382 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1383 match value {
1384 AttributeValue::SS(ss) => Ok(ss),
1385 AttributeValue::L(l) => {
1386 l.into_iter()
1388 .map(|av| match av {
1389 AttributeValue::S(s) => Ok(s),
1390 other => Err(ConversionError {
1391 expected: "S (within L)",
1392 actual: other.type_name(),
1393 }),
1394 })
1395 .collect()
1396 }
1397 other => Err(ConversionError {
1398 expected: "SS or L",
1399 actual: other.type_name(),
1400 }),
1401 }
1402 }
1403}
1404
1405#[cfg(test)]
1406mod tests {
1407 use super::*;
1408
1409 #[test]
1410 fn test_serialize_string() {
1411 let val = AttributeValue::S("hello".to_string());
1412 let json = serde_json::to_string(&val).unwrap();
1413 assert_eq!(json, r#"{"S":"hello"}"#);
1414 }
1415
1416 #[test]
1417 fn test_serialize_number() {
1418 let val = AttributeValue::N("42".to_string());
1419 let json = serde_json::to_string(&val).unwrap();
1420 assert_eq!(json, r#"{"N":"42"}"#);
1421 }
1422
1423 #[test]
1424 fn test_serialize_binary() {
1425 let val = AttributeValue::B(vec![1, 2, 3]);
1426 let json = serde_json::to_string(&val).unwrap();
1427 assert_eq!(json, r#"{"B":"AQID"}"#);
1428 }
1429
1430 #[test]
1431 fn test_serialize_bool() {
1432 let val = AttributeValue::BOOL(true);
1433 let json = serde_json::to_string(&val).unwrap();
1434 assert_eq!(json, r#"{"BOOL":true}"#);
1435 }
1436
1437 #[test]
1438 fn test_serialize_null() {
1439 let val = AttributeValue::NULL(true);
1440 let json = serde_json::to_string(&val).unwrap();
1441 assert_eq!(json, r#"{"NULL":true}"#);
1442 }
1443
1444 #[test]
1445 fn test_deserialize_null_true() {
1446 let val: AttributeValue = serde_json::from_str(r#"{"NULL":true}"#).unwrap();
1447 assert_eq!(val, AttributeValue::NULL(true));
1448 }
1449
1450 #[test]
1451 fn test_deserialize_null_false_normalises_to_true() {
1452 let val: AttributeValue = serde_json::from_str(r#"{"NULL":false}"#).unwrap();
1456 assert_eq!(val, AttributeValue::NULL(true));
1457 }
1458
1459 #[test]
1460 fn test_deserialize_null_non_boolean_rejected() {
1461 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":"no"}"#).unwrap_err();
1463 assert!(
1464 err.to_string().contains("must have the value of true"),
1465 "unexpected error: {err}"
1466 );
1467 }
1468
1469 #[test]
1470 fn test_serialize_string_set() {
1471 let val = AttributeValue::SS(vec!["a".to_string(), "b".to_string()]);
1472 let json = serde_json::to_string(&val).unwrap();
1473 assert_eq!(json, r#"{"SS":["a","b"]}"#);
1474 }
1475
1476 #[test]
1477 fn test_serialize_list() {
1478 let val = AttributeValue::L(vec![
1479 AttributeValue::S("hello".to_string()),
1480 AttributeValue::N("42".to_string()),
1481 ]);
1482 let json = serde_json::to_string(&val).unwrap();
1483 assert_eq!(json, r#"{"L":[{"S":"hello"},{"N":"42"}]}"#);
1484 }
1485
1486 #[test]
1487 fn test_serialize_map() {
1488 let mut m = HashMap::new();
1489 m.insert("key".to_string(), AttributeValue::S("value".to_string()));
1490 let val = AttributeValue::M(m);
1491 let json = serde_json::to_string(&val).unwrap();
1492 assert_eq!(json, r#"{"M":{"key":{"S":"value"}}}"#);
1493 }
1494
1495 #[test]
1496 fn test_round_trip_all_types() {
1497 let values = vec![
1498 AttributeValue::S("hello".to_string()),
1499 AttributeValue::N("42.5".to_string()),
1500 AttributeValue::B(vec![0, 255, 128]),
1501 AttributeValue::BOOL(false),
1502 AttributeValue::NULL(true),
1503 AttributeValue::SS(vec!["x".to_string(), "y".to_string()]),
1504 AttributeValue::NS(vec!["1".to_string(), "2.5".to_string()]),
1505 AttributeValue::BS(vec![vec![1], vec![2, 3]]),
1506 AttributeValue::L(vec![
1507 AttributeValue::S("nested".to_string()),
1508 AttributeValue::N("99".to_string()),
1509 ]),
1510 ];
1511
1512 for val in values {
1513 let json = serde_json::to_string(&val).unwrap();
1514 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
1515 assert_eq!(val, deserialized, "Round-trip failed for {json}");
1516 }
1517 }
1518
1519 #[test]
1520 fn test_size_string() {
1521 let val = AttributeValue::S("hello".to_string());
1522 assert_eq!(val.size(), 5);
1523 }
1524
1525 #[test]
1526 fn test_size_number() {
1527 let val = AttributeValue::N("42".to_string());
1529 assert_eq!(val.size(), 2);
1530 }
1531
1532 #[test]
1533 fn test_size_bool() {
1534 assert_eq!(AttributeValue::BOOL(true).size(), 1);
1535 }
1536
1537 #[test]
1538 fn test_size_null() {
1539 assert_eq!(AttributeValue::NULL(true).size(), 1);
1540 }
1541
1542 #[test]
1543 fn test_key_string_s() {
1544 let val = AttributeValue::S("hello".to_string());
1545 assert_eq!(val.to_key_string(), Some("S:hello".to_string()));
1546 }
1547
1548 #[test]
1549 fn test_key_string_n() {
1550 let val = AttributeValue::N("42".to_string());
1551 let key = val.to_key_string().unwrap();
1552 assert!(key.starts_with("N:"));
1553 }
1554
1555 #[test]
1556 fn test_key_string_b() {
1557 let val = AttributeValue::B(vec![0xff, 0x00, 0xab]);
1558 assert_eq!(val.to_key_string(), Some("B:ff00ab".to_string()));
1559 }
1560
1561 #[test]
1562 fn test_key_string_non_key_type_returns_none() {
1563 assert_eq!(AttributeValue::BOOL(true).to_key_string(), None);
1564 assert_eq!(AttributeValue::L(vec![]).to_key_string(), None);
1565 }
1566
1567 #[test]
1569 fn test_number_sort_ordering() {
1570 let numbers = vec![
1571 "-1000", "-100", "-10", "-1", "-0.5", "-0.001", "0", "0.001", "0.5", "1", "10", "100",
1572 "1000",
1573 ];
1574 let encoded: Vec<String> = numbers
1575 .iter()
1576 .map(|n| normalize_number_for_sort(n))
1577 .collect();
1578
1579 for i in 0..encoded.len() - 1 {
1580 assert!(
1581 encoded[i] < encoded[i + 1],
1582 "Sort order broken: {} ({}) should be < {} ({})",
1583 numbers[i],
1584 encoded[i],
1585 numbers[i + 1],
1586 encoded[i + 1]
1587 );
1588 }
1589 }
1590
1591 #[test]
1592 fn test_number_sort_zero_variants() {
1593 let z1 = normalize_number_for_sort("0");
1594 let z2 = normalize_number_for_sort("-0");
1595 let z3 = normalize_number_for_sort("0.0");
1596 assert_eq!(z1, z2);
1597 assert_eq!(z2, z3);
1598 }
1599
1600 #[test]
1601 fn test_number_sort_decimals() {
1602 let a = normalize_number_for_sort("1.5");
1603 let b = normalize_number_for_sort("2.5");
1604 assert!(a < b);
1605
1606 let c = normalize_number_for_sort("0.001");
1607 let d = normalize_number_for_sort("0.01");
1608 assert!(c < d);
1609 }
1610
1611 #[test]
1612 fn test_number_sort_scientific() {
1613 let a = normalize_number_for_sort("1e10");
1614 let b = normalize_number_for_sort("1e11");
1615 assert!(a < b);
1616
1617 let c = normalize_number_for_sort("-1e11");
1618 let d = normalize_number_for_sort("-1e10");
1619 assert!(c < d);
1620 }
1621
1622 #[test]
1627 fn test_validate_number_accepts_dynamodb_grammar() {
1628 for input in [
1629 "+5", "+1.5", "+0", "-0", "+0.0", "+1e2", "1e+2", "1.5E+3", "-7", "+.5", ".5", "5.",
1630 "00042", "1.23E10", "+1e-2", "1E-130", "+1E-130",
1631 ] {
1632 assert!(
1633 validate_dynamo_number(input).is_ok(),
1634 "expected {input} to validate, got {:?}",
1635 validate_dynamo_number(input)
1636 );
1637 }
1638 }
1639
1640 #[test]
1641 fn test_validate_number_rejects_malformed() {
1642 for input in [
1645 "+e2", "e2", "+1+2", "1+2", "+1.2.3", "1.2.3", "++5", "+-5", "-+5", "+", "-", "1e",
1646 "1e+", ".", "1.2e3.4", "0x5", "NaN", "Infinity", "1_000", " 5", "5 ", "1 5", "",
1647 ] {
1648 assert!(
1649 matches!(
1650 validate_dynamo_number(input),
1651 Err(crate::errors::DynoxideError::ValidationException(_))
1652 ),
1653 "expected {input:?} to be rejected with ValidationException, got {:?}",
1654 validate_dynamo_number(input)
1655 );
1656 }
1657 }
1658
1659 #[test]
1660 fn test_normalize_number_matches_dynamodb() {
1661 for (input, stored) in [
1662 ("+5", "5"),
1663 ("+1.5", "1.5"),
1664 ("+0", "0"),
1665 ("-0", "0"),
1666 ("+0.0", "0"),
1667 ("+1e2", "100"),
1668 ("1e+2", "100"),
1669 ("1.5E+3", "1500"),
1670 ("-7", "-7"),
1671 ("+.5", "0.5"),
1672 (".5", "0.5"),
1673 ("5.", "5"),
1674 ("00042", "42"),
1675 ("1.23E10", "12300000000"),
1676 ("+1e-2", "0.01"),
1677 ] {
1678 assert_eq!(
1679 normalize_dynamo_number(input),
1680 stored,
1681 "{input} should normalise to {stored}"
1682 );
1683 }
1684 }
1685
1686 #[test]
1687 fn test_type_name() {
1688 assert_eq!(AttributeValue::S("".to_string()).type_name(), "S");
1689 assert_eq!(AttributeValue::N("0".to_string()).type_name(), "N");
1690 assert_eq!(AttributeValue::B(vec![]).type_name(), "B");
1691 assert_eq!(AttributeValue::BOOL(true).type_name(), "BOOL");
1692 assert_eq!(AttributeValue::NULL(true).type_name(), "NULL");
1693 assert_eq!(AttributeValue::SS(vec![]).type_name(), "SS");
1694 assert_eq!(AttributeValue::NS(vec![]).type_name(), "NS");
1695 assert_eq!(AttributeValue::BS(vec![]).type_name(), "BS");
1696 assert_eq!(AttributeValue::L(vec![]).type_name(), "L");
1697 assert_eq!(AttributeValue::M(HashMap::new()).type_name(), "M");
1698 }
1699}