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 let n = val.as_bool().unwrap_or(false);
274 Ok(AttributeValue::NULL(n))
275 }
276 "SS" => {
277 let arr = val
278 .as_array()
279 .ok_or_else(|| de::Error::custom("expected array for SS"))?;
280 let ss: Result<Vec<String>, _> = arr
281 .iter()
282 .map(|v| {
283 v.as_str()
284 .map(|s| s.to_string())
285 .ok_or_else(|| de::Error::custom("expected string in SS"))
286 })
287 .collect();
288 Ok(AttributeValue::SS(ss?))
289 }
290 "NS" => {
291 let arr = val
292 .as_array()
293 .ok_or_else(|| de::Error::custom("expected array for NS"))?;
294 let ns: Result<Vec<String>, _> = arr
295 .iter()
296 .map(|v| {
297 v.as_str()
298 .map(|s| s.to_string())
299 .ok_or_else(|| de::Error::custom("expected string in NS"))
300 })
301 .collect();
302 Ok(AttributeValue::NS(ns?))
303 }
304 "BS" => {
305 let arr = val
306 .as_array()
307 .ok_or_else(|| de::Error::custom("expected array for BS"))?;
308 let mut decoded = Vec::with_capacity(arr.len());
309 for item in arr {
310 let encoded = item
311 .as_str()
312 .ok_or_else(|| de::Error::custom("expected string in BS"))?;
313 decoded.push(
314 BASE64
315 .decode(encoded)
316 .map_err(|e| de::Error::custom(format!("invalid base64: {e}")))?,
317 );
318 }
319 Ok(AttributeValue::BS(decoded))
320 }
321 "L" => {
322 let arr = val
323 .as_array()
324 .ok_or_else(|| de::Error::custom("expected array for L"))?;
325 let list: Result<Vec<AttributeValue>, _> = arr
326 .iter()
327 .map(|v| serde_json::from_value(v.clone()).map_err(de::Error::custom))
328 .collect();
329 Ok(AttributeValue::L(list?))
330 }
331 "M" => {
332 let map_val = val
333 .as_object()
334 .ok_or_else(|| de::Error::custom("expected object for M"))?;
335 let mut result = std::collections::HashMap::new();
336 for (k, v) in map_val {
337 let av: AttributeValue =
338 serde_json::from_value(v.clone()).map_err(de::Error::custom)?;
339 result.insert(k.clone(), av);
340 }
341 Ok(AttributeValue::M(result))
342 }
343 _ => unreachable!(),
344 }
345 }
346}
347
348fn validate_number_in_deser(n: &str) -> Result<(), String> {
355 if n.is_empty() {
356 return Err("VALIDATION:The parameter cannot be converted to a numeric value".to_string());
357 }
358 let trimmed = n.trim();
360 let is_valid = trimmed.parse::<f64>().is_ok()
361 || trimmed
362 .to_lowercase()
363 .contains('e')
364 .then(|| trimmed.parse::<f64>().ok())
365 .is_some();
366 if !is_valid {
367 return Err(format!(
368 "VALIDATION:The parameter cannot be converted to a numeric value: {n}"
369 ));
370 }
371 if let Err(e) = validate_dynamo_number(n) {
373 let msg = match e {
374 crate::errors::DynoxideError::ValidationException(m) => format!("VALIDATION:{m}"),
375 _ => format!("VALIDATION:{}", e),
376 };
377 return Err(msg);
378 }
379 Ok(())
380}
381
382pub fn normalize_number_for_sort(num_str: &str) -> String {
396 let trimmed = num_str.trim();
397
398 if trimmed.is_empty() || trimmed == "0" || trimmed == "-0" || trimmed == "0.0" {
399 return zero_encoding();
400 }
401
402 let negative = trimmed.starts_with('-');
403 let abs_str = if negative { &trimmed[1..] } else { trimmed };
404
405 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
407
408 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
409 return zero_encoding();
410 }
411
412 if negative {
413 encode_negative(&mantissa_digits, exponent)
414 } else {
415 encode_positive(&mantissa_digits, exponent)
416 }
417}
418
419pub fn validate_dynamo_number(
425 num_str: &str,
426) -> std::result::Result<(), crate::errors::DynoxideError> {
427 let trimmed = num_str.trim();
428
429 if trimmed.is_empty() {
430 return Err(crate::errors::DynoxideError::ValidationException(
431 "The parameter cannot be converted to a numeric value".to_string(),
432 ));
433 }
434
435 let negative = trimmed.starts_with('-');
436 let abs_str = if negative { &trimmed[1..] } else { trimmed };
437
438 if abs_str.is_empty() || !abs_str.chars().any(|c| c.is_ascii_digit()) {
442 return Err(crate::errors::DynoxideError::ValidationException(format!(
443 "The parameter cannot be converted to a numeric value: {}",
444 trimmed
445 )));
446 }
447 let valid = abs_str.chars().enumerate().all(|(i, c)| {
448 c.is_ascii_digit() || c == '.' || c == 'e' || c == 'E' || ((c == '+' || c == '-') && i > 0) });
450 if !valid {
451 return Err(crate::errors::DynoxideError::ValidationException(format!(
452 "The parameter cannot be converted to a numeric value: {}",
453 trimmed
454 )));
455 }
456
457 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
458
459 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
461 return Ok(());
462 }
463
464 if mantissa_digits.len() > 38 {
466 return Err(crate::errors::DynoxideError::ValidationException(
467 "Attempting to store more than 38 significant digits in a Number".to_string(),
468 ));
469 }
470
471 if exponent > 126 {
474 return Err(crate::errors::DynoxideError::ValidationException(
475 "Number overflow. Attempting to store a number with magnitude larger than supported range"
476 .to_string(),
477 ));
478 }
479
480 if exponent < -129 {
487 return Err(crate::errors::DynoxideError::ValidationException(
488 "Number underflow. Attempting to store a number with magnitude smaller than supported range"
489 .to_string(),
490 ));
491 }
492
493 Ok(())
494}
495
496pub fn normalize_dynamo_number(num_str: &str) -> String {
504 let trimmed = num_str.trim();
505 if trimmed.is_empty() {
506 return "0".to_string();
507 }
508
509 let negative = trimmed.starts_with('-');
510 let abs_str = if negative {
511 &trimmed[1..]
512 } else {
513 trimmed.trim_start_matches('+')
514 };
515
516 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
517
518 if mantissa_digits.is_empty() {
520 return "0".to_string();
521 }
522
523 let num_digits = mantissa_digits.len() as i32;
528 let int_digits = exponent; let mut result = String::new();
531 if negative {
532 result.push('-');
533 }
534
535 if int_digits <= 0 {
536 result.push_str("0.");
538 for _ in 0..(-int_digits) {
539 result.push('0');
540 }
541 for &d in &mantissa_digits {
542 result.push((b'0' + d) as char);
543 }
544 } else if int_digits >= num_digits {
545 for &d in &mantissa_digits {
547 result.push((b'0' + d) as char);
548 }
549 for _ in 0..(int_digits - num_digits) {
550 result.push('0');
551 }
552 } else {
553 let int_part = int_digits as usize;
555 for &d in &mantissa_digits[..int_part] {
556 result.push((b'0' + d) as char);
557 }
558 result.push('.');
559 for &d in &mantissa_digits[int_part..] {
560 result.push((b'0' + d) as char);
561 }
562 }
563
564 result
565}
566
567fn zero_encoding() -> String {
568 format!("1{}{}", "0".repeat(4), "0".repeat(40))
570}
571
572fn encode_positive(mantissa: &[u8], exponent: i32) -> String {
573 let exp_encoded = (exponent + 5000) as u16;
574 let mantissa_str = mantissa_to_string(mantissa, 40);
575 format!("2{exp_encoded:04}{mantissa_str}")
576}
577
578fn encode_negative(mantissa: &[u8], exponent: i32) -> String {
579 let exp_encoded = 9999 - (exponent + 5000) as u16;
581 let mantissa_str = complement_mantissa(mantissa, 40);
582 format!("0{exp_encoded:04}{mantissa_str}")
583}
584
585pub(crate) fn parse_number_parts(s: &str) -> (Vec<u8>, i32) {
589 let (coeff, exp_part) = if let Some(pos) = s.to_ascii_lowercase().find('e') {
591 let coeff = &s[..pos];
592 let exp: i32 = s[pos + 1..].parse().unwrap_or(0);
593 (coeff, exp)
594 } else {
595 (s, 0)
596 };
597
598 let (int_part, frac_part) = if let Some(dot) = coeff.find('.') {
600 (&coeff[..dot], &coeff[dot + 1..])
601 } else {
602 (coeff, "")
603 };
604
605 let mut digits: Vec<u8> = Vec::new();
607 for ch in int_part.chars().chain(frac_part.chars()) {
608 if ch.is_ascii_digit() {
609 digits.push(ch as u8 - b'0');
610 }
611 }
612
613 if digits.is_empty() {
614 return (vec![], 0);
615 }
616
617 let int_len = int_part.chars().filter(|c| c.is_ascii_digit()).count() as i32;
619
620 let leading_zeros = digits.iter().take_while(|&&d| d == 0).count();
622 digits.drain(..leading_zeros);
623
624 while digits.last() == Some(&0) {
626 digits.pop();
627 }
628
629 if digits.is_empty() {
630 return (vec![], 0);
631 }
632
633 let exponent = int_len - leading_zeros as i32 + exp_part;
636
637 (digits, exponent)
638}
639
640fn mantissa_to_string(digits: &[u8], width: usize) -> String {
641 let mut s = String::with_capacity(width);
642 for &d in digits.iter().take(width) {
643 s.push((b'0' + d) as char);
644 }
645 while s.len() < width {
646 s.push('0');
647 }
648 s
649}
650
651fn complement_mantissa(digits: &[u8], width: usize) -> String {
652 let mut s = String::with_capacity(width);
653 for i in 0..width {
654 let d = if i < digits.len() { digits[i] } else { 0 };
655 s.push((b'0' + (9 - d)) as char);
656 }
657 s
658}
659
660fn hex_encode(bytes: &[u8]) -> String {
662 let mut s = String::with_capacity(bytes.len() * 2);
663 for &b in bytes {
664 s.push_str(&format!("{b:02x}"));
665 }
666 s
667}
668
669pub type Item = HashMap<String, AttributeValue>;
675
676#[derive(Debug, Clone, Default, Serialize, Deserialize)]
678pub struct SseSpecification {
679 #[serde(rename = "Enabled", default)]
680 pub enabled: Option<bool>,
681 #[serde(rename = "SSEType", default)]
682 pub sse_type: Option<String>,
683 #[serde(rename = "KMSMasterKeyId", default)]
684 pub kms_master_key_id: Option<String>,
685}
686
687#[derive(Debug, Clone, Default, Serialize, Deserialize)]
689pub struct Tag {
690 #[serde(rename = "Key")]
691 pub key: String,
692 #[serde(rename = "Value")]
693 pub value: String,
694}
695
696pub fn item_size(item: &Item) -> usize {
698 item.iter()
699 .map(|(name, value)| name.len() + value.size())
700 .sum()
701}
702
703pub const MAX_ITEM_SIZE: usize = 400 * 1024;
705
706#[derive(Debug, Clone, Serialize, Deserialize)]
709pub struct ItemCollectionMetrics {
710 #[serde(rename = "ItemCollectionKey")]
711 pub item_collection_key: HashMap<String, AttributeValue>,
712 #[serde(rename = "SizeEstimateRangeGB")]
713 pub size_estimate_range_gb: Vec<f64>,
714}
715
716#[derive(Debug, Clone, Default, Serialize, Deserialize)]
718pub struct ConsumedCapacity {
719 #[serde(rename = "TableName")]
720 pub table_name: String,
721 #[serde(rename = "CapacityUnits")]
722 pub capacity_units: f64,
723 #[serde(rename = "Table", skip_serializing_if = "Option::is_none")]
724 pub table: Option<CapacityDetail>,
725 #[serde(
726 rename = "GlobalSecondaryIndexes",
727 skip_serializing_if = "Option::is_none"
728 )]
729 pub global_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
730 #[serde(
731 rename = "LocalSecondaryIndexes",
732 skip_serializing_if = "Option::is_none"
733 )]
734 pub local_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
735}
736
737#[derive(Debug, Clone, Default, Serialize, Deserialize)]
739pub struct CapacityDetail {
740 #[serde(rename = "CapacityUnits")]
741 pub capacity_units: f64,
742 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
743 pub read_capacity_units: Option<f64>,
744 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
745 pub write_capacity_units: Option<f64>,
746}
747
748pub const TRANSACTIONAL_CAPACITY_FACTOR: f64 = 2.0;
753
754pub fn write_capacity_units(item_size_bytes: usize) -> f64 {
756 ((item_size_bytes as f64) / 1024.0).ceil().max(1.0)
757}
758
759pub fn read_capacity_units(item_size_bytes: usize) -> f64 {
763 ((item_size_bytes as f64) / 4096.0).ceil().max(1.0)
764}
765
766pub fn read_capacity_units_with_consistency(item_size_bytes: usize, consistent: bool) -> f64 {
771 let strongly = read_capacity_units(item_size_bytes);
772 if consistent { strongly } else { strongly / 2.0 }
773}
774
775pub fn consumed_capacity(
777 table_name: &str,
778 capacity_units: f64,
779 mode: &Option<String>,
780) -> Option<ConsumedCapacity> {
781 let mode = mode.as_deref().unwrap_or("NONE");
782 match mode {
783 "TOTAL" => Some(ConsumedCapacity {
784 table_name: table_name.to_string(),
785 capacity_units,
786 table: None,
787 global_secondary_indexes: None,
788 local_secondary_indexes: None,
789 }),
790 "INDEXES" => Some(ConsumedCapacity {
791 table_name: table_name.to_string(),
792 capacity_units,
793 table: Some(CapacityDetail {
794 capacity_units,
795 ..Default::default()
796 }),
797 global_secondary_indexes: None,
798 local_secondary_indexes: None,
799 }),
800 _ => None,
801 }
802}
803
804pub fn consumed_capacity_with_indexes(
806 table_name: &str,
807 table_units: f64,
808 gsi_units: &HashMap<String, f64>,
809 mode: &Option<String>,
810) -> Option<ConsumedCapacity> {
811 consumed_capacity_with_secondary_indexes(
812 table_name,
813 table_units,
814 gsi_units,
815 &HashMap::new(),
816 mode,
817 )
818}
819
820pub fn consumed_capacity_with_secondary_indexes(
822 table_name: &str,
823 table_units: f64,
824 gsi_units: &HashMap<String, f64>,
825 lsi_units: &HashMap<String, f64>,
826 mode: &Option<String>,
827) -> Option<ConsumedCapacity> {
828 let units_to_map = |units: &HashMap<String, f64>| -> Option<HashMap<String, CapacityDetail>> {
829 if units.is_empty() {
830 None
831 } else {
832 Some(
833 units
834 .iter()
835 .map(|(name, &u)| {
836 (
837 name.clone(),
838 CapacityDetail {
839 capacity_units: u,
840 ..Default::default()
841 },
842 )
843 })
844 .collect(),
845 )
846 }
847 };
848
849 match mode.as_deref().unwrap_or("NONE") {
850 "INDEXES" => {
851 let gsi_total: f64 = gsi_units.values().sum();
852 let lsi_total: f64 = lsi_units.values().sum();
853 Some(ConsumedCapacity {
854 table_name: table_name.to_string(),
855 capacity_units: table_units + gsi_total + lsi_total,
856 table: Some(CapacityDetail {
857 capacity_units: table_units,
858 ..Default::default()
859 }),
860 global_secondary_indexes: units_to_map(gsi_units),
861 local_secondary_indexes: units_to_map(lsi_units),
862 })
863 }
864 "TOTAL" => {
865 let gsi_total: f64 = gsi_units.values().sum();
866 let lsi_total: f64 = lsi_units.values().sum();
867 Some(ConsumedCapacity {
868 table_name: table_name.to_string(),
869 capacity_units: table_units + gsi_total + lsi_total,
870 table: None,
871 global_secondary_indexes: None,
872 local_secondary_indexes: None,
873 })
874 }
875 _ => None,
876 }
877}
878
879pub fn transactional_read_capacity(
884 table_name: &str,
885 units: f64,
886 mode: &Option<String>,
887) -> Option<ConsumedCapacity> {
888 match mode.as_deref().unwrap_or("NONE") {
889 "TOTAL" => Some(ConsumedCapacity {
890 table_name: table_name.to_string(),
891 capacity_units: units,
892 ..Default::default()
893 }),
894 "INDEXES" => Some(ConsumedCapacity {
895 table_name: table_name.to_string(),
896 capacity_units: units,
897 table: Some(CapacityDetail {
898 capacity_units: units,
899 read_capacity_units: Some(units),
900 ..Default::default()
901 }),
902 ..Default::default()
903 }),
904 _ => None,
905 }
906}
907
908pub fn transactional_write_capacity(
913 table_name: &str,
914 units: f64,
915 mode: &Option<String>,
916) -> Option<ConsumedCapacity> {
917 match mode.as_deref().unwrap_or("NONE") {
918 "TOTAL" => Some(ConsumedCapacity {
919 table_name: table_name.to_string(),
920 capacity_units: units,
921 ..Default::default()
922 }),
923 "INDEXES" => Some(ConsumedCapacity {
924 table_name: table_name.to_string(),
925 capacity_units: units,
926 table: Some(CapacityDetail {
927 capacity_units: units,
928 write_capacity_units: Some(units),
929 ..Default::default()
930 }),
931 ..Default::default()
932 }),
933 _ => None,
934 }
935}
936
937#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
939pub struct KeySchemaElement {
940 #[serde(rename = "AttributeName", alias = "attribute_name")]
941 pub attribute_name: String,
942 #[serde(rename = "KeyType", alias = "key_type")]
943 pub key_type: KeyType,
944}
945
946#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
948pub enum KeyType {
949 #[default]
950 HASH,
951 RANGE,
952}
953
954#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
956pub struct AttributeDefinition {
957 #[serde(rename = "AttributeName", alias = "attribute_name")]
958 pub attribute_name: String,
959 #[serde(rename = "AttributeType", alias = "attribute_type")]
960 pub attribute_type: ScalarAttributeType,
961}
962
963#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
965pub enum ScalarAttributeType {
966 #[default]
967 S,
968 N,
969 B,
970}
971
972#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
974pub struct Projection {
975 #[serde(
976 rename = "ProjectionType",
977 alias = "projection_type",
978 default,
979 skip_serializing_if = "Option::is_none"
980 )]
981 pub projection_type: Option<ProjectionType>,
982 #[serde(
983 rename = "NonKeyAttributes",
984 alias = "non_key_attributes",
985 skip_serializing_if = "Option::is_none"
986 )]
987 pub non_key_attributes: Option<Vec<String>>,
988}
989
990#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
992#[allow(non_camel_case_types)]
993pub enum ProjectionType {
994 #[default]
995 ALL,
996 KEYS_ONLY,
997 INCLUDE,
998}
999
1000#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1002pub struct GlobalSecondaryIndex {
1003 #[serde(rename = "IndexName", alias = "index_name")]
1004 pub index_name: String,
1005 #[serde(rename = "KeySchema", alias = "key_schema")]
1006 pub key_schema: Vec<KeySchemaElement>,
1007 #[serde(rename = "Projection", alias = "projection")]
1008 pub projection: Projection,
1009 #[serde(
1010 rename = "ProvisionedThroughput",
1011 alias = "provisioned_throughput",
1012 skip_serializing_if = "Option::is_none"
1013 )]
1014 pub provisioned_throughput: Option<ProvisionedThroughput>,
1015}
1016
1017#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1019pub struct LocalSecondaryIndex {
1020 #[serde(rename = "IndexName", alias = "index_name")]
1021 pub index_name: String,
1022 #[serde(rename = "KeySchema", alias = "key_schema")]
1023 pub key_schema: Vec<KeySchemaElement>,
1024 #[serde(rename = "Projection", alias = "projection")]
1025 pub projection: Projection,
1026}
1027
1028#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1030pub struct ProvisionedThroughput {
1031 #[serde(rename = "ReadCapacityUnits", default)]
1032 pub read_capacity_units: Option<i64>,
1033 #[serde(rename = "WriteCapacityUnits", default)]
1034 pub write_capacity_units: Option<i64>,
1035}
1036
1037#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1039pub struct OnDemandThroughput {
1040 #[serde(
1041 rename = "MaxReadRequestUnits",
1042 default,
1043 skip_serializing_if = "Option::is_none"
1044 )]
1045 pub max_read_request_units: Option<i64>,
1046 #[serde(
1047 rename = "MaxWriteRequestUnits",
1048 default,
1049 skip_serializing_if = "Option::is_none"
1050 )]
1051 pub max_write_request_units: Option<i64>,
1052}
1053
1054#[derive(Debug, Clone, PartialEq)]
1060pub struct ConversionError {
1061 pub expected: &'static str,
1063 pub actual: &'static str,
1065}
1066
1067impl fmt::Display for ConversionError {
1068 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1069 write!(f, "expected {}, got {}", self.expected, self.actual)
1070 }
1071}
1072
1073impl std::error::Error for ConversionError {}
1074
1075impl From<String> for AttributeValue {
1078 fn from(value: String) -> Self {
1079 AttributeValue::S(value)
1080 }
1081}
1082
1083impl From<&str> for AttributeValue {
1084 fn from(value: &str) -> Self {
1085 AttributeValue::S(value.to_string())
1086 }
1087}
1088
1089impl From<bool> for AttributeValue {
1090 fn from(value: bool) -> Self {
1091 AttributeValue::BOOL(value)
1092 }
1093}
1094
1095impl From<Vec<u8>> for AttributeValue {
1096 fn from(value: Vec<u8>) -> Self {
1097 AttributeValue::B(value)
1098 }
1099}
1100
1101impl From<&[u8]> for AttributeValue {
1102 fn from(value: &[u8]) -> Self {
1103 AttributeValue::B(value.to_vec())
1104 }
1105}
1106
1107macro_rules! impl_from_integer {
1109 ($($t:ty),+) => {
1110 $(
1111 impl From<$t> for AttributeValue {
1112 fn from(value: $t) -> Self {
1113 AttributeValue::N(value.to_string())
1114 }
1115 }
1116 )+
1117 };
1118}
1119
1120impl_from_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1121
1122impl From<HashMap<String, AttributeValue>> for AttributeValue {
1124 fn from(value: HashMap<String, AttributeValue>) -> Self {
1125 AttributeValue::M(value)
1126 }
1127}
1128
1129impl From<Vec<AttributeValue>> for AttributeValue {
1130 fn from(value: Vec<AttributeValue>) -> Self {
1131 AttributeValue::L(value)
1132 }
1133}
1134
1135impl From<HashSet<String>> for AttributeValue {
1136 fn from(value: HashSet<String>) -> Self {
1137 AttributeValue::SS(value.into_iter().collect())
1138 }
1139}
1140
1141impl From<BTreeSet<String>> for AttributeValue {
1142 fn from(value: BTreeSet<String>) -> Self {
1143 AttributeValue::SS(value.into_iter().collect())
1144 }
1145}
1146
1147impl TryFrom<f64> for AttributeValue {
1150 type Error = ConversionError;
1151
1152 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
1153 if value.is_finite() {
1154 Ok(AttributeValue::N(value.to_string()))
1155 } else {
1156 Err(ConversionError {
1157 expected: "finite f64",
1158 actual: "NaN or Infinity",
1159 })
1160 }
1161 }
1162}
1163
1164impl TryFrom<f32> for AttributeValue {
1165 type Error = ConversionError;
1166
1167 fn try_from(value: f32) -> std::result::Result<Self, Self::Error> {
1168 if value.is_finite() {
1169 Ok(AttributeValue::N(value.to_string()))
1170 } else {
1171 Err(ConversionError {
1172 expected: "finite f32",
1173 actual: "NaN or Infinity",
1174 })
1175 }
1176 }
1177}
1178
1179impl TryFrom<AttributeValue> for String {
1182 type Error = ConversionError;
1183
1184 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1185 match value {
1186 AttributeValue::S(s) => Ok(s),
1187 other => Err(ConversionError {
1188 expected: "S",
1189 actual: other.type_name(),
1190 }),
1191 }
1192 }
1193}
1194
1195impl TryFrom<AttributeValue> for bool {
1196 type Error = ConversionError;
1197
1198 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1199 match value {
1200 AttributeValue::BOOL(b) => Ok(b),
1201 other => Err(ConversionError {
1202 expected: "BOOL",
1203 actual: other.type_name(),
1204 }),
1205 }
1206 }
1207}
1208
1209impl TryFrom<AttributeValue> for Vec<u8> {
1210 type Error = ConversionError;
1211
1212 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1213 match value {
1214 AttributeValue::B(b) => Ok(b),
1215 other => Err(ConversionError {
1216 expected: "B",
1217 actual: other.type_name(),
1218 }),
1219 }
1220 }
1221}
1222
1223macro_rules! impl_try_from_av_integer {
1224 ($($t:ty),+) => {
1225 $(
1226 impl TryFrom<AttributeValue> for $t {
1227 type Error = ConversionError;
1228
1229 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1230 match value {
1231 AttributeValue::N(n) => n.parse::<$t>().map_err(|_| ConversionError {
1232 expected: stringify!($t),
1233 actual: "N (parse failed)",
1234 }),
1235 other => Err(ConversionError {
1236 expected: "N",
1237 actual: other.type_name(),
1238 }),
1239 }
1240 }
1241 }
1242 )+
1243 };
1244}
1245
1246impl_try_from_av_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1247
1248impl TryFrom<AttributeValue> for f64 {
1249 type Error = ConversionError;
1250
1251 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1252 match value {
1253 AttributeValue::N(n) => n.parse::<f64>().map_err(|_| ConversionError {
1254 expected: "f64",
1255 actual: "N (parse failed)",
1256 }),
1257 other => Err(ConversionError {
1258 expected: "N",
1259 actual: other.type_name(),
1260 }),
1261 }
1262 }
1263}
1264
1265impl TryFrom<AttributeValue> for f32 {
1266 type Error = ConversionError;
1267
1268 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1269 match value {
1270 AttributeValue::N(n) => n.parse::<f32>().map_err(|_| ConversionError {
1271 expected: "f32",
1272 actual: "N (parse failed)",
1273 }),
1274 other => Err(ConversionError {
1275 expected: "N",
1276 actual: other.type_name(),
1277 }),
1278 }
1279 }
1280}
1281
1282impl TryFrom<AttributeValue> for HashMap<String, AttributeValue> {
1283 type Error = ConversionError;
1284
1285 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1286 match value {
1287 AttributeValue::M(m) => Ok(m),
1288 other => Err(ConversionError {
1289 expected: "M",
1290 actual: other.type_name(),
1291 }),
1292 }
1293 }
1294}
1295
1296impl TryFrom<AttributeValue> for Vec<AttributeValue> {
1297 type Error = ConversionError;
1298
1299 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1300 match value {
1301 AttributeValue::L(l) => Ok(l),
1302 other => Err(ConversionError {
1303 expected: "L",
1304 actual: other.type_name(),
1305 }),
1306 }
1307 }
1308}
1309
1310impl TryFrom<AttributeValue> for Vec<String> {
1311 type Error = ConversionError;
1312
1313 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1314 match value {
1315 AttributeValue::SS(ss) => Ok(ss),
1316 AttributeValue::L(l) => {
1317 l.into_iter()
1319 .map(|av| match av {
1320 AttributeValue::S(s) => Ok(s),
1321 other => Err(ConversionError {
1322 expected: "S (within L)",
1323 actual: other.type_name(),
1324 }),
1325 })
1326 .collect()
1327 }
1328 other => Err(ConversionError {
1329 expected: "SS or L",
1330 actual: other.type_name(),
1331 }),
1332 }
1333 }
1334}
1335
1336#[cfg(test)]
1337mod tests {
1338 use super::*;
1339
1340 #[test]
1341 fn test_serialize_string() {
1342 let val = AttributeValue::S("hello".to_string());
1343 let json = serde_json::to_string(&val).unwrap();
1344 assert_eq!(json, r#"{"S":"hello"}"#);
1345 }
1346
1347 #[test]
1348 fn test_serialize_number() {
1349 let val = AttributeValue::N("42".to_string());
1350 let json = serde_json::to_string(&val).unwrap();
1351 assert_eq!(json, r#"{"N":"42"}"#);
1352 }
1353
1354 #[test]
1355 fn test_serialize_binary() {
1356 let val = AttributeValue::B(vec![1, 2, 3]);
1357 let json = serde_json::to_string(&val).unwrap();
1358 assert_eq!(json, r#"{"B":"AQID"}"#);
1359 }
1360
1361 #[test]
1362 fn test_serialize_bool() {
1363 let val = AttributeValue::BOOL(true);
1364 let json = serde_json::to_string(&val).unwrap();
1365 assert_eq!(json, r#"{"BOOL":true}"#);
1366 }
1367
1368 #[test]
1369 fn test_serialize_null() {
1370 let val = AttributeValue::NULL(true);
1371 let json = serde_json::to_string(&val).unwrap();
1372 assert_eq!(json, r#"{"NULL":true}"#);
1373 }
1374
1375 #[test]
1376 fn test_serialize_string_set() {
1377 let val = AttributeValue::SS(vec!["a".to_string(), "b".to_string()]);
1378 let json = serde_json::to_string(&val).unwrap();
1379 assert_eq!(json, r#"{"SS":["a","b"]}"#);
1380 }
1381
1382 #[test]
1383 fn test_serialize_list() {
1384 let val = AttributeValue::L(vec![
1385 AttributeValue::S("hello".to_string()),
1386 AttributeValue::N("42".to_string()),
1387 ]);
1388 let json = serde_json::to_string(&val).unwrap();
1389 assert_eq!(json, r#"{"L":[{"S":"hello"},{"N":"42"}]}"#);
1390 }
1391
1392 #[test]
1393 fn test_serialize_map() {
1394 let mut m = HashMap::new();
1395 m.insert("key".to_string(), AttributeValue::S("value".to_string()));
1396 let val = AttributeValue::M(m);
1397 let json = serde_json::to_string(&val).unwrap();
1398 assert_eq!(json, r#"{"M":{"key":{"S":"value"}}}"#);
1399 }
1400
1401 #[test]
1402 fn test_round_trip_all_types() {
1403 let values = vec![
1404 AttributeValue::S("hello".to_string()),
1405 AttributeValue::N("42.5".to_string()),
1406 AttributeValue::B(vec![0, 255, 128]),
1407 AttributeValue::BOOL(false),
1408 AttributeValue::NULL(true),
1409 AttributeValue::SS(vec!["x".to_string(), "y".to_string()]),
1410 AttributeValue::NS(vec!["1".to_string(), "2.5".to_string()]),
1411 AttributeValue::BS(vec![vec![1], vec![2, 3]]),
1412 AttributeValue::L(vec![
1413 AttributeValue::S("nested".to_string()),
1414 AttributeValue::N("99".to_string()),
1415 ]),
1416 ];
1417
1418 for val in values {
1419 let json = serde_json::to_string(&val).unwrap();
1420 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
1421 assert_eq!(val, deserialized, "Round-trip failed for {json}");
1422 }
1423 }
1424
1425 #[test]
1426 fn test_size_string() {
1427 let val = AttributeValue::S("hello".to_string());
1428 assert_eq!(val.size(), 5);
1429 }
1430
1431 #[test]
1432 fn test_size_number() {
1433 let val = AttributeValue::N("42".to_string());
1435 assert_eq!(val.size(), 2);
1436 }
1437
1438 #[test]
1439 fn test_size_bool() {
1440 assert_eq!(AttributeValue::BOOL(true).size(), 1);
1441 }
1442
1443 #[test]
1444 fn test_size_null() {
1445 assert_eq!(AttributeValue::NULL(true).size(), 1);
1446 }
1447
1448 #[test]
1449 fn test_key_string_s() {
1450 let val = AttributeValue::S("hello".to_string());
1451 assert_eq!(val.to_key_string(), Some("S:hello".to_string()));
1452 }
1453
1454 #[test]
1455 fn test_key_string_n() {
1456 let val = AttributeValue::N("42".to_string());
1457 let key = val.to_key_string().unwrap();
1458 assert!(key.starts_with("N:"));
1459 }
1460
1461 #[test]
1462 fn test_key_string_b() {
1463 let val = AttributeValue::B(vec![0xff, 0x00, 0xab]);
1464 assert_eq!(val.to_key_string(), Some("B:ff00ab".to_string()));
1465 }
1466
1467 #[test]
1468 fn test_key_string_non_key_type_returns_none() {
1469 assert_eq!(AttributeValue::BOOL(true).to_key_string(), None);
1470 assert_eq!(AttributeValue::L(vec![]).to_key_string(), None);
1471 }
1472
1473 #[test]
1475 fn test_number_sort_ordering() {
1476 let numbers = vec![
1477 "-1000", "-100", "-10", "-1", "-0.5", "-0.001", "0", "0.001", "0.5", "1", "10", "100",
1478 "1000",
1479 ];
1480 let encoded: Vec<String> = numbers
1481 .iter()
1482 .map(|n| normalize_number_for_sort(n))
1483 .collect();
1484
1485 for i in 0..encoded.len() - 1 {
1486 assert!(
1487 encoded[i] < encoded[i + 1],
1488 "Sort order broken: {} ({}) should be < {} ({})",
1489 numbers[i],
1490 encoded[i],
1491 numbers[i + 1],
1492 encoded[i + 1]
1493 );
1494 }
1495 }
1496
1497 #[test]
1498 fn test_number_sort_zero_variants() {
1499 let z1 = normalize_number_for_sort("0");
1500 let z2 = normalize_number_for_sort("-0");
1501 let z3 = normalize_number_for_sort("0.0");
1502 assert_eq!(z1, z2);
1503 assert_eq!(z2, z3);
1504 }
1505
1506 #[test]
1507 fn test_number_sort_decimals() {
1508 let a = normalize_number_for_sort("1.5");
1509 let b = normalize_number_for_sort("2.5");
1510 assert!(a < b);
1511
1512 let c = normalize_number_for_sort("0.001");
1513 let d = normalize_number_for_sort("0.01");
1514 assert!(c < d);
1515 }
1516
1517 #[test]
1518 fn test_number_sort_scientific() {
1519 let a = normalize_number_for_sort("1e10");
1520 let b = normalize_number_for_sort("1e11");
1521 assert!(a < b);
1522
1523 let c = normalize_number_for_sort("-1e11");
1524 let d = normalize_number_for_sort("-1e10");
1525 assert!(c < d);
1526 }
1527
1528 #[test]
1529 fn test_type_name() {
1530 assert_eq!(AttributeValue::S("".to_string()).type_name(), "S");
1531 assert_eq!(AttributeValue::N("0".to_string()).type_name(), "N");
1532 assert_eq!(AttributeValue::B(vec![]).type_name(), "B");
1533 assert_eq!(AttributeValue::BOOL(true).type_name(), "BOOL");
1534 assert_eq!(AttributeValue::NULL(true).type_name(), "NULL");
1535 assert_eq!(AttributeValue::SS(vec![]).type_name(), "SS");
1536 assert_eq!(AttributeValue::NS(vec![]).type_name(), "NS");
1537 assert_eq!(AttributeValue::BS(vec![]).type_name(), "BS");
1538 assert_eq!(AttributeValue::L(vec![]).type_name(), "L");
1539 assert_eq!(AttributeValue::M(HashMap::new()).type_name(), "M");
1540 }
1541}