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