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 if n.is_empty() {
363 return Err("VALIDATION:The parameter cannot be converted to a numeric value".to_string());
364 }
365 let trimmed = n.trim();
367 let is_valid = trimmed.parse::<f64>().is_ok()
368 || trimmed
369 .to_lowercase()
370 .contains('e')
371 .then(|| trimmed.parse::<f64>().ok())
372 .is_some();
373 if !is_valid {
374 return Err(format!(
375 "VALIDATION:The parameter cannot be converted to a numeric value: {n}"
376 ));
377 }
378 if let Err(e) = validate_dynamo_number(n) {
380 let msg = match e {
381 crate::errors::DynoxideError::ValidationException(m) => format!("VALIDATION:{m}"),
382 _ => format!("VALIDATION:{}", e),
383 };
384 return Err(msg);
385 }
386 Ok(())
387}
388
389pub fn normalize_number_for_sort(num_str: &str) -> String {
403 let trimmed = num_str.trim();
404
405 if trimmed.is_empty() || trimmed == "0" || trimmed == "-0" || trimmed == "0.0" {
406 return zero_encoding();
407 }
408
409 let negative = trimmed.starts_with('-');
410 let abs_str = if negative { &trimmed[1..] } else { trimmed };
411
412 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
414
415 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
416 return zero_encoding();
417 }
418
419 if negative {
420 encode_negative(&mantissa_digits, exponent)
421 } else {
422 encode_positive(&mantissa_digits, exponent)
423 }
424}
425
426pub fn validate_dynamo_number(
432 num_str: &str,
433) -> std::result::Result<(), crate::errors::DynoxideError> {
434 let trimmed = num_str.trim();
435
436 if trimmed.is_empty() {
437 return Err(crate::errors::DynoxideError::ValidationException(
438 "The parameter cannot be converted to a numeric value".to_string(),
439 ));
440 }
441
442 let negative = trimmed.starts_with('-');
443 let abs_str = if negative { &trimmed[1..] } else { trimmed };
444
445 if abs_str.is_empty() || !abs_str.chars().any(|c| c.is_ascii_digit()) {
449 return Err(crate::errors::DynoxideError::ValidationException(format!(
450 "The parameter cannot be converted to a numeric value: {}",
451 trimmed
452 )));
453 }
454 let valid = abs_str.chars().enumerate().all(|(i, c)| {
455 c.is_ascii_digit() || c == '.' || c == 'e' || c == 'E' || ((c == '+' || c == '-') && i > 0) });
457 if !valid {
458 return Err(crate::errors::DynoxideError::ValidationException(format!(
459 "The parameter cannot be converted to a numeric value: {}",
460 trimmed
461 )));
462 }
463
464 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
465
466 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
468 return Ok(());
469 }
470
471 if mantissa_digits.len() > 38 {
473 return Err(crate::errors::DynoxideError::ValidationException(
474 "Attempting to store more than 38 significant digits in a Number".to_string(),
475 ));
476 }
477
478 if exponent > 126 {
481 return Err(crate::errors::DynoxideError::ValidationException(
482 "Number overflow. Attempting to store a number with magnitude larger than supported range"
483 .to_string(),
484 ));
485 }
486
487 if exponent < -129 {
494 return Err(crate::errors::DynoxideError::ValidationException(
495 "Number underflow. Attempting to store a number with magnitude smaller than supported range"
496 .to_string(),
497 ));
498 }
499
500 Ok(())
501}
502
503pub fn normalize_dynamo_number(num_str: &str) -> String {
511 let trimmed = num_str.trim();
512 if trimmed.is_empty() {
513 return "0".to_string();
514 }
515
516 let negative = trimmed.starts_with('-');
517 let abs_str = if negative {
518 &trimmed[1..]
519 } else {
520 trimmed.trim_start_matches('+')
521 };
522
523 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
524
525 if mantissa_digits.is_empty() {
527 return "0".to_string();
528 }
529
530 let num_digits = mantissa_digits.len() as i32;
535 let int_digits = exponent; let mut result = String::new();
538 if negative {
539 result.push('-');
540 }
541
542 if int_digits <= 0 {
543 result.push_str("0.");
545 for _ in 0..(-int_digits) {
546 result.push('0');
547 }
548 for &d in &mantissa_digits {
549 result.push((b'0' + d) as char);
550 }
551 } else if int_digits >= num_digits {
552 for &d in &mantissa_digits {
554 result.push((b'0' + d) as char);
555 }
556 for _ in 0..(int_digits - num_digits) {
557 result.push('0');
558 }
559 } else {
560 let int_part = int_digits as usize;
562 for &d in &mantissa_digits[..int_part] {
563 result.push((b'0' + d) as char);
564 }
565 result.push('.');
566 for &d in &mantissa_digits[int_part..] {
567 result.push((b'0' + d) as char);
568 }
569 }
570
571 result
572}
573
574fn zero_encoding() -> String {
575 format!("1{}{}", "0".repeat(4), "0".repeat(40))
577}
578
579fn encode_positive(mantissa: &[u8], exponent: i32) -> String {
580 let exp_encoded = (exponent + 5000) as u16;
581 let mantissa_str = mantissa_to_string(mantissa, 40);
582 format!("2{exp_encoded:04}{mantissa_str}")
583}
584
585fn encode_negative(mantissa: &[u8], exponent: i32) -> String {
586 let exp_encoded = 9999 - (exponent + 5000) as u16;
588 let mantissa_str = complement_mantissa(mantissa, 40);
589 format!("0{exp_encoded:04}{mantissa_str}")
590}
591
592pub(crate) fn parse_number_parts(s: &str) -> (Vec<u8>, i32) {
596 let (coeff, exp_part) = if let Some(pos) = s.to_ascii_lowercase().find('e') {
598 let coeff = &s[..pos];
599 let exp: i32 = s[pos + 1..].parse().unwrap_or(0);
600 (coeff, exp)
601 } else {
602 (s, 0)
603 };
604
605 let (int_part, frac_part) = if let Some(dot) = coeff.find('.') {
607 (&coeff[..dot], &coeff[dot + 1..])
608 } else {
609 (coeff, "")
610 };
611
612 let mut digits: Vec<u8> = Vec::new();
614 for ch in int_part.chars().chain(frac_part.chars()) {
615 if ch.is_ascii_digit() {
616 digits.push(ch as u8 - b'0');
617 }
618 }
619
620 if digits.is_empty() {
621 return (vec![], 0);
622 }
623
624 let int_len = int_part.chars().filter(|c| c.is_ascii_digit()).count() as i32;
626
627 let leading_zeros = digits.iter().take_while(|&&d| d == 0).count();
629 digits.drain(..leading_zeros);
630
631 while digits.last() == Some(&0) {
633 digits.pop();
634 }
635
636 if digits.is_empty() {
637 return (vec![], 0);
638 }
639
640 let exponent = int_len - leading_zeros as i32 + exp_part;
643
644 (digits, exponent)
645}
646
647fn mantissa_to_string(digits: &[u8], width: usize) -> String {
648 let mut s = String::with_capacity(width);
649 for &d in digits.iter().take(width) {
650 s.push((b'0' + d) as char);
651 }
652 while s.len() < width {
653 s.push('0');
654 }
655 s
656}
657
658fn complement_mantissa(digits: &[u8], width: usize) -> String {
659 let mut s = String::with_capacity(width);
660 for i in 0..width {
661 let d = if i < digits.len() { digits[i] } else { 0 };
662 s.push((b'0' + (9 - d)) as char);
663 }
664 s
665}
666
667fn hex_encode(bytes: &[u8]) -> String {
669 let mut s = String::with_capacity(bytes.len() * 2);
670 for &b in bytes {
671 s.push_str(&format!("{b:02x}"));
672 }
673 s
674}
675
676pub type Item = HashMap<String, AttributeValue>;
682
683#[derive(Debug, Clone, Default, Serialize, Deserialize)]
685pub struct SseSpecification {
686 #[serde(rename = "Enabled", default)]
687 pub enabled: Option<bool>,
688 #[serde(rename = "SSEType", default)]
689 pub sse_type: Option<String>,
690 #[serde(rename = "KMSMasterKeyId", default)]
691 pub kms_master_key_id: Option<String>,
692}
693
694#[derive(Debug, Clone, Default, Serialize, Deserialize)]
696pub struct Tag {
697 #[serde(rename = "Key")]
698 pub key: String,
699 #[serde(rename = "Value")]
700 pub value: String,
701}
702
703pub fn item_size(item: &Item) -> usize {
705 item.iter()
706 .map(|(name, value)| name.len() + value.size())
707 .sum()
708}
709
710pub const MAX_ITEM_SIZE: usize = 400 * 1024;
712
713#[derive(Debug, Clone, Serialize, Deserialize)]
716pub struct ItemCollectionMetrics {
717 #[serde(rename = "ItemCollectionKey")]
718 pub item_collection_key: HashMap<String, AttributeValue>,
719 #[serde(rename = "SizeEstimateRangeGB")]
720 pub size_estimate_range_gb: Vec<f64>,
721}
722
723#[derive(Debug, Clone, Default, Serialize, Deserialize)]
725pub struct ConsumedCapacity {
726 #[serde(rename = "TableName")]
727 pub table_name: String,
728 #[serde(rename = "CapacityUnits")]
729 pub capacity_units: f64,
730 #[serde(rename = "Table", skip_serializing_if = "Option::is_none")]
731 pub table: Option<CapacityDetail>,
732 #[serde(
733 rename = "GlobalSecondaryIndexes",
734 skip_serializing_if = "Option::is_none"
735 )]
736 pub global_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
737 #[serde(
738 rename = "LocalSecondaryIndexes",
739 skip_serializing_if = "Option::is_none"
740 )]
741 pub local_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
742}
743
744#[derive(Debug, Clone, Default, Serialize, Deserialize)]
746pub struct CapacityDetail {
747 #[serde(rename = "CapacityUnits")]
748 pub capacity_units: f64,
749 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
750 pub read_capacity_units: Option<f64>,
751 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
752 pub write_capacity_units: Option<f64>,
753}
754
755pub const TRANSACTIONAL_CAPACITY_FACTOR: f64 = 2.0;
760
761pub fn write_capacity_units(item_size_bytes: usize) -> f64 {
763 ((item_size_bytes as f64) / 1024.0).ceil().max(1.0)
764}
765
766pub fn read_capacity_units(item_size_bytes: usize) -> f64 {
770 ((item_size_bytes as f64) / 4096.0).ceil().max(1.0)
771}
772
773pub fn read_capacity_units_with_consistency(item_size_bytes: usize, consistent: bool) -> f64 {
778 let strongly = read_capacity_units(item_size_bytes);
779 if consistent { strongly } else { strongly / 2.0 }
780}
781
782pub fn consumed_capacity(
784 table_name: &str,
785 capacity_units: f64,
786 mode: &Option<String>,
787) -> Option<ConsumedCapacity> {
788 let mode = mode.as_deref().unwrap_or("NONE");
789 match mode {
790 "TOTAL" => Some(ConsumedCapacity {
791 table_name: table_name.to_string(),
792 capacity_units,
793 table: None,
794 global_secondary_indexes: None,
795 local_secondary_indexes: None,
796 }),
797 "INDEXES" => Some(ConsumedCapacity {
798 table_name: table_name.to_string(),
799 capacity_units,
800 table: Some(CapacityDetail {
801 capacity_units,
802 ..Default::default()
803 }),
804 global_secondary_indexes: None,
805 local_secondary_indexes: None,
806 }),
807 _ => None,
808 }
809}
810
811pub fn consumed_capacity_with_indexes(
813 table_name: &str,
814 table_units: f64,
815 gsi_units: &HashMap<String, f64>,
816 mode: &Option<String>,
817) -> Option<ConsumedCapacity> {
818 consumed_capacity_with_secondary_indexes(
819 table_name,
820 table_units,
821 gsi_units,
822 &HashMap::new(),
823 mode,
824 )
825}
826
827pub fn consumed_capacity_with_secondary_indexes(
829 table_name: &str,
830 table_units: f64,
831 gsi_units: &HashMap<String, f64>,
832 lsi_units: &HashMap<String, f64>,
833 mode: &Option<String>,
834) -> Option<ConsumedCapacity> {
835 let units_to_map = |units: &HashMap<String, f64>| -> Option<HashMap<String, CapacityDetail>> {
836 if units.is_empty() {
837 None
838 } else {
839 Some(
840 units
841 .iter()
842 .map(|(name, &u)| {
843 (
844 name.clone(),
845 CapacityDetail {
846 capacity_units: u,
847 ..Default::default()
848 },
849 )
850 })
851 .collect(),
852 )
853 }
854 };
855
856 match mode.as_deref().unwrap_or("NONE") {
857 "INDEXES" => {
858 let gsi_total: f64 = gsi_units.values().sum();
859 let lsi_total: f64 = lsi_units.values().sum();
860 Some(ConsumedCapacity {
861 table_name: table_name.to_string(),
862 capacity_units: table_units + gsi_total + lsi_total,
863 table: Some(CapacityDetail {
864 capacity_units: table_units,
865 ..Default::default()
866 }),
867 global_secondary_indexes: units_to_map(gsi_units),
868 local_secondary_indexes: units_to_map(lsi_units),
869 })
870 }
871 "TOTAL" => {
872 let gsi_total: f64 = gsi_units.values().sum();
873 let lsi_total: f64 = lsi_units.values().sum();
874 Some(ConsumedCapacity {
875 table_name: table_name.to_string(),
876 capacity_units: table_units + gsi_total + lsi_total,
877 table: None,
878 global_secondary_indexes: None,
879 local_secondary_indexes: None,
880 })
881 }
882 _ => None,
883 }
884}
885
886pub fn transactional_read_capacity(
891 table_name: &str,
892 units: f64,
893 mode: &Option<String>,
894) -> Option<ConsumedCapacity> {
895 match mode.as_deref().unwrap_or("NONE") {
896 "TOTAL" => Some(ConsumedCapacity {
897 table_name: table_name.to_string(),
898 capacity_units: units,
899 ..Default::default()
900 }),
901 "INDEXES" => Some(ConsumedCapacity {
902 table_name: table_name.to_string(),
903 capacity_units: units,
904 table: Some(CapacityDetail {
905 capacity_units: units,
906 read_capacity_units: Some(units),
907 ..Default::default()
908 }),
909 ..Default::default()
910 }),
911 _ => None,
912 }
913}
914
915pub fn transactional_write_capacity(
920 table_name: &str,
921 units: f64,
922 mode: &Option<String>,
923) -> Option<ConsumedCapacity> {
924 match mode.as_deref().unwrap_or("NONE") {
925 "TOTAL" => Some(ConsumedCapacity {
926 table_name: table_name.to_string(),
927 capacity_units: units,
928 ..Default::default()
929 }),
930 "INDEXES" => Some(ConsumedCapacity {
931 table_name: table_name.to_string(),
932 capacity_units: units,
933 table: Some(CapacityDetail {
934 capacity_units: units,
935 write_capacity_units: Some(units),
936 ..Default::default()
937 }),
938 ..Default::default()
939 }),
940 _ => None,
941 }
942}
943
944#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
946pub struct KeySchemaElement {
947 #[serde(rename = "AttributeName", alias = "attribute_name")]
948 pub attribute_name: String,
949 #[serde(rename = "KeyType", alias = "key_type")]
950 pub key_type: KeyType,
951}
952
953#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
955pub enum KeyType {
956 #[default]
957 HASH,
958 RANGE,
959}
960
961#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
963pub struct AttributeDefinition {
964 #[serde(rename = "AttributeName", alias = "attribute_name")]
965 pub attribute_name: String,
966 #[serde(rename = "AttributeType", alias = "attribute_type")]
967 pub attribute_type: ScalarAttributeType,
968}
969
970#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
972pub enum ScalarAttributeType {
973 #[default]
974 S,
975 N,
976 B,
977}
978
979#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
981pub struct Projection {
982 #[serde(
983 rename = "ProjectionType",
984 alias = "projection_type",
985 default,
986 skip_serializing_if = "Option::is_none"
987 )]
988 pub projection_type: Option<ProjectionType>,
989 #[serde(
990 rename = "NonKeyAttributes",
991 alias = "non_key_attributes",
992 skip_serializing_if = "Option::is_none"
993 )]
994 pub non_key_attributes: Option<Vec<String>>,
995}
996
997#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
999#[allow(non_camel_case_types)]
1000pub enum ProjectionType {
1001 #[default]
1002 ALL,
1003 KEYS_ONLY,
1004 INCLUDE,
1005}
1006
1007#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1009pub struct GlobalSecondaryIndex {
1010 #[serde(rename = "IndexName", alias = "index_name")]
1011 pub index_name: String,
1012 #[serde(rename = "KeySchema", alias = "key_schema")]
1013 pub key_schema: Vec<KeySchemaElement>,
1014 #[serde(rename = "Projection", alias = "projection")]
1015 pub projection: Projection,
1016 #[serde(
1017 rename = "ProvisionedThroughput",
1018 alias = "provisioned_throughput",
1019 skip_serializing_if = "Option::is_none"
1020 )]
1021 pub provisioned_throughput: Option<ProvisionedThroughput>,
1022}
1023
1024#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1026pub struct LocalSecondaryIndex {
1027 #[serde(rename = "IndexName", alias = "index_name")]
1028 pub index_name: String,
1029 #[serde(rename = "KeySchema", alias = "key_schema")]
1030 pub key_schema: Vec<KeySchemaElement>,
1031 #[serde(rename = "Projection", alias = "projection")]
1032 pub projection: Projection,
1033}
1034
1035#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1037pub struct ProvisionedThroughput {
1038 #[serde(rename = "ReadCapacityUnits", default)]
1039 pub read_capacity_units: Option<i64>,
1040 #[serde(rename = "WriteCapacityUnits", default)]
1041 pub write_capacity_units: Option<i64>,
1042}
1043
1044#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1046pub struct OnDemandThroughput {
1047 #[serde(
1048 rename = "MaxReadRequestUnits",
1049 default,
1050 skip_serializing_if = "Option::is_none"
1051 )]
1052 pub max_read_request_units: Option<i64>,
1053 #[serde(
1054 rename = "MaxWriteRequestUnits",
1055 default,
1056 skip_serializing_if = "Option::is_none"
1057 )]
1058 pub max_write_request_units: Option<i64>,
1059}
1060
1061#[derive(Debug, Clone, PartialEq)]
1067pub struct ConversionError {
1068 pub expected: &'static str,
1070 pub actual: &'static str,
1072}
1073
1074impl fmt::Display for ConversionError {
1075 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1076 write!(f, "expected {}, got {}", self.expected, self.actual)
1077 }
1078}
1079
1080impl std::error::Error for ConversionError {}
1081
1082impl From<String> for AttributeValue {
1085 fn from(value: String) -> Self {
1086 AttributeValue::S(value)
1087 }
1088}
1089
1090impl From<&str> for AttributeValue {
1091 fn from(value: &str) -> Self {
1092 AttributeValue::S(value.to_string())
1093 }
1094}
1095
1096impl From<bool> for AttributeValue {
1097 fn from(value: bool) -> Self {
1098 AttributeValue::BOOL(value)
1099 }
1100}
1101
1102impl From<Vec<u8>> for AttributeValue {
1103 fn from(value: Vec<u8>) -> Self {
1104 AttributeValue::B(value)
1105 }
1106}
1107
1108impl From<&[u8]> for AttributeValue {
1109 fn from(value: &[u8]) -> Self {
1110 AttributeValue::B(value.to_vec())
1111 }
1112}
1113
1114macro_rules! impl_from_integer {
1116 ($($t:ty),+) => {
1117 $(
1118 impl From<$t> for AttributeValue {
1119 fn from(value: $t) -> Self {
1120 AttributeValue::N(value.to_string())
1121 }
1122 }
1123 )+
1124 };
1125}
1126
1127impl_from_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1128
1129impl From<HashMap<String, AttributeValue>> for AttributeValue {
1131 fn from(value: HashMap<String, AttributeValue>) -> Self {
1132 AttributeValue::M(value)
1133 }
1134}
1135
1136impl From<Vec<AttributeValue>> for AttributeValue {
1137 fn from(value: Vec<AttributeValue>) -> Self {
1138 AttributeValue::L(value)
1139 }
1140}
1141
1142impl From<HashSet<String>> for AttributeValue {
1143 fn from(value: HashSet<String>) -> Self {
1144 AttributeValue::SS(value.into_iter().collect())
1145 }
1146}
1147
1148impl From<BTreeSet<String>> for AttributeValue {
1149 fn from(value: BTreeSet<String>) -> Self {
1150 AttributeValue::SS(value.into_iter().collect())
1151 }
1152}
1153
1154impl TryFrom<f64> for AttributeValue {
1157 type Error = ConversionError;
1158
1159 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
1160 if value.is_finite() {
1161 Ok(AttributeValue::N(value.to_string()))
1162 } else {
1163 Err(ConversionError {
1164 expected: "finite f64",
1165 actual: "NaN or Infinity",
1166 })
1167 }
1168 }
1169}
1170
1171impl TryFrom<f32> for AttributeValue {
1172 type Error = ConversionError;
1173
1174 fn try_from(value: f32) -> std::result::Result<Self, Self::Error> {
1175 if value.is_finite() {
1176 Ok(AttributeValue::N(value.to_string()))
1177 } else {
1178 Err(ConversionError {
1179 expected: "finite f32",
1180 actual: "NaN or Infinity",
1181 })
1182 }
1183 }
1184}
1185
1186impl TryFrom<AttributeValue> for String {
1189 type Error = ConversionError;
1190
1191 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1192 match value {
1193 AttributeValue::S(s) => Ok(s),
1194 other => Err(ConversionError {
1195 expected: "S",
1196 actual: other.type_name(),
1197 }),
1198 }
1199 }
1200}
1201
1202impl TryFrom<AttributeValue> for bool {
1203 type Error = ConversionError;
1204
1205 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1206 match value {
1207 AttributeValue::BOOL(b) => Ok(b),
1208 other => Err(ConversionError {
1209 expected: "BOOL",
1210 actual: other.type_name(),
1211 }),
1212 }
1213 }
1214}
1215
1216impl TryFrom<AttributeValue> for Vec<u8> {
1217 type Error = ConversionError;
1218
1219 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1220 match value {
1221 AttributeValue::B(b) => Ok(b),
1222 other => Err(ConversionError {
1223 expected: "B",
1224 actual: other.type_name(),
1225 }),
1226 }
1227 }
1228}
1229
1230macro_rules! impl_try_from_av_integer {
1231 ($($t:ty),+) => {
1232 $(
1233 impl TryFrom<AttributeValue> for $t {
1234 type Error = ConversionError;
1235
1236 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1237 match value {
1238 AttributeValue::N(n) => n.parse::<$t>().map_err(|_| ConversionError {
1239 expected: stringify!($t),
1240 actual: "N (parse failed)",
1241 }),
1242 other => Err(ConversionError {
1243 expected: "N",
1244 actual: other.type_name(),
1245 }),
1246 }
1247 }
1248 }
1249 )+
1250 };
1251}
1252
1253impl_try_from_av_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1254
1255impl TryFrom<AttributeValue> for f64 {
1256 type Error = ConversionError;
1257
1258 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1259 match value {
1260 AttributeValue::N(n) => n.parse::<f64>().map_err(|_| ConversionError {
1261 expected: "f64",
1262 actual: "N (parse failed)",
1263 }),
1264 other => Err(ConversionError {
1265 expected: "N",
1266 actual: other.type_name(),
1267 }),
1268 }
1269 }
1270}
1271
1272impl TryFrom<AttributeValue> for f32 {
1273 type Error = ConversionError;
1274
1275 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1276 match value {
1277 AttributeValue::N(n) => n.parse::<f32>().map_err(|_| ConversionError {
1278 expected: "f32",
1279 actual: "N (parse failed)",
1280 }),
1281 other => Err(ConversionError {
1282 expected: "N",
1283 actual: other.type_name(),
1284 }),
1285 }
1286 }
1287}
1288
1289impl TryFrom<AttributeValue> for HashMap<String, AttributeValue> {
1290 type Error = ConversionError;
1291
1292 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1293 match value {
1294 AttributeValue::M(m) => Ok(m),
1295 other => Err(ConversionError {
1296 expected: "M",
1297 actual: other.type_name(),
1298 }),
1299 }
1300 }
1301}
1302
1303impl TryFrom<AttributeValue> for Vec<AttributeValue> {
1304 type Error = ConversionError;
1305
1306 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1307 match value {
1308 AttributeValue::L(l) => Ok(l),
1309 other => Err(ConversionError {
1310 expected: "L",
1311 actual: other.type_name(),
1312 }),
1313 }
1314 }
1315}
1316
1317impl TryFrom<AttributeValue> for Vec<String> {
1318 type Error = ConversionError;
1319
1320 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1321 match value {
1322 AttributeValue::SS(ss) => Ok(ss),
1323 AttributeValue::L(l) => {
1324 l.into_iter()
1326 .map(|av| match av {
1327 AttributeValue::S(s) => Ok(s),
1328 other => Err(ConversionError {
1329 expected: "S (within L)",
1330 actual: other.type_name(),
1331 }),
1332 })
1333 .collect()
1334 }
1335 other => Err(ConversionError {
1336 expected: "SS or L",
1337 actual: other.type_name(),
1338 }),
1339 }
1340 }
1341}
1342
1343#[cfg(test)]
1344mod tests {
1345 use super::*;
1346
1347 #[test]
1348 fn test_serialize_string() {
1349 let val = AttributeValue::S("hello".to_string());
1350 let json = serde_json::to_string(&val).unwrap();
1351 assert_eq!(json, r#"{"S":"hello"}"#);
1352 }
1353
1354 #[test]
1355 fn test_serialize_number() {
1356 let val = AttributeValue::N("42".to_string());
1357 let json = serde_json::to_string(&val).unwrap();
1358 assert_eq!(json, r#"{"N":"42"}"#);
1359 }
1360
1361 #[test]
1362 fn test_serialize_binary() {
1363 let val = AttributeValue::B(vec![1, 2, 3]);
1364 let json = serde_json::to_string(&val).unwrap();
1365 assert_eq!(json, r#"{"B":"AQID"}"#);
1366 }
1367
1368 #[test]
1369 fn test_serialize_bool() {
1370 let val = AttributeValue::BOOL(true);
1371 let json = serde_json::to_string(&val).unwrap();
1372 assert_eq!(json, r#"{"BOOL":true}"#);
1373 }
1374
1375 #[test]
1376 fn test_serialize_null() {
1377 let val = AttributeValue::NULL(true);
1378 let json = serde_json::to_string(&val).unwrap();
1379 assert_eq!(json, r#"{"NULL":true}"#);
1380 }
1381
1382 #[test]
1383 fn test_deserialize_null_true() {
1384 let val: AttributeValue = serde_json::from_str(r#"{"NULL":true}"#).unwrap();
1385 assert_eq!(val, AttributeValue::NULL(true));
1386 }
1387
1388 #[test]
1389 fn test_deserialize_null_false_normalises_to_true() {
1390 let val: AttributeValue = serde_json::from_str(r#"{"NULL":false}"#).unwrap();
1394 assert_eq!(val, AttributeValue::NULL(true));
1395 }
1396
1397 #[test]
1398 fn test_deserialize_null_non_boolean_rejected() {
1399 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":"no"}"#).unwrap_err();
1401 assert!(
1402 err.to_string().contains("must have the value of true"),
1403 "unexpected error: {err}"
1404 );
1405 }
1406
1407 #[test]
1408 fn test_serialize_string_set() {
1409 let val = AttributeValue::SS(vec!["a".to_string(), "b".to_string()]);
1410 let json = serde_json::to_string(&val).unwrap();
1411 assert_eq!(json, r#"{"SS":["a","b"]}"#);
1412 }
1413
1414 #[test]
1415 fn test_serialize_list() {
1416 let val = AttributeValue::L(vec![
1417 AttributeValue::S("hello".to_string()),
1418 AttributeValue::N("42".to_string()),
1419 ]);
1420 let json = serde_json::to_string(&val).unwrap();
1421 assert_eq!(json, r#"{"L":[{"S":"hello"},{"N":"42"}]}"#);
1422 }
1423
1424 #[test]
1425 fn test_serialize_map() {
1426 let mut m = HashMap::new();
1427 m.insert("key".to_string(), AttributeValue::S("value".to_string()));
1428 let val = AttributeValue::M(m);
1429 let json = serde_json::to_string(&val).unwrap();
1430 assert_eq!(json, r#"{"M":{"key":{"S":"value"}}}"#);
1431 }
1432
1433 #[test]
1434 fn test_round_trip_all_types() {
1435 let values = vec![
1436 AttributeValue::S("hello".to_string()),
1437 AttributeValue::N("42.5".to_string()),
1438 AttributeValue::B(vec![0, 255, 128]),
1439 AttributeValue::BOOL(false),
1440 AttributeValue::NULL(true),
1441 AttributeValue::SS(vec!["x".to_string(), "y".to_string()]),
1442 AttributeValue::NS(vec!["1".to_string(), "2.5".to_string()]),
1443 AttributeValue::BS(vec![vec![1], vec![2, 3]]),
1444 AttributeValue::L(vec![
1445 AttributeValue::S("nested".to_string()),
1446 AttributeValue::N("99".to_string()),
1447 ]),
1448 ];
1449
1450 for val in values {
1451 let json = serde_json::to_string(&val).unwrap();
1452 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
1453 assert_eq!(val, deserialized, "Round-trip failed for {json}");
1454 }
1455 }
1456
1457 #[test]
1458 fn test_size_string() {
1459 let val = AttributeValue::S("hello".to_string());
1460 assert_eq!(val.size(), 5);
1461 }
1462
1463 #[test]
1464 fn test_size_number() {
1465 let val = AttributeValue::N("42".to_string());
1467 assert_eq!(val.size(), 2);
1468 }
1469
1470 #[test]
1471 fn test_size_bool() {
1472 assert_eq!(AttributeValue::BOOL(true).size(), 1);
1473 }
1474
1475 #[test]
1476 fn test_size_null() {
1477 assert_eq!(AttributeValue::NULL(true).size(), 1);
1478 }
1479
1480 #[test]
1481 fn test_key_string_s() {
1482 let val = AttributeValue::S("hello".to_string());
1483 assert_eq!(val.to_key_string(), Some("S:hello".to_string()));
1484 }
1485
1486 #[test]
1487 fn test_key_string_n() {
1488 let val = AttributeValue::N("42".to_string());
1489 let key = val.to_key_string().unwrap();
1490 assert!(key.starts_with("N:"));
1491 }
1492
1493 #[test]
1494 fn test_key_string_b() {
1495 let val = AttributeValue::B(vec![0xff, 0x00, 0xab]);
1496 assert_eq!(val.to_key_string(), Some("B:ff00ab".to_string()));
1497 }
1498
1499 #[test]
1500 fn test_key_string_non_key_type_returns_none() {
1501 assert_eq!(AttributeValue::BOOL(true).to_key_string(), None);
1502 assert_eq!(AttributeValue::L(vec![]).to_key_string(), None);
1503 }
1504
1505 #[test]
1507 fn test_number_sort_ordering() {
1508 let numbers = vec![
1509 "-1000", "-100", "-10", "-1", "-0.5", "-0.001", "0", "0.001", "0.5", "1", "10", "100",
1510 "1000",
1511 ];
1512 let encoded: Vec<String> = numbers
1513 .iter()
1514 .map(|n| normalize_number_for_sort(n))
1515 .collect();
1516
1517 for i in 0..encoded.len() - 1 {
1518 assert!(
1519 encoded[i] < encoded[i + 1],
1520 "Sort order broken: {} ({}) should be < {} ({})",
1521 numbers[i],
1522 encoded[i],
1523 numbers[i + 1],
1524 encoded[i + 1]
1525 );
1526 }
1527 }
1528
1529 #[test]
1530 fn test_number_sort_zero_variants() {
1531 let z1 = normalize_number_for_sort("0");
1532 let z2 = normalize_number_for_sort("-0");
1533 let z3 = normalize_number_for_sort("0.0");
1534 assert_eq!(z1, z2);
1535 assert_eq!(z2, z3);
1536 }
1537
1538 #[test]
1539 fn test_number_sort_decimals() {
1540 let a = normalize_number_for_sort("1.5");
1541 let b = normalize_number_for_sort("2.5");
1542 assert!(a < b);
1543
1544 let c = normalize_number_for_sort("0.001");
1545 let d = normalize_number_for_sort("0.01");
1546 assert!(c < d);
1547 }
1548
1549 #[test]
1550 fn test_number_sort_scientific() {
1551 let a = normalize_number_for_sort("1e10");
1552 let b = normalize_number_for_sort("1e11");
1553 assert!(a < b);
1554
1555 let c = normalize_number_for_sort("-1e11");
1556 let d = normalize_number_for_sort("-1e10");
1557 assert!(c < d);
1558 }
1559
1560 #[test]
1561 fn test_type_name() {
1562 assert_eq!(AttributeValue::S("".to_string()).type_name(), "S");
1563 assert_eq!(AttributeValue::N("0".to_string()).type_name(), "N");
1564 assert_eq!(AttributeValue::B(vec![]).type_name(), "B");
1565 assert_eq!(AttributeValue::BOOL(true).type_name(), "BOOL");
1566 assert_eq!(AttributeValue::NULL(true).type_name(), "NULL");
1567 assert_eq!(AttributeValue::SS(vec![]).type_name(), "SS");
1568 assert_eq!(AttributeValue::NS(vec![]).type_name(), "NS");
1569 assert_eq!(AttributeValue::BS(vec![]).type_name(), "BS");
1570 assert_eq!(AttributeValue::L(vec![]).type_name(), "L");
1571 assert_eq!(AttributeValue::M(HashMap::new()).type_name(), "M");
1572 }
1573}