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() != Some(true) {
279 return Err(de::Error::custom(format!(
280 "{}One or more parameter values were invalid: \
281 Null attribute value types must have the value of true",
282 crate::serde_errors::REQUEST_VALIDATION_MARKER
283 )));
284 }
285 Ok(AttributeValue::NULL(true))
286 }
287 "SS" => {
288 let arr = val
289 .as_array()
290 .ok_or_else(|| de::Error::custom("expected array for SS"))?;
291 let ss: Result<Vec<String>, _> = arr
292 .iter()
293 .map(|v| {
294 v.as_str()
295 .map(|s| s.to_string())
296 .ok_or_else(|| de::Error::custom("expected string in SS"))
297 })
298 .collect();
299 Ok(AttributeValue::SS(ss?))
300 }
301 "NS" => {
302 let arr = val
303 .as_array()
304 .ok_or_else(|| de::Error::custom("expected array for NS"))?;
305 let ns: Result<Vec<String>, _> = arr
306 .iter()
307 .map(|v| {
308 v.as_str()
309 .map(|s| s.to_string())
310 .ok_or_else(|| de::Error::custom("expected string in NS"))
311 })
312 .collect();
313 Ok(AttributeValue::NS(ns?))
314 }
315 "BS" => {
316 let arr = val
317 .as_array()
318 .ok_or_else(|| de::Error::custom("expected array for BS"))?;
319 let mut decoded = Vec::with_capacity(arr.len());
320 for item in arr {
321 let encoded = item
322 .as_str()
323 .ok_or_else(|| de::Error::custom("expected string in BS"))?;
324 decoded.push(
325 BASE64
326 .decode(encoded)
327 .map_err(|e| de::Error::custom(format!("invalid base64: {e}")))?,
328 );
329 }
330 Ok(AttributeValue::BS(decoded))
331 }
332 "L" => {
333 let arr = val
334 .as_array()
335 .ok_or_else(|| de::Error::custom("expected array for L"))?;
336 let list: Result<Vec<AttributeValue>, _> = arr
337 .iter()
338 .map(|v| serde_json::from_value(v.clone()).map_err(de::Error::custom))
339 .collect();
340 Ok(AttributeValue::L(list?))
341 }
342 "M" => {
343 let map_val = val
344 .as_object()
345 .ok_or_else(|| de::Error::custom("expected object for M"))?;
346 let mut result = std::collections::HashMap::new();
347 for (k, v) in map_val {
348 let av: AttributeValue =
349 serde_json::from_value(v.clone()).map_err(de::Error::custom)?;
350 result.insert(k.clone(), av);
351 }
352 Ok(AttributeValue::M(result))
353 }
354 _ => unreachable!(),
355 }
356 }
357}
358
359fn validate_number_in_deser(n: &str) -> Result<(), String> {
366 match validate_dynamo_number(n) {
369 Ok(()) => Ok(()),
370 Err(crate::errors::DynoxideError::ValidationException(m)) => Err(format!("VALIDATION:{m}")),
371 Err(e) => Err(format!("VALIDATION:{e}")),
372 }
373}
374
375pub fn normalize_number_for_sort(num_str: &str) -> String {
389 let trimmed = num_str.trim();
390
391 if trimmed.is_empty() || trimmed == "0" || trimmed == "-0" || trimmed == "0.0" {
392 return zero_encoding();
393 }
394
395 let negative = trimmed.starts_with('-');
396 let abs_str = if negative { &trimmed[1..] } else { trimmed };
397
398 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
400
401 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
402 return zero_encoding();
403 }
404
405 if negative {
406 encode_negative(&mantissa_digits, exponent)
407 } else {
408 encode_positive(&mantissa_digits, exponent)
409 }
410}
411
412pub fn validate_dynamo_number(
418 num_str: &str,
419) -> std::result::Result<(), crate::errors::DynoxideError> {
420 if num_str.is_empty() {
421 return Err(crate::errors::DynoxideError::ValidationException(
422 "The parameter cannot be converted to a numeric value".to_string(),
423 ));
424 }
425
426 if !is_well_formed_dynamo_number(num_str) {
435 return Err(crate::errors::DynoxideError::ValidationException(format!(
436 "The parameter cannot be converted to a numeric value: {num_str}"
437 )));
438 }
439
440 let (mantissa_digits, exponent) = parse_number_parts(num_str);
443
444 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
446 return Ok(());
447 }
448
449 if mantissa_digits.len() > 38 {
451 return Err(crate::errors::DynoxideError::ValidationException(
452 "Attempting to store more than 38 significant digits in a Number".to_string(),
453 ));
454 }
455
456 if exponent > 126 {
459 return Err(crate::errors::DynoxideError::ValidationException(
460 "Number overflow. Attempting to store a number with magnitude larger than supported range"
461 .to_string(),
462 ));
463 }
464
465 if exponent < -129 {
472 return Err(crate::errors::DynoxideError::ValidationException(
473 "Number underflow. Attempting to store a number with magnitude smaller than supported range"
474 .to_string(),
475 ));
476 }
477
478 Ok(())
479}
480
481fn is_well_formed_dynamo_number(s: &str) -> bool {
487 let bytes = s.as_bytes();
488 let n = bytes.len();
489 let mut i = 0;
490
491 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
493 i += 1;
494 }
495
496 let mut coeff_digits = 0usize;
498 let mut dots = 0usize;
499 while i < n && (bytes[i].is_ascii_digit() || bytes[i] == b'.') {
500 if bytes[i] == b'.' {
501 dots += 1;
502 if dots > 1 {
503 return false;
504 }
505 } else {
506 coeff_digits += 1;
507 }
508 i += 1;
509 }
510 if coeff_digits == 0 {
511 return false;
512 }
513
514 if i < n && (bytes[i] == b'e' || bytes[i] == b'E') {
516 i += 1;
517 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
518 i += 1;
519 }
520 let mut exp_digits = 0usize;
521 while i < n && bytes[i].is_ascii_digit() {
522 exp_digits += 1;
523 i += 1;
524 }
525 if exp_digits == 0 {
526 return false;
527 }
528 }
529
530 i == n
532}
533
534pub fn normalize_dynamo_number(num_str: &str) -> String {
542 let trimmed = num_str.trim();
543 if trimmed.is_empty() {
544 return "0".to_string();
545 }
546
547 let negative = trimmed.starts_with('-');
548 let abs_str = if negative {
549 &trimmed[1..]
550 } else {
551 trimmed.trim_start_matches('+')
552 };
553
554 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
555
556 if mantissa_digits.is_empty() {
558 return "0".to_string();
559 }
560
561 let num_digits = mantissa_digits.len() as i32;
566 let int_digits = exponent; let mut result = String::new();
569 if negative {
570 result.push('-');
571 }
572
573 if int_digits <= 0 {
574 result.push_str("0.");
576 for _ in 0..(-int_digits) {
577 result.push('0');
578 }
579 for &d in &mantissa_digits {
580 result.push((b'0' + d) as char);
581 }
582 } else if int_digits >= num_digits {
583 for &d in &mantissa_digits {
585 result.push((b'0' + d) as char);
586 }
587 for _ in 0..(int_digits - num_digits) {
588 result.push('0');
589 }
590 } else {
591 let int_part = int_digits as usize;
593 for &d in &mantissa_digits[..int_part] {
594 result.push((b'0' + d) as char);
595 }
596 result.push('.');
597 for &d in &mantissa_digits[int_part..] {
598 result.push((b'0' + d) as char);
599 }
600 }
601
602 result
603}
604
605fn zero_encoding() -> String {
606 format!("1{}{}", "0".repeat(4), "0".repeat(40))
608}
609
610fn encode_positive(mantissa: &[u8], exponent: i32) -> String {
611 let exp_encoded = (exponent + 5000) as u16;
612 let mantissa_str = mantissa_to_string(mantissa, 40);
613 format!("2{exp_encoded:04}{mantissa_str}")
614}
615
616fn encode_negative(mantissa: &[u8], exponent: i32) -> String {
617 let exp_encoded = 9999 - (exponent + 5000) as u16;
619 let mantissa_str = complement_mantissa(mantissa, 40);
620 format!("0{exp_encoded:04}{mantissa_str}")
621}
622
623pub(crate) fn parse_number_parts(s: &str) -> (Vec<u8>, i32) {
627 let (coeff, exp_part) = if let Some(pos) = s.to_ascii_lowercase().find('e') {
629 let coeff = &s[..pos];
630 let exp: i32 = s[pos + 1..].parse().unwrap_or(0);
631 (coeff, exp)
632 } else {
633 (s, 0)
634 };
635
636 let (int_part, frac_part) = if let Some(dot) = coeff.find('.') {
638 (&coeff[..dot], &coeff[dot + 1..])
639 } else {
640 (coeff, "")
641 };
642
643 let mut digits: Vec<u8> = Vec::new();
645 for ch in int_part.chars().chain(frac_part.chars()) {
646 if ch.is_ascii_digit() {
647 digits.push(ch as u8 - b'0');
648 }
649 }
650
651 if digits.is_empty() {
652 return (vec![], 0);
653 }
654
655 let int_len = int_part.chars().filter(|c| c.is_ascii_digit()).count() as i32;
657
658 let leading_zeros = digits.iter().take_while(|&&d| d == 0).count();
660 digits.drain(..leading_zeros);
661
662 while digits.last() == Some(&0) {
664 digits.pop();
665 }
666
667 if digits.is_empty() {
668 return (vec![], 0);
669 }
670
671 let exponent = int_len - leading_zeros as i32 + exp_part;
674
675 (digits, exponent)
676}
677
678fn mantissa_to_string(digits: &[u8], width: usize) -> String {
679 let mut s = String::with_capacity(width);
680 for &d in digits.iter().take(width) {
681 s.push((b'0' + d) as char);
682 }
683 while s.len() < width {
684 s.push('0');
685 }
686 s
687}
688
689fn complement_mantissa(digits: &[u8], width: usize) -> String {
690 let mut s = String::with_capacity(width);
691 for i in 0..width {
692 let d = if i < digits.len() { digits[i] } else { 0 };
693 s.push((b'0' + (9 - d)) as char);
694 }
695 s
696}
697
698fn hex_encode(bytes: &[u8]) -> String {
700 let mut s = String::with_capacity(bytes.len() * 2);
701 for &b in bytes {
702 s.push_str(&format!("{b:02x}"));
703 }
704 s
705}
706
707pub type Item = HashMap<String, AttributeValue>;
713
714#[derive(Debug, Clone, Default, Serialize, Deserialize)]
716pub struct SseSpecification {
717 #[serde(rename = "Enabled", default)]
718 pub enabled: Option<bool>,
719 #[serde(rename = "SSEType", default)]
720 pub sse_type: Option<String>,
721 #[serde(rename = "KMSMasterKeyId", default)]
722 pub kms_master_key_id: Option<String>,
723}
724
725#[derive(Debug, Clone, Default, Serialize, Deserialize)]
727pub struct Tag {
728 #[serde(rename = "Key")]
729 pub key: String,
730 #[serde(rename = "Value")]
731 pub value: String,
732}
733
734pub fn item_size(item: &Item) -> usize {
736 item.iter()
737 .map(|(name, value)| name.len() + value.size())
738 .sum()
739}
740
741pub const MAX_ITEM_SIZE: usize = 400 * 1024;
743
744#[derive(Debug, Clone, Serialize, Deserialize)]
747pub struct ItemCollectionMetrics {
748 #[serde(rename = "ItemCollectionKey")]
749 pub item_collection_key: HashMap<String, AttributeValue>,
750 #[serde(rename = "SizeEstimateRangeGB")]
751 pub size_estimate_range_gb: Vec<f64>,
752}
753
754#[derive(Debug, Clone, Default, Serialize, Deserialize)]
756pub struct ConsumedCapacity {
757 #[serde(rename = "TableName")]
758 pub table_name: String,
759 #[serde(rename = "CapacityUnits")]
760 pub capacity_units: f64,
761 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
762 pub read_capacity_units: Option<f64>,
763 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
764 pub write_capacity_units: Option<f64>,
765 #[serde(rename = "Table", skip_serializing_if = "Option::is_none")]
766 pub table: Option<CapacityDetail>,
767 #[serde(
768 rename = "GlobalSecondaryIndexes",
769 skip_serializing_if = "Option::is_none"
770 )]
771 pub global_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
772 #[serde(
773 rename = "LocalSecondaryIndexes",
774 skip_serializing_if = "Option::is_none"
775 )]
776 pub local_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
777}
778
779#[derive(Debug, Clone, Default, Serialize, Deserialize)]
781pub struct CapacityDetail {
782 #[serde(rename = "CapacityUnits")]
783 pub capacity_units: f64,
784 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
785 pub read_capacity_units: Option<f64>,
786 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
787 pub write_capacity_units: Option<f64>,
788}
789
790pub const TRANSACTIONAL_CAPACITY_FACTOR: f64 = 2.0;
795
796pub fn write_capacity_units(item_size_bytes: usize) -> f64 {
798 ((item_size_bytes as f64) / 1024.0).ceil().max(1.0)
799}
800
801pub fn read_capacity_units(item_size_bytes: usize) -> f64 {
805 ((item_size_bytes as f64) / 4096.0).ceil().max(1.0)
806}
807
808pub fn read_capacity_units_with_consistency(item_size_bytes: usize, consistent: bool) -> f64 {
813 let strongly = read_capacity_units(item_size_bytes);
814 if consistent { strongly } else { strongly / 2.0 }
815}
816
817pub fn consumed_capacity(
819 table_name: &str,
820 capacity_units: f64,
821 mode: &Option<String>,
822) -> Option<ConsumedCapacity> {
823 let mode = mode.as_deref().unwrap_or("NONE");
824 match mode {
825 "TOTAL" => Some(ConsumedCapacity {
826 table_name: table_name.to_string(),
827 capacity_units,
828 table: None,
829 global_secondary_indexes: None,
830 local_secondary_indexes: None,
831 ..Default::default()
832 }),
833 "INDEXES" => Some(ConsumedCapacity {
834 table_name: table_name.to_string(),
835 capacity_units,
836 table: Some(CapacityDetail {
837 capacity_units,
838 ..Default::default()
839 }),
840 global_secondary_indexes: None,
841 local_secondary_indexes: None,
842 ..Default::default()
843 }),
844 _ => None,
845 }
846}
847
848pub fn consumed_capacity_with_indexes(
850 table_name: &str,
851 table_units: f64,
852 gsi_units: &HashMap<String, f64>,
853 mode: &Option<String>,
854) -> Option<ConsumedCapacity> {
855 consumed_capacity_with_secondary_indexes(
856 table_name,
857 table_units,
858 gsi_units,
859 &HashMap::new(),
860 mode,
861 )
862}
863
864pub fn consumed_capacity_with_secondary_indexes(
866 table_name: &str,
867 table_units: f64,
868 gsi_units: &HashMap<String, f64>,
869 lsi_units: &HashMap<String, f64>,
870 mode: &Option<String>,
871) -> Option<ConsumedCapacity> {
872 let units_to_map = |units: &HashMap<String, f64>| -> Option<HashMap<String, CapacityDetail>> {
873 if units.is_empty() {
874 None
875 } else {
876 Some(
877 units
878 .iter()
879 .map(|(name, &u)| {
880 (
881 name.clone(),
882 CapacityDetail {
883 capacity_units: u,
884 ..Default::default()
885 },
886 )
887 })
888 .collect(),
889 )
890 }
891 };
892
893 match mode.as_deref().unwrap_or("NONE") {
894 "INDEXES" => {
895 let gsi_total: f64 = gsi_units.values().sum();
896 let lsi_total: f64 = lsi_units.values().sum();
897 Some(ConsumedCapacity {
898 table_name: table_name.to_string(),
899 capacity_units: table_units + gsi_total + lsi_total,
900 table: Some(CapacityDetail {
901 capacity_units: table_units,
902 ..Default::default()
903 }),
904 global_secondary_indexes: units_to_map(gsi_units),
905 local_secondary_indexes: units_to_map(lsi_units),
906 ..Default::default()
907 })
908 }
909 "TOTAL" => {
910 let gsi_total: f64 = gsi_units.values().sum();
911 let lsi_total: f64 = lsi_units.values().sum();
912 Some(ConsumedCapacity {
913 table_name: table_name.to_string(),
914 capacity_units: table_units + gsi_total + lsi_total,
915 table: None,
916 global_secondary_indexes: None,
917 local_secondary_indexes: None,
918 ..Default::default()
919 })
920 }
921 _ => None,
922 }
923}
924
925pub fn transactional_read_capacity(
930 table_name: &str,
931 units: f64,
932 mode: &Option<String>,
933) -> Option<ConsumedCapacity> {
934 match mode.as_deref().unwrap_or("NONE") {
935 "TOTAL" => Some(ConsumedCapacity {
936 table_name: table_name.to_string(),
937 capacity_units: units,
938 read_capacity_units: Some(units),
939 ..Default::default()
940 }),
941 "INDEXES" => Some(ConsumedCapacity {
942 table_name: table_name.to_string(),
943 capacity_units: units,
944 read_capacity_units: Some(units),
945 table: Some(CapacityDetail {
946 capacity_units: units,
947 read_capacity_units: Some(units),
948 ..Default::default()
949 }),
950 ..Default::default()
951 }),
952 _ => None,
953 }
954}
955
956pub fn build_transactional_capacity(
963 table_units: &HashMap<String, f64>,
964 mode: &Option<String>,
965 builder: fn(&str, f64, &Option<String>) -> Option<ConsumedCapacity>,
966) -> Option<Vec<ConsumedCapacity>> {
967 if matches!(mode.as_deref(), Some("TOTAL") | Some("INDEXES")) {
968 Some(
969 table_units
970 .iter()
971 .filter_map(|(table, &units)| builder(table, units, mode))
972 .collect(),
973 )
974 } else {
975 None
976 }
977}
978
979pub fn transactional_write_capacity(
984 table_name: &str,
985 units: f64,
986 mode: &Option<String>,
987) -> Option<ConsumedCapacity> {
988 match mode.as_deref().unwrap_or("NONE") {
989 "TOTAL" => Some(ConsumedCapacity {
990 table_name: table_name.to_string(),
991 capacity_units: units,
992 write_capacity_units: Some(units),
993 ..Default::default()
994 }),
995 "INDEXES" => Some(ConsumedCapacity {
996 table_name: table_name.to_string(),
997 capacity_units: units,
998 write_capacity_units: Some(units),
999 table: Some(CapacityDetail {
1000 capacity_units: units,
1001 write_capacity_units: Some(units),
1002 ..Default::default()
1003 }),
1004 ..Default::default()
1005 }),
1006 _ => None,
1007 }
1008}
1009
1010#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1012pub struct KeySchemaElement {
1013 #[serde(rename = "AttributeName", alias = "attribute_name")]
1014 pub attribute_name: String,
1015 #[serde(rename = "KeyType", alias = "key_type")]
1016 pub key_type: KeyType,
1017}
1018
1019#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1021pub enum KeyType {
1022 #[default]
1023 HASH,
1024 RANGE,
1025}
1026
1027#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1029pub struct AttributeDefinition {
1030 #[serde(rename = "AttributeName", alias = "attribute_name")]
1031 pub attribute_name: String,
1032 #[serde(rename = "AttributeType", alias = "attribute_type")]
1033 pub attribute_type: ScalarAttributeType,
1034}
1035
1036#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1038pub enum ScalarAttributeType {
1039 #[default]
1040 S,
1041 N,
1042 B,
1043}
1044
1045#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1047pub struct Projection {
1048 #[serde(
1049 rename = "ProjectionType",
1050 alias = "projection_type",
1051 default,
1052 skip_serializing_if = "Option::is_none"
1053 )]
1054 pub projection_type: Option<ProjectionType>,
1055 #[serde(
1056 rename = "NonKeyAttributes",
1057 alias = "non_key_attributes",
1058 skip_serializing_if = "Option::is_none"
1059 )]
1060 pub non_key_attributes: Option<Vec<String>>,
1061}
1062
1063#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1065#[allow(non_camel_case_types)]
1066pub enum ProjectionType {
1067 #[default]
1068 ALL,
1069 KEYS_ONLY,
1070 INCLUDE,
1071}
1072
1073#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1075pub struct GlobalSecondaryIndex {
1076 #[serde(rename = "IndexName", alias = "index_name")]
1077 pub index_name: String,
1078 #[serde(rename = "KeySchema", alias = "key_schema")]
1079 pub key_schema: Vec<KeySchemaElement>,
1080 #[serde(rename = "Projection", alias = "projection")]
1081 pub projection: Projection,
1082 #[serde(
1083 rename = "ProvisionedThroughput",
1084 alias = "provisioned_throughput",
1085 skip_serializing_if = "Option::is_none"
1086 )]
1087 pub provisioned_throughput: Option<ProvisionedThroughput>,
1088}
1089
1090#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1092pub struct LocalSecondaryIndex {
1093 #[serde(rename = "IndexName", alias = "index_name")]
1094 pub index_name: String,
1095 #[serde(rename = "KeySchema", alias = "key_schema")]
1096 pub key_schema: Vec<KeySchemaElement>,
1097 #[serde(rename = "Projection", alias = "projection")]
1098 pub projection: Projection,
1099}
1100
1101#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1103pub struct ProvisionedThroughput {
1104 #[serde(rename = "ReadCapacityUnits", alias = "read_capacity_units", default)]
1105 pub read_capacity_units: Option<i64>,
1106 #[serde(rename = "WriteCapacityUnits", alias = "write_capacity_units", default)]
1107 pub write_capacity_units: Option<i64>,
1108}
1109
1110#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1112pub struct OnDemandThroughput {
1113 #[serde(
1114 rename = "MaxReadRequestUnits",
1115 alias = "max_read_request_units",
1116 default,
1117 skip_serializing_if = "Option::is_none"
1118 )]
1119 pub max_read_request_units: Option<i64>,
1120 #[serde(
1121 rename = "MaxWriteRequestUnits",
1122 alias = "max_write_request_units",
1123 default,
1124 skip_serializing_if = "Option::is_none"
1125 )]
1126 pub max_write_request_units: Option<i64>,
1127}
1128
1129#[derive(Debug, Clone, PartialEq)]
1135pub struct ConversionError {
1136 pub expected: &'static str,
1138 pub actual: &'static str,
1140}
1141
1142impl fmt::Display for ConversionError {
1143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1144 write!(f, "expected {}, got {}", self.expected, self.actual)
1145 }
1146}
1147
1148impl std::error::Error for ConversionError {}
1149
1150impl From<String> for AttributeValue {
1153 fn from(value: String) -> Self {
1154 AttributeValue::S(value)
1155 }
1156}
1157
1158impl From<&str> for AttributeValue {
1159 fn from(value: &str) -> Self {
1160 AttributeValue::S(value.to_string())
1161 }
1162}
1163
1164impl From<bool> for AttributeValue {
1165 fn from(value: bool) -> Self {
1166 AttributeValue::BOOL(value)
1167 }
1168}
1169
1170impl From<Vec<u8>> for AttributeValue {
1171 fn from(value: Vec<u8>) -> Self {
1172 AttributeValue::B(value)
1173 }
1174}
1175
1176impl From<&[u8]> for AttributeValue {
1177 fn from(value: &[u8]) -> Self {
1178 AttributeValue::B(value.to_vec())
1179 }
1180}
1181
1182macro_rules! impl_from_integer {
1184 ($($t:ty),+) => {
1185 $(
1186 impl From<$t> for AttributeValue {
1187 fn from(value: $t) -> Self {
1188 AttributeValue::N(value.to_string())
1189 }
1190 }
1191 )+
1192 };
1193}
1194
1195impl_from_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1196
1197impl From<HashMap<String, AttributeValue>> for AttributeValue {
1199 fn from(value: HashMap<String, AttributeValue>) -> Self {
1200 AttributeValue::M(value)
1201 }
1202}
1203
1204impl From<Vec<AttributeValue>> for AttributeValue {
1205 fn from(value: Vec<AttributeValue>) -> Self {
1206 AttributeValue::L(value)
1207 }
1208}
1209
1210impl From<HashSet<String>> for AttributeValue {
1211 fn from(value: HashSet<String>) -> Self {
1212 AttributeValue::SS(value.into_iter().collect())
1213 }
1214}
1215
1216impl From<BTreeSet<String>> for AttributeValue {
1217 fn from(value: BTreeSet<String>) -> Self {
1218 AttributeValue::SS(value.into_iter().collect())
1219 }
1220}
1221
1222impl TryFrom<f64> for AttributeValue {
1225 type Error = ConversionError;
1226
1227 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
1228 if value.is_finite() {
1229 Ok(AttributeValue::N(value.to_string()))
1230 } else {
1231 Err(ConversionError {
1232 expected: "finite f64",
1233 actual: "NaN or Infinity",
1234 })
1235 }
1236 }
1237}
1238
1239impl TryFrom<f32> for AttributeValue {
1240 type Error = ConversionError;
1241
1242 fn try_from(value: f32) -> std::result::Result<Self, Self::Error> {
1243 if value.is_finite() {
1244 Ok(AttributeValue::N(value.to_string()))
1245 } else {
1246 Err(ConversionError {
1247 expected: "finite f32",
1248 actual: "NaN or Infinity",
1249 })
1250 }
1251 }
1252}
1253
1254impl TryFrom<AttributeValue> for String {
1257 type Error = ConversionError;
1258
1259 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1260 match value {
1261 AttributeValue::S(s) => Ok(s),
1262 other => Err(ConversionError {
1263 expected: "S",
1264 actual: other.type_name(),
1265 }),
1266 }
1267 }
1268}
1269
1270impl TryFrom<AttributeValue> for bool {
1271 type Error = ConversionError;
1272
1273 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1274 match value {
1275 AttributeValue::BOOL(b) => Ok(b),
1276 other => Err(ConversionError {
1277 expected: "BOOL",
1278 actual: other.type_name(),
1279 }),
1280 }
1281 }
1282}
1283
1284impl TryFrom<AttributeValue> for Vec<u8> {
1285 type Error = ConversionError;
1286
1287 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1288 match value {
1289 AttributeValue::B(b) => Ok(b),
1290 other => Err(ConversionError {
1291 expected: "B",
1292 actual: other.type_name(),
1293 }),
1294 }
1295 }
1296}
1297
1298macro_rules! impl_try_from_av_integer {
1299 ($($t:ty),+) => {
1300 $(
1301 impl TryFrom<AttributeValue> for $t {
1302 type Error = ConversionError;
1303
1304 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1305 match value {
1306 AttributeValue::N(n) => n.parse::<$t>().map_err(|_| ConversionError {
1307 expected: stringify!($t),
1308 actual: "N (parse failed)",
1309 }),
1310 other => Err(ConversionError {
1311 expected: "N",
1312 actual: other.type_name(),
1313 }),
1314 }
1315 }
1316 }
1317 )+
1318 };
1319}
1320
1321impl_try_from_av_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1322
1323impl TryFrom<AttributeValue> for f64 {
1324 type Error = ConversionError;
1325
1326 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1327 match value {
1328 AttributeValue::N(n) => n.parse::<f64>().map_err(|_| ConversionError {
1329 expected: "f64",
1330 actual: "N (parse failed)",
1331 }),
1332 other => Err(ConversionError {
1333 expected: "N",
1334 actual: other.type_name(),
1335 }),
1336 }
1337 }
1338}
1339
1340impl TryFrom<AttributeValue> for f32 {
1341 type Error = ConversionError;
1342
1343 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1344 match value {
1345 AttributeValue::N(n) => n.parse::<f32>().map_err(|_| ConversionError {
1346 expected: "f32",
1347 actual: "N (parse failed)",
1348 }),
1349 other => Err(ConversionError {
1350 expected: "N",
1351 actual: other.type_name(),
1352 }),
1353 }
1354 }
1355}
1356
1357impl TryFrom<AttributeValue> for HashMap<String, AttributeValue> {
1358 type Error = ConversionError;
1359
1360 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1361 match value {
1362 AttributeValue::M(m) => Ok(m),
1363 other => Err(ConversionError {
1364 expected: "M",
1365 actual: other.type_name(),
1366 }),
1367 }
1368 }
1369}
1370
1371impl TryFrom<AttributeValue> for Vec<AttributeValue> {
1372 type Error = ConversionError;
1373
1374 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1375 match value {
1376 AttributeValue::L(l) => Ok(l),
1377 other => Err(ConversionError {
1378 expected: "L",
1379 actual: other.type_name(),
1380 }),
1381 }
1382 }
1383}
1384
1385impl TryFrom<AttributeValue> for Vec<String> {
1386 type Error = ConversionError;
1387
1388 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1389 match value {
1390 AttributeValue::SS(ss) => Ok(ss),
1391 AttributeValue::L(l) => {
1392 l.into_iter()
1394 .map(|av| match av {
1395 AttributeValue::S(s) => Ok(s),
1396 other => Err(ConversionError {
1397 expected: "S (within L)",
1398 actual: other.type_name(),
1399 }),
1400 })
1401 .collect()
1402 }
1403 other => Err(ConversionError {
1404 expected: "SS or L",
1405 actual: other.type_name(),
1406 }),
1407 }
1408 }
1409}
1410
1411#[cfg(test)]
1412mod tests {
1413 use super::*;
1414
1415 #[test]
1416 fn test_serialize_string() {
1417 let val = AttributeValue::S("hello".to_string());
1418 let json = serde_json::to_string(&val).unwrap();
1419 assert_eq!(json, r#"{"S":"hello"}"#);
1420 }
1421
1422 #[test]
1423 fn test_serialize_number() {
1424 let val = AttributeValue::N("42".to_string());
1425 let json = serde_json::to_string(&val).unwrap();
1426 assert_eq!(json, r#"{"N":"42"}"#);
1427 }
1428
1429 #[test]
1430 fn test_serialize_binary() {
1431 let val = AttributeValue::B(vec![1, 2, 3]);
1432 let json = serde_json::to_string(&val).unwrap();
1433 assert_eq!(json, r#"{"B":"AQID"}"#);
1434 }
1435
1436 #[test]
1437 fn test_serialize_bool() {
1438 let val = AttributeValue::BOOL(true);
1439 let json = serde_json::to_string(&val).unwrap();
1440 assert_eq!(json, r#"{"BOOL":true}"#);
1441 }
1442
1443 #[test]
1444 fn test_serialize_null() {
1445 let val = AttributeValue::NULL(true);
1446 let json = serde_json::to_string(&val).unwrap();
1447 assert_eq!(json, r#"{"NULL":true}"#);
1448 }
1449
1450 #[test]
1451 fn test_deserialize_null_true() {
1452 let val: AttributeValue = serde_json::from_str(r#"{"NULL":true}"#).unwrap();
1453 assert_eq!(val, AttributeValue::NULL(true));
1454 }
1455
1456 #[test]
1457 fn test_deserialize_null_false_rejected() {
1458 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":false}"#).unwrap_err();
1461 assert!(
1462 err.to_string().contains("must have the value of true"),
1463 "unexpected error: {err}"
1464 );
1465 }
1466
1467 #[test]
1468 fn test_deserialize_null_non_boolean_rejected() {
1469 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":"no"}"#).unwrap_err();
1471 assert!(
1472 err.to_string().contains("must have the value of true"),
1473 "unexpected error: {err}"
1474 );
1475 }
1476
1477 #[test]
1478 fn test_serialize_string_set() {
1479 let val = AttributeValue::SS(vec!["a".to_string(), "b".to_string()]);
1480 let json = serde_json::to_string(&val).unwrap();
1481 assert_eq!(json, r#"{"SS":["a","b"]}"#);
1482 }
1483
1484 #[test]
1485 fn test_serialize_list() {
1486 let val = AttributeValue::L(vec![
1487 AttributeValue::S("hello".to_string()),
1488 AttributeValue::N("42".to_string()),
1489 ]);
1490 let json = serde_json::to_string(&val).unwrap();
1491 assert_eq!(json, r#"{"L":[{"S":"hello"},{"N":"42"}]}"#);
1492 }
1493
1494 #[test]
1495 fn test_serialize_map() {
1496 let mut m = HashMap::new();
1497 m.insert("key".to_string(), AttributeValue::S("value".to_string()));
1498 let val = AttributeValue::M(m);
1499 let json = serde_json::to_string(&val).unwrap();
1500 assert_eq!(json, r#"{"M":{"key":{"S":"value"}}}"#);
1501 }
1502
1503 #[test]
1504 fn test_round_trip_all_types() {
1505 let values = vec![
1506 AttributeValue::S("hello".to_string()),
1507 AttributeValue::N("42.5".to_string()),
1508 AttributeValue::B(vec![0, 255, 128]),
1509 AttributeValue::BOOL(false),
1510 AttributeValue::NULL(true),
1511 AttributeValue::SS(vec!["x".to_string(), "y".to_string()]),
1512 AttributeValue::NS(vec!["1".to_string(), "2.5".to_string()]),
1513 AttributeValue::BS(vec![vec![1], vec![2, 3]]),
1514 AttributeValue::L(vec![
1515 AttributeValue::S("nested".to_string()),
1516 AttributeValue::N("99".to_string()),
1517 ]),
1518 ];
1519
1520 for val in values {
1521 let json = serde_json::to_string(&val).unwrap();
1522 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
1523 assert_eq!(val, deserialized, "Round-trip failed for {json}");
1524 }
1525 }
1526
1527 #[test]
1528 fn test_size_string() {
1529 let val = AttributeValue::S("hello".to_string());
1530 assert_eq!(val.size(), 5);
1531 }
1532
1533 #[test]
1534 fn test_size_number() {
1535 let val = AttributeValue::N("42".to_string());
1537 assert_eq!(val.size(), 2);
1538 }
1539
1540 #[test]
1541 fn test_size_bool() {
1542 assert_eq!(AttributeValue::BOOL(true).size(), 1);
1543 }
1544
1545 #[test]
1546 fn test_size_null() {
1547 assert_eq!(AttributeValue::NULL(true).size(), 1);
1548 }
1549
1550 #[test]
1551 fn test_key_string_s() {
1552 let val = AttributeValue::S("hello".to_string());
1553 assert_eq!(val.to_key_string(), Some("S:hello".to_string()));
1554 }
1555
1556 #[test]
1557 fn test_key_string_n() {
1558 let val = AttributeValue::N("42".to_string());
1559 let key = val.to_key_string().unwrap();
1560 assert!(key.starts_with("N:"));
1561 }
1562
1563 #[test]
1564 fn test_key_string_b() {
1565 let val = AttributeValue::B(vec![0xff, 0x00, 0xab]);
1566 assert_eq!(val.to_key_string(), Some("B:ff00ab".to_string()));
1567 }
1568
1569 #[test]
1570 fn test_key_string_non_key_type_returns_none() {
1571 assert_eq!(AttributeValue::BOOL(true).to_key_string(), None);
1572 assert_eq!(AttributeValue::L(vec![]).to_key_string(), None);
1573 }
1574
1575 #[test]
1577 fn test_number_sort_ordering() {
1578 let numbers = vec![
1579 "-1000", "-100", "-10", "-1", "-0.5", "-0.001", "0", "0.001", "0.5", "1", "10", "100",
1580 "1000",
1581 ];
1582 let encoded: Vec<String> = numbers
1583 .iter()
1584 .map(|n| normalize_number_for_sort(n))
1585 .collect();
1586
1587 for i in 0..encoded.len() - 1 {
1588 assert!(
1589 encoded[i] < encoded[i + 1],
1590 "Sort order broken: {} ({}) should be < {} ({})",
1591 numbers[i],
1592 encoded[i],
1593 numbers[i + 1],
1594 encoded[i + 1]
1595 );
1596 }
1597 }
1598
1599 #[test]
1600 fn test_number_sort_zero_variants() {
1601 let z1 = normalize_number_for_sort("0");
1602 let z2 = normalize_number_for_sort("-0");
1603 let z3 = normalize_number_for_sort("0.0");
1604 assert_eq!(z1, z2);
1605 assert_eq!(z2, z3);
1606 }
1607
1608 #[test]
1609 fn test_number_sort_decimals() {
1610 let a = normalize_number_for_sort("1.5");
1611 let b = normalize_number_for_sort("2.5");
1612 assert!(a < b);
1613
1614 let c = normalize_number_for_sort("0.001");
1615 let d = normalize_number_for_sort("0.01");
1616 assert!(c < d);
1617 }
1618
1619 #[test]
1620 fn test_number_sort_scientific() {
1621 let a = normalize_number_for_sort("1e10");
1622 let b = normalize_number_for_sort("1e11");
1623 assert!(a < b);
1624
1625 let c = normalize_number_for_sort("-1e11");
1626 let d = normalize_number_for_sort("-1e10");
1627 assert!(c < d);
1628 }
1629
1630 #[test]
1635 fn test_validate_number_accepts_dynamodb_grammar() {
1636 for input in [
1637 "+5", "+1.5", "+0", "-0", "+0.0", "+1e2", "1e+2", "1.5E+3", "-7", "+.5", ".5", "5.",
1638 "00042", "1.23E10", "+1e-2", "1E-130", "+1E-130",
1639 ] {
1640 assert!(
1641 validate_dynamo_number(input).is_ok(),
1642 "expected {input} to validate, got {:?}",
1643 validate_dynamo_number(input)
1644 );
1645 }
1646 }
1647
1648 #[test]
1649 fn test_validate_number_rejects_malformed() {
1650 for input in [
1653 "+e2", "e2", "+1+2", "1+2", "+1.2.3", "1.2.3", "++5", "+-5", "-+5", "+", "-", "1e",
1654 "1e+", ".", "1.2e3.4", "0x5", "NaN", "Infinity", "1_000", " 5", "5 ", "1 5", "",
1655 ] {
1656 assert!(
1657 matches!(
1658 validate_dynamo_number(input),
1659 Err(crate::errors::DynoxideError::ValidationException(_))
1660 ),
1661 "expected {input:?} to be rejected with ValidationException, got {:?}",
1662 validate_dynamo_number(input)
1663 );
1664 }
1665 }
1666
1667 #[test]
1668 fn test_normalize_number_matches_dynamodb() {
1669 for (input, stored) in [
1670 ("+5", "5"),
1671 ("+1.5", "1.5"),
1672 ("+0", "0"),
1673 ("-0", "0"),
1674 ("+0.0", "0"),
1675 ("+1e2", "100"),
1676 ("1e+2", "100"),
1677 ("1.5E+3", "1500"),
1678 ("-7", "-7"),
1679 ("+.5", "0.5"),
1680 (".5", "0.5"),
1681 ("5.", "5"),
1682 ("00042", "42"),
1683 ("1.23E10", "12300000000"),
1684 ("+1e-2", "0.01"),
1685 ] {
1686 assert_eq!(
1687 normalize_dynamo_number(input),
1688 stored,
1689 "{input} should normalise to {stored}"
1690 );
1691 }
1692 }
1693
1694 #[test]
1695 fn test_type_name() {
1696 assert_eq!(AttributeValue::S("".to_string()).type_name(), "S");
1697 assert_eq!(AttributeValue::N("0".to_string()).type_name(), "N");
1698 assert_eq!(AttributeValue::B(vec![]).type_name(), "B");
1699 assert_eq!(AttributeValue::BOOL(true).type_name(), "BOOL");
1700 assert_eq!(AttributeValue::NULL(true).type_name(), "NULL");
1701 assert_eq!(AttributeValue::SS(vec![]).type_name(), "SS");
1702 assert_eq!(AttributeValue::NS(vec![]).type_name(), "NS");
1703 assert_eq!(AttributeValue::BS(vec![]).type_name(), "BS");
1704 assert_eq!(AttributeValue::L(vec![]).type_name(), "L");
1705 assert_eq!(AttributeValue::M(HashMap::new()).type_name(), "M");
1706 }
1707}