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) => number_size(n),
48 AttributeValue::B(b) => b.len(),
49 AttributeValue::BOOL(_) => 1,
50 AttributeValue::NULL(_) => 1,
51 AttributeValue::SS(ss) => ss.iter().map(|s| s.len()).sum(),
52 AttributeValue::NS(ns) => ns.iter().map(|n| number_size(n)).sum(),
53 AttributeValue::BS(bs) => bs.iter().map(|b| b.len()).sum(),
54 AttributeValue::L(items) => {
55 3 + items.len() + items.iter().map(|v| v.size()).sum::<usize>()
57 }
58 AttributeValue::M(map) => {
59 3 + map
61 .iter()
62 .map(|(k, v)| k.len() + 1 + v.size())
63 .sum::<usize>()
64 }
65 }
66 }
67
68 pub fn type_name(&self) -> &'static str {
70 match self {
71 AttributeValue::S(_) => "S",
72 AttributeValue::N(_) => "N",
73 AttributeValue::B(_) => "B",
74 AttributeValue::BOOL(_) => "BOOL",
75 AttributeValue::NULL(_) => "NULL",
76 AttributeValue::SS(_) => "SS",
77 AttributeValue::NS(_) => "NS",
78 AttributeValue::BS(_) => "BS",
79 AttributeValue::L(_) => "L",
80 AttributeValue::M(_) => "M",
81 }
82 }
83
84 pub fn is_scalar(&self) -> bool {
86 matches!(
87 self,
88 AttributeValue::S(_)
89 | AttributeValue::N(_)
90 | AttributeValue::B(_)
91 | AttributeValue::BOOL(_)
92 | AttributeValue::NULL(_)
93 )
94 }
95
96 pub fn is_set(&self) -> bool {
98 matches!(
99 self,
100 AttributeValue::SS(_) | AttributeValue::NS(_) | AttributeValue::BS(_)
101 )
102 }
103
104 pub fn to_key_string(&self) -> Option<String> {
111 match self {
112 AttributeValue::S(s) => Some(format!("S:{s}")),
113 AttributeValue::N(n) => Some(format!("N:{}", normalize_number_for_sort(n))),
114 AttributeValue::B(b) => Some(format!("B:{}", hex_encode(b))),
115 _ => None, }
117 }
118}
119
120impl fmt::Display for AttributeValue {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 match self {
123 AttributeValue::S(s) => write!(f, "\"{s}\""),
124 AttributeValue::N(n) => write!(f, "{n}"),
125 AttributeValue::B(b) => write!(f, "<binary {} bytes>", b.len()),
126 AttributeValue::BOOL(b) => write!(f, "{b}"),
127 AttributeValue::NULL(_) => write!(f, "null"),
128 AttributeValue::SS(ss) => write!(f, "{ss:?}"),
129 AttributeValue::NS(ns) => write!(f, "{ns:?}"),
130 AttributeValue::BS(bs) => write!(f, "<binary set {} items>", bs.len()),
131 AttributeValue::L(items) => write!(f, "<list {} items>", items.len()),
132 AttributeValue::M(map) => write!(f, "<map {} keys>", map.len()),
133 }
134 }
135}
136
137impl Serialize for AttributeValue {
142 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
143 where
144 S: Serializer,
145 {
146 let mut map = serializer.serialize_map(Some(1))?;
147 match self {
148 AttributeValue::S(s) => map.serialize_entry("S", s)?,
149 AttributeValue::N(n) => map.serialize_entry("N", n)?,
150 AttributeValue::B(b) => {
151 map.serialize_entry("B", &BASE64.encode(b))?;
152 }
153 AttributeValue::BOOL(b) => map.serialize_entry("BOOL", b)?,
154 AttributeValue::NULL(n) => map.serialize_entry("NULL", n)?,
155 AttributeValue::SS(ss) => map.serialize_entry("SS", ss)?,
156 AttributeValue::NS(ns) => map.serialize_entry("NS", ns)?,
157 AttributeValue::BS(bs) => {
158 let encoded: Vec<String> = bs.iter().map(|b| BASE64.encode(b)).collect();
159 map.serialize_entry("BS", &encoded)?;
160 }
161 AttributeValue::L(items) => map.serialize_entry("L", items)?,
162 AttributeValue::M(m) => map.serialize_entry("M", m)?,
163 }
164 map.end()
165 }
166}
167
168impl<'de> Deserialize<'de> for AttributeValue {
169 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170 where
171 D: Deserializer<'de>,
172 {
173 let raw = serde_json::Value::deserialize(deserializer)?;
175
176 let obj = raw
177 .as_object()
178 .ok_or_else(|| de::Error::custom("empty AttributeValue object"))?;
179
180 if obj.is_empty() {
181 return Err(de::Error::custom("empty AttributeValue object"));
182 }
183
184 let known_types = ["S", "N", "B", "BOOL", "NULL", "SS", "NS", "BS", "L", "M"];
186 let present: Vec<&str> = obj
187 .keys()
188 .filter(|k| known_types.contains(&k.as_str()))
189 .map(|k| k.as_str())
190 .collect();
191
192 if present.is_empty() {
193 return Err(de::Error::custom(
194 "Supplied AttributeValue is empty, must contain exactly one of the supported datatypes",
195 ));
196 }
197
198 for &type_key in &present {
201 match type_key {
202 "N" => {
203 if let Some(n) = obj.get("N").and_then(|v| v.as_str()) {
204 validate_number_in_deser(n).map_err(de::Error::custom)?;
205 }
206 }
207 "NS" => {
208 if let Some(arr) = obj.get("NS").and_then(|v| v.as_array()) {
209 for item in arr {
210 if let Some(n) = item.as_str() {
211 validate_number_in_deser(n).map_err(de::Error::custom)?;
212 }
213 }
214 }
215 }
216 _ => {}
217 }
218 }
219
220 if present.len() > 1 {
222 return Err(de::Error::custom(
223 "VALIDATION:Supplied AttributeValue has more than one datatypes set, \
224 must contain exactly one of the supported datatypes",
225 ));
226 }
227
228 let type_key = present[0];
229 let val = &obj[type_key];
230
231 match type_key {
232 "S" => {
233 let s = val
234 .as_str()
235 .ok_or_else(|| de::Error::custom("expected string for S"))?;
236 Ok(AttributeValue::S(s.to_string()))
237 }
238 "N" => {
239 let n = val
240 .as_str()
241 .ok_or_else(|| de::Error::custom("expected string for N"))?;
242 Ok(AttributeValue::N(n.to_string()))
243 }
244 "B" => {
245 let encoded = val
246 .as_str()
247 .ok_or_else(|| de::Error::custom("expected string for B"))?;
248 let bytes = BASE64
249 .decode(encoded)
250 .map_err(|e| de::Error::custom(format!("invalid base64: {e}")))?;
251 Ok(AttributeValue::B(bytes))
252 }
253 "BOOL" => {
254 let b = val
255 .as_bool()
256 .ok_or_else(|| de::Error::custom("expected boolean for BOOL"))?;
257 Ok(AttributeValue::BOOL(b))
258 }
259 "NULL" => {
260 if val.as_bool() != Some(true) {
268 return Err(de::Error::custom(format!(
269 "{}One or more parameter values were invalid: \
270 Null attribute value types must have the value of true",
271 crate::serde_errors::REQUEST_VALIDATION_MARKER
272 )));
273 }
274 Ok(AttributeValue::NULL(true))
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 match validate_dynamo_number(n) {
358 Ok(()) => Ok(()),
359 Err(crate::errors::DynoxideError::ValidationException(m)) => Err(format!("VALIDATION:{m}")),
360 Err(e) => Err(format!("VALIDATION:{e}")),
361 }
362}
363
364pub fn normalize_number_for_sort(num_str: &str) -> String {
378 let trimmed = num_str.trim();
379
380 if trimmed.is_empty() || trimmed == "0" || trimmed == "-0" || trimmed == "0.0" {
381 return zero_encoding();
382 }
383
384 let negative = trimmed.starts_with('-');
385 let abs_str = if negative { &trimmed[1..] } else { trimmed };
386
387 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
389
390 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
391 return zero_encoding();
392 }
393
394 if negative {
395 encode_negative(&mantissa_digits, exponent)
396 } else {
397 encode_positive(&mantissa_digits, exponent)
398 }
399}
400
401pub fn validate_dynamo_number(
407 num_str: &str,
408) -> std::result::Result<(), crate::errors::DynoxideError> {
409 if num_str.is_empty() {
410 return Err(crate::errors::DynoxideError::ValidationException(
411 "The parameter cannot be converted to a numeric value".to_string(),
412 ));
413 }
414
415 if !is_well_formed_dynamo_number(num_str) {
424 return Err(crate::errors::DynoxideError::ValidationException(format!(
425 "The parameter cannot be converted to a numeric value: {num_str}"
426 )));
427 }
428
429 let (mantissa_digits, exponent) = parse_number_parts(num_str);
432
433 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
435 return Ok(());
436 }
437
438 if mantissa_digits.len() > 38 {
440 return Err(crate::errors::DynoxideError::ValidationException(
441 "Attempting to store more than 38 significant digits in a Number".to_string(),
442 ));
443 }
444
445 if exponent > 126 {
448 return Err(crate::errors::DynoxideError::ValidationException(
449 "Number overflow. Attempting to store a number with magnitude larger than supported range"
450 .to_string(),
451 ));
452 }
453
454 if exponent < -129 {
461 return Err(crate::errors::DynoxideError::ValidationException(
462 "Number underflow. Attempting to store a number with magnitude smaller than supported range"
463 .to_string(),
464 ));
465 }
466
467 Ok(())
468}
469
470fn is_well_formed_dynamo_number(s: &str) -> bool {
476 let bytes = s.as_bytes();
477 let n = bytes.len();
478 let mut i = 0;
479
480 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
482 i += 1;
483 }
484
485 let mut coeff_digits = 0usize;
487 let mut dots = 0usize;
488 while i < n && (bytes[i].is_ascii_digit() || bytes[i] == b'.') {
489 if bytes[i] == b'.' {
490 dots += 1;
491 if dots > 1 {
492 return false;
493 }
494 } else {
495 coeff_digits += 1;
496 }
497 i += 1;
498 }
499 if coeff_digits == 0 {
500 return false;
501 }
502
503 if i < n && (bytes[i] == b'e' || bytes[i] == b'E') {
505 i += 1;
506 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
507 i += 1;
508 }
509 let mut exp_digits = 0usize;
510 while i < n && bytes[i].is_ascii_digit() {
511 exp_digits += 1;
512 i += 1;
513 }
514 if exp_digits == 0 {
515 return false;
516 }
517 }
518
519 i == n
521}
522
523pub fn number_size(num_str: &str) -> usize {
537 let trimmed = num_str.trim();
538 let negative = trimmed.starts_with('-');
539 let abs = trimmed.strip_prefix(['-', '+']).unwrap_or(trimmed);
540 let (mantissa, exponent) = parse_number_parts(abs);
541
542 let int_digits = exponent.clamp(0, mantissa.len() as i32) as usize;
546 let frac_digits = mantissa.len() - int_digits;
547
548 let sign = usize::from(negative && !mantissa.is_empty());
552
553 int_digits.div_ceil(2) + frac_digits.div_ceil(2) + 1 + sign
554}
555
556pub fn normalize_dynamo_number(num_str: &str) -> String {
564 let trimmed = num_str.trim();
565 if trimmed.is_empty() {
566 return "0".to_string();
567 }
568
569 let negative = trimmed.starts_with('-');
570 let abs_str = if negative {
571 &trimmed[1..]
572 } else {
573 trimmed.trim_start_matches('+')
574 };
575
576 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
577
578 if mantissa_digits.is_empty() {
580 return "0".to_string();
581 }
582
583 let num_digits = mantissa_digits.len() as i32;
588 let int_digits = exponent; let mut result = String::new();
591 if negative {
592 result.push('-');
593 }
594
595 if int_digits <= 0 {
596 result.push_str("0.");
598 for _ in 0..(-int_digits) {
599 result.push('0');
600 }
601 for &d in &mantissa_digits {
602 result.push((b'0' + d) as char);
603 }
604 } else if int_digits >= num_digits {
605 for &d in &mantissa_digits {
607 result.push((b'0' + d) as char);
608 }
609 for _ in 0..(int_digits - num_digits) {
610 result.push('0');
611 }
612 } else {
613 let int_part = int_digits as usize;
615 for &d in &mantissa_digits[..int_part] {
616 result.push((b'0' + d) as char);
617 }
618 result.push('.');
619 for &d in &mantissa_digits[int_part..] {
620 result.push((b'0' + d) as char);
621 }
622 }
623
624 result
625}
626
627fn zero_encoding() -> String {
628 format!("1{}{}", "0".repeat(4), "0".repeat(40))
630}
631
632fn encode_positive(mantissa: &[u8], exponent: i32) -> String {
633 let exp_encoded = (exponent + 5000) as u16;
634 let mantissa_str = mantissa_to_string(mantissa, 40);
635 format!("2{exp_encoded:04}{mantissa_str}")
636}
637
638fn encode_negative(mantissa: &[u8], exponent: i32) -> String {
639 let exp_encoded = 9999 - (exponent + 5000) as u16;
641 let mantissa_str = complement_mantissa(mantissa, 40);
642 format!("0{exp_encoded:04}{mantissa_str}")
643}
644
645pub(crate) fn parse_number_parts(s: &str) -> (Vec<u8>, i32) {
649 let (coeff, exp_part) = if let Some(pos) = s.to_ascii_lowercase().find('e') {
651 let coeff = &s[..pos];
652 let exp: i32 = s[pos + 1..].parse().unwrap_or(0);
653 (coeff, exp)
654 } else {
655 (s, 0)
656 };
657
658 let (int_part, frac_part) = if let Some(dot) = coeff.find('.') {
660 (&coeff[..dot], &coeff[dot + 1..])
661 } else {
662 (coeff, "")
663 };
664
665 let mut digits: Vec<u8> = Vec::new();
667 for ch in int_part.chars().chain(frac_part.chars()) {
668 if ch.is_ascii_digit() {
669 digits.push(ch as u8 - b'0');
670 }
671 }
672
673 if digits.is_empty() {
674 return (vec![], 0);
675 }
676
677 let int_len = int_part.chars().filter(|c| c.is_ascii_digit()).count() as i32;
679
680 let leading_zeros = digits.iter().take_while(|&&d| d == 0).count();
682 digits.drain(..leading_zeros);
683
684 while digits.last() == Some(&0) {
686 digits.pop();
687 }
688
689 if digits.is_empty() {
690 return (vec![], 0);
691 }
692
693 let exponent = int_len - leading_zeros as i32 + exp_part;
696
697 (digits, exponent)
698}
699
700fn mantissa_to_string(digits: &[u8], width: usize) -> String {
701 let mut s = String::with_capacity(width);
702 for &d in digits.iter().take(width) {
703 s.push((b'0' + d) as char);
704 }
705 while s.len() < width {
706 s.push('0');
707 }
708 s
709}
710
711fn complement_mantissa(digits: &[u8], width: usize) -> String {
712 let mut s = String::with_capacity(width);
713 for i in 0..width {
714 let d = if i < digits.len() { digits[i] } else { 0 };
715 s.push((b'0' + (9 - d)) as char);
716 }
717 s
718}
719
720fn hex_encode(bytes: &[u8]) -> String {
722 let mut s = String::with_capacity(bytes.len() * 2);
723 for &b in bytes {
724 s.push_str(&format!("{b:02x}"));
725 }
726 s
727}
728
729pub type Item = HashMap<String, AttributeValue>;
735
736#[derive(Debug, Clone, Default, Serialize, Deserialize)]
738pub struct SseSpecification {
739 #[serde(rename = "Enabled", default)]
740 pub enabled: Option<bool>,
741 #[serde(rename = "SSEType", default)]
742 pub sse_type: Option<String>,
743 #[serde(rename = "KMSMasterKeyId", default)]
744 pub kms_master_key_id: Option<String>,
745}
746
747#[derive(Debug, Clone, Default, Serialize, Deserialize)]
749pub struct Tag {
750 #[serde(rename = "Key")]
751 pub key: String,
752 #[serde(rename = "Value")]
753 pub value: String,
754}
755
756pub fn item_size(item: &Item) -> usize {
758 item.iter()
759 .map(|(name, value)| name.len() + value.size())
760 .sum()
761}
762
763pub const MAX_ITEM_SIZE: usize = 400 * 1024;
765
766#[derive(Debug, Clone, Serialize, Deserialize)]
769pub struct ItemCollectionMetrics {
770 #[serde(rename = "ItemCollectionKey")]
771 pub item_collection_key: HashMap<String, AttributeValue>,
772 #[serde(rename = "SizeEstimateRangeGB")]
773 pub size_estimate_range_gb: Vec<f64>,
774}
775
776#[derive(Debug, Clone, Default, Serialize, Deserialize)]
778pub struct ConsumedCapacity {
779 #[serde(rename = "TableName")]
780 pub table_name: String,
781 #[serde(rename = "CapacityUnits")]
782 pub capacity_units: f64,
783 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
784 pub read_capacity_units: Option<f64>,
785 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
786 pub write_capacity_units: Option<f64>,
787 #[serde(rename = "Table", skip_serializing_if = "Option::is_none")]
788 pub table: Option<CapacityDetail>,
789 #[serde(
790 rename = "GlobalSecondaryIndexes",
791 skip_serializing_if = "Option::is_none"
792 )]
793 pub global_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
794 #[serde(
795 rename = "LocalSecondaryIndexes",
796 skip_serializing_if = "Option::is_none"
797 )]
798 pub local_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
799 #[serde(rename = "VectorIndexes", skip_serializing_if = "Option::is_none")]
803 pub vector_indexes: Option<HashMap<String, VectorCapacityDetail>>,
804}
805
806#[derive(Debug, Clone, Default, Serialize, Deserialize)]
808pub struct CapacityDetail {
809 #[serde(rename = "CapacityUnits")]
810 pub capacity_units: f64,
811 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
812 pub read_capacity_units: Option<f64>,
813 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
814 pub write_capacity_units: Option<f64>,
815}
816
817#[derive(Debug, Clone, Default, Serialize, Deserialize)]
824pub struct VectorCapacityDetail {
825 #[serde(rename = "VectorWriteRequestBytes")]
826 pub vector_write_request_bytes: f64,
827}
828
829#[derive(Debug, Clone, Default, Serialize, Deserialize)]
837pub struct VectorSearchCapacity {
838 #[serde(rename = "VectorSearchRequestBytes")]
839 pub vector_search_request_bytes: f64,
840}
841
842pub const MIN_VECTOR_REQUEST_BYTES: f64 = 1024.0;
856
857pub fn vector_request_bytes(bytes: usize) -> f64 {
859 (bytes as f64).max(MIN_VECTOR_REQUEST_BYTES)
860}
861
862pub const TRANSACTIONAL_CAPACITY_FACTOR: f64 = 2.0;
867
868pub fn write_capacity_units(item_size_bytes: usize) -> f64 {
870 ((item_size_bytes as f64) / 1024.0).ceil().max(1.0)
871}
872
873pub fn table_write_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
880 let old = old_size.map(write_capacity_units);
881 let new = new_size.map(write_capacity_units);
882 match (old, new) {
883 (Some(old), Some(new)) => old.max(new),
884 (Some(units), None) | (None, Some(units)) => units,
885 (None, None) => write_capacity_units(0),
888 }
889}
890
891pub fn table_read_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
899 let larger = old_size.unwrap_or(0).max(new_size.unwrap_or(0));
900 read_capacity_units(larger)
901}
902
903pub fn read_capacity_units(item_size_bytes: usize) -> f64 {
907 ((item_size_bytes as f64) / 4096.0).ceil().max(1.0)
908}
909
910pub fn read_capacity_units_with_consistency(item_size_bytes: usize, consistent: bool) -> f64 {
915 let strongly = read_capacity_units(item_size_bytes);
916 if consistent { strongly } else { strongly / 2.0 }
917}
918
919pub fn capacity_wanted(mode: Option<&str>) -> bool {
927 matches!(mode, Some("TOTAL") | Some("INDEXES"))
928}
929
930pub fn vector_capacity_wanted(mode: Option<&str>) -> bool {
940 matches!(mode, Some("INDEXES"))
941}
942
943pub fn consumed_capacity(
945 table_name: &str,
946 capacity_units: f64,
947 mode: &Option<String>,
948) -> Option<ConsumedCapacity> {
949 let mode = mode.as_deref().unwrap_or("NONE");
950 match mode {
951 "TOTAL" => Some(ConsumedCapacity {
952 table_name: table_name.to_string(),
953 capacity_units,
954 table: None,
955 global_secondary_indexes: None,
956 local_secondary_indexes: None,
957 ..Default::default()
958 }),
959 "INDEXES" => Some(ConsumedCapacity {
960 table_name: table_name.to_string(),
961 capacity_units,
962 table: Some(CapacityDetail {
963 capacity_units,
964 ..Default::default()
965 }),
966 global_secondary_indexes: None,
967 local_secondary_indexes: None,
968 ..Default::default()
969 }),
970 _ => None,
971 }
972}
973
974pub fn consumed_capacity_with_indexes(
976 table_name: &str,
977 table_units: f64,
978 gsi_units: &HashMap<String, f64>,
979 mode: &Option<String>,
980) -> Option<ConsumedCapacity> {
981 consumed_capacity_with_secondary_indexes(
982 table_name,
983 table_units,
984 gsi_units,
985 &HashMap::new(),
986 mode,
987 )
988}
989
990pub fn consumed_capacity_with_secondary_indexes(
992 table_name: &str,
993 table_units: f64,
994 gsi_units: &HashMap<String, f64>,
995 lsi_units: &HashMap<String, f64>,
996 mode: &Option<String>,
997) -> Option<ConsumedCapacity> {
998 secondary_index_capacity(
999 table_name,
1000 table_units,
1001 gsi_units,
1002 lsi_units,
1003 mode,
1004 CapacityAxis::None,
1005 )
1006}
1007
1008#[derive(Clone, Copy, PartialEq)]
1016enum CapacityAxis {
1017 None,
1018 Write,
1019}
1020
1021impl CapacityAxis {
1022 fn write_units(self, units: f64) -> Option<f64> {
1024 match self {
1025 Self::None => None,
1026 Self::Write => Some(units),
1027 }
1028 }
1029}
1030
1031fn secondary_index_capacity(
1034 table_name: &str,
1035 table_units: f64,
1036 gsi_units: &HashMap<String, f64>,
1037 lsi_units: &HashMap<String, f64>,
1038 mode: &Option<String>,
1039 axis: CapacityAxis,
1040) -> Option<ConsumedCapacity> {
1041 let total = table_units + gsi_units.values().sum::<f64>() + lsi_units.values().sum::<f64>();
1042
1043 match mode.as_deref().unwrap_or("NONE") {
1044 "INDEXES" => Some(ConsumedCapacity {
1045 table_name: table_name.to_string(),
1046 capacity_units: total,
1047 write_capacity_units: axis.write_units(total),
1048 table: Some(CapacityDetail {
1049 capacity_units: table_units,
1050 write_capacity_units: axis.write_units(table_units),
1051 ..Default::default()
1052 }),
1053 global_secondary_indexes: capacity_detail_map(gsi_units, axis),
1054 local_secondary_indexes: capacity_detail_map(lsi_units, axis),
1055 ..Default::default()
1056 }),
1057 "TOTAL" => Some(ConsumedCapacity {
1058 table_name: table_name.to_string(),
1059 capacity_units: total,
1060 write_capacity_units: axis.write_units(total),
1061 ..Default::default()
1062 }),
1063 _ => None,
1064 }
1065}
1066
1067pub fn consumed_capacity_with_vector_indexes(
1075 table_name: &str,
1076 table_units: f64,
1077 gsi_units: &HashMap<String, f64>,
1078 lsi_units: &HashMap<String, f64>,
1079 vector_bytes: &HashMap<String, f64>,
1080 mode: &Option<String>,
1081) -> Option<ConsumedCapacity> {
1082 attach_vector_index_capacity(
1083 consumed_capacity_with_secondary_indexes(
1084 table_name,
1085 table_units,
1086 gsi_units,
1087 lsi_units,
1088 mode,
1089 ),
1090 vector_bytes,
1091 mode,
1092 )
1093}
1094
1095pub(crate) fn attach_vector_index_capacity(
1104 capacity: Option<ConsumedCapacity>,
1105 vector_bytes: &HashMap<String, f64>,
1106 mode: &Option<String>,
1107) -> Option<ConsumedCapacity> {
1108 let mut capacity = capacity?;
1109 if vector_bytes.is_empty() || !vector_capacity_wanted(mode.as_deref()) {
1110 return Some(capacity);
1111 }
1112 capacity.vector_indexes = Some(
1113 vector_bytes
1114 .iter()
1115 .map(|(name, bytes)| {
1116 (
1117 name.clone(),
1118 VectorCapacityDetail {
1119 vector_write_request_bytes: *bytes,
1120 },
1121 )
1122 })
1123 .collect(),
1124 );
1125 Some(capacity)
1126}
1127
1128pub fn transactional_read_capacity(
1133 table_name: &str,
1134 units: f64,
1135 mode: &Option<String>,
1136) -> Option<ConsumedCapacity> {
1137 match mode.as_deref().unwrap_or("NONE") {
1138 "TOTAL" => Some(ConsumedCapacity {
1139 table_name: table_name.to_string(),
1140 capacity_units: units,
1141 read_capacity_units: Some(units),
1142 ..Default::default()
1143 }),
1144 "INDEXES" => Some(ConsumedCapacity {
1145 table_name: table_name.to_string(),
1146 capacity_units: units,
1147 read_capacity_units: Some(units),
1148 table: Some(CapacityDetail {
1149 capacity_units: units,
1150 read_capacity_units: Some(units),
1151 ..Default::default()
1152 }),
1153 ..Default::default()
1154 }),
1155 _ => None,
1156 }
1157}
1158
1159pub fn build_transactional_capacity(
1166 table_units: &HashMap<String, f64>,
1167 mode: &Option<String>,
1168 builder: fn(&str, f64, &Option<String>) -> Option<ConsumedCapacity>,
1169) -> Option<Vec<ConsumedCapacity>> {
1170 if !capacity_wanted(mode.as_deref()) {
1171 return None;
1172 }
1173
1174 let mut tables: Vec<&String> = table_units.keys().collect();
1179 tables.sort();
1180
1181 Some(
1182 tables
1183 .into_iter()
1184 .filter_map(|table| builder(table, *table_units.get(table)?, mode))
1185 .collect(),
1186 )
1187}
1188
1189pub fn transactional_write_capacity_with_indexes(
1201 table_name: &str,
1202 table_units: f64,
1203 gsi_units: &HashMap<String, f64>,
1204 lsi_units: &HashMap<String, f64>,
1205 mode: &Option<String>,
1206) -> Option<ConsumedCapacity> {
1207 secondary_index_capacity(
1208 table_name,
1209 table_units,
1210 gsi_units,
1211 lsi_units,
1212 mode,
1213 CapacityAxis::Write,
1214 )
1215}
1216
1217fn capacity_detail_map(
1220 units: &HashMap<String, f64>,
1221 axis: CapacityAxis,
1222) -> Option<HashMap<String, CapacityDetail>> {
1223 if units.is_empty() {
1224 return None;
1225 }
1226 Some(
1227 units
1228 .iter()
1229 .map(|(name, &u)| {
1230 (
1231 name.clone(),
1232 CapacityDetail {
1233 capacity_units: u,
1234 write_capacity_units: axis.write_units(u),
1235 ..Default::default()
1236 },
1237 )
1238 })
1239 .collect(),
1240 )
1241}
1242
1243pub fn transactional_write_capacity(
1248 table_name: &str,
1249 units: f64,
1250 mode: &Option<String>,
1251) -> Option<ConsumedCapacity> {
1252 match mode.as_deref().unwrap_or("NONE") {
1253 "TOTAL" => Some(ConsumedCapacity {
1254 table_name: table_name.to_string(),
1255 capacity_units: units,
1256 write_capacity_units: Some(units),
1257 ..Default::default()
1258 }),
1259 "INDEXES" => Some(ConsumedCapacity {
1260 table_name: table_name.to_string(),
1261 capacity_units: units,
1262 write_capacity_units: Some(units),
1263 table: Some(CapacityDetail {
1264 capacity_units: units,
1265 write_capacity_units: Some(units),
1266 ..Default::default()
1267 }),
1268 ..Default::default()
1269 }),
1270 _ => None,
1271 }
1272}
1273
1274#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1276pub struct KeySchemaElement {
1277 #[serde(rename = "AttributeName", alias = "attribute_name")]
1278 pub attribute_name: String,
1279 #[serde(rename = "KeyType", alias = "key_type")]
1280 pub key_type: KeyType,
1281}
1282
1283#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1285pub enum KeyType {
1286 #[default]
1287 HASH,
1288 RANGE,
1289}
1290
1291#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1293pub struct AttributeDefinition {
1294 #[serde(rename = "AttributeName", alias = "attribute_name")]
1295 pub attribute_name: String,
1296 #[serde(rename = "AttributeType", alias = "attribute_type")]
1297 pub attribute_type: ScalarAttributeType,
1298}
1299
1300#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1302pub enum ScalarAttributeType {
1303 #[default]
1304 S,
1305 N,
1306 B,
1307}
1308
1309#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1311pub struct Projection {
1312 #[serde(
1313 rename = "ProjectionType",
1314 alias = "projection_type",
1315 default,
1316 skip_serializing_if = "Option::is_none"
1317 )]
1318 pub projection_type: Option<ProjectionType>,
1319 #[serde(
1320 rename = "NonKeyAttributes",
1321 alias = "non_key_attributes",
1322 skip_serializing_if = "Option::is_none"
1323 )]
1324 pub non_key_attributes: Option<Vec<String>>,
1325}
1326
1327#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1329#[allow(non_camel_case_types)]
1330pub enum ProjectionType {
1331 #[default]
1332 ALL,
1333 KEYS_ONLY,
1334 INCLUDE,
1335}
1336
1337#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1339pub struct GlobalSecondaryIndex {
1340 #[serde(rename = "IndexName", alias = "index_name")]
1341 pub index_name: String,
1342 #[serde(rename = "KeySchema", alias = "key_schema")]
1343 pub key_schema: Vec<KeySchemaElement>,
1344 #[serde(rename = "Projection", alias = "projection")]
1345 pub projection: Projection,
1346 #[serde(
1347 rename = "ProvisionedThroughput",
1348 alias = "provisioned_throughput",
1349 skip_serializing_if = "Option::is_none"
1350 )]
1351 pub provisioned_throughput: Option<ProvisionedThroughput>,
1352}
1353
1354#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1356pub struct LocalSecondaryIndex {
1357 #[serde(rename = "IndexName", alias = "index_name")]
1358 pub index_name: String,
1359 #[serde(rename = "KeySchema", alias = "key_schema")]
1360 pub key_schema: Vec<KeySchemaElement>,
1361 #[serde(rename = "Projection", alias = "projection")]
1362 pub projection: Projection,
1363}
1364
1365#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1368pub struct VectorAttributeDefinition {
1369 #[serde(rename = "AttributeName", alias = "attribute_name")]
1370 pub attribute_name: String,
1371}
1372
1373#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1376pub struct SearchSchemaElement {
1377 #[serde(rename = "AttributeName", alias = "attribute_name")]
1378 pub attribute_name: String,
1379 #[serde(
1380 rename = "SearchSchemaElementType",
1381 alias = "search_schema_element_type"
1382 )]
1383 pub search_schema_element_type: String,
1384}
1385
1386#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1391pub struct VectorIndex {
1392 #[serde(rename = "IndexName", alias = "index_name")]
1393 pub index_name: String,
1394 #[serde(rename = "VectorAttribute", alias = "vector_attribute")]
1395 pub vector_attribute: VectorAttributeDefinition,
1396 #[serde(
1397 rename = "SearchSchema",
1398 alias = "search_schema",
1399 default,
1400 skip_serializing_if = "Option::is_none"
1401 )]
1402 pub search_schema: Option<Vec<SearchSchemaElement>>,
1403 #[serde(rename = "Projection", alias = "projection")]
1404 pub projection: Projection,
1405 #[serde(rename = "Dimensions", alias = "dimensions")]
1406 pub dimensions: u32,
1407 #[serde(rename = "DistanceFunction", alias = "distance_function")]
1408 pub distance_function: String,
1409}
1410
1411#[derive(Debug, Clone, PartialEq)]
1416pub enum VectorValueError {
1417 NotAList,
1419 WrongDimensions {
1421 actual: usize,
1423 },
1424 ElementNotANumber {
1426 position: usize,
1428 actual: &'static str,
1430 },
1431 ElementOutOfRange {
1435 position: usize,
1437 value: String,
1439 },
1440}
1441
1442pub fn check_vector_f32_values(
1452 value: &AttributeValue,
1453 dimensions: u32,
1454) -> std::result::Result<Vec<f32>, VectorValueError> {
1455 let AttributeValue::L(elems) = value else {
1456 return Err(VectorValueError::NotAList);
1457 };
1458 if elems.len() != dimensions as usize {
1459 return Err(VectorValueError::WrongDimensions {
1460 actual: elems.len(),
1461 });
1462 }
1463 let mut out = Vec::with_capacity(elems.len());
1464 for (position, elem) in elems.iter().enumerate() {
1465 let AttributeValue::N(n) = elem else {
1466 return Err(VectorValueError::ElementNotANumber {
1467 position,
1468 actual: elem.type_name(),
1469 });
1470 };
1471 match n.parse::<f32>() {
1474 Ok(v) if v.is_finite() => out.push(v),
1475 _ => {
1476 return Err(VectorValueError::ElementOutOfRange {
1477 position,
1478 value: n.clone(),
1479 });
1480 }
1481 }
1482 }
1483 Ok(out)
1484}
1485
1486pub fn vector_f32_values(value: &AttributeValue, dimensions: u32) -> Option<Vec<f32>> {
1491 check_vector_f32_values(value, dimensions).ok()
1492}
1493
1494pub fn f32_number_string(v: f32) -> String {
1499 serde_json::to_string(&v).unwrap_or_else(|_| "0.0".to_string())
1500}
1501
1502#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1504pub struct ProvisionedThroughput {
1505 #[serde(rename = "ReadCapacityUnits", alias = "read_capacity_units", default)]
1506 pub read_capacity_units: Option<i64>,
1507 #[serde(rename = "WriteCapacityUnits", alias = "write_capacity_units", default)]
1508 pub write_capacity_units: Option<i64>,
1509}
1510
1511#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1513pub struct OnDemandThroughput {
1514 #[serde(
1515 rename = "MaxReadRequestUnits",
1516 alias = "max_read_request_units",
1517 default,
1518 skip_serializing_if = "Option::is_none"
1519 )]
1520 pub max_read_request_units: Option<i64>,
1521 #[serde(
1522 rename = "MaxWriteRequestUnits",
1523 alias = "max_write_request_units",
1524 default,
1525 skip_serializing_if = "Option::is_none"
1526 )]
1527 pub max_write_request_units: Option<i64>,
1528}
1529
1530#[derive(Debug, Clone, PartialEq)]
1536pub struct ConversionError {
1537 pub expected: &'static str,
1539 pub actual: &'static str,
1541}
1542
1543impl fmt::Display for ConversionError {
1544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1545 write!(f, "expected {}, got {}", self.expected, self.actual)
1546 }
1547}
1548
1549impl std::error::Error for ConversionError {}
1550
1551impl From<String> for AttributeValue {
1554 fn from(value: String) -> Self {
1555 AttributeValue::S(value)
1556 }
1557}
1558
1559impl From<&str> for AttributeValue {
1560 fn from(value: &str) -> Self {
1561 AttributeValue::S(value.to_string())
1562 }
1563}
1564
1565impl From<bool> for AttributeValue {
1566 fn from(value: bool) -> Self {
1567 AttributeValue::BOOL(value)
1568 }
1569}
1570
1571impl From<Vec<u8>> for AttributeValue {
1572 fn from(value: Vec<u8>) -> Self {
1573 AttributeValue::B(value)
1574 }
1575}
1576
1577impl From<&[u8]> for AttributeValue {
1578 fn from(value: &[u8]) -> Self {
1579 AttributeValue::B(value.to_vec())
1580 }
1581}
1582
1583macro_rules! impl_from_integer {
1585 ($($t:ty),+) => {
1586 $(
1587 impl From<$t> for AttributeValue {
1588 fn from(value: $t) -> Self {
1589 AttributeValue::N(value.to_string())
1590 }
1591 }
1592 )+
1593 };
1594}
1595
1596impl_from_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1597
1598impl From<HashMap<String, AttributeValue>> for AttributeValue {
1600 fn from(value: HashMap<String, AttributeValue>) -> Self {
1601 AttributeValue::M(value)
1602 }
1603}
1604
1605impl From<Vec<AttributeValue>> for AttributeValue {
1606 fn from(value: Vec<AttributeValue>) -> Self {
1607 AttributeValue::L(value)
1608 }
1609}
1610
1611impl From<HashSet<String>> for AttributeValue {
1612 fn from(value: HashSet<String>) -> Self {
1613 AttributeValue::SS(value.into_iter().collect())
1614 }
1615}
1616
1617impl From<BTreeSet<String>> for AttributeValue {
1618 fn from(value: BTreeSet<String>) -> Self {
1619 AttributeValue::SS(value.into_iter().collect())
1620 }
1621}
1622
1623impl TryFrom<f64> for AttributeValue {
1626 type Error = ConversionError;
1627
1628 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
1629 if value.is_finite() {
1630 Ok(AttributeValue::N(value.to_string()))
1631 } else {
1632 Err(ConversionError {
1633 expected: "finite f64",
1634 actual: "NaN or Infinity",
1635 })
1636 }
1637 }
1638}
1639
1640impl TryFrom<f32> for AttributeValue {
1641 type Error = ConversionError;
1642
1643 fn try_from(value: f32) -> std::result::Result<Self, Self::Error> {
1644 if value.is_finite() {
1645 Ok(AttributeValue::N(value.to_string()))
1646 } else {
1647 Err(ConversionError {
1648 expected: "finite f32",
1649 actual: "NaN or Infinity",
1650 })
1651 }
1652 }
1653}
1654
1655impl TryFrom<AttributeValue> for String {
1658 type Error = ConversionError;
1659
1660 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1661 match value {
1662 AttributeValue::S(s) => Ok(s),
1663 other => Err(ConversionError {
1664 expected: "S",
1665 actual: other.type_name(),
1666 }),
1667 }
1668 }
1669}
1670
1671impl TryFrom<AttributeValue> for bool {
1672 type Error = ConversionError;
1673
1674 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1675 match value {
1676 AttributeValue::BOOL(b) => Ok(b),
1677 other => Err(ConversionError {
1678 expected: "BOOL",
1679 actual: other.type_name(),
1680 }),
1681 }
1682 }
1683}
1684
1685impl TryFrom<AttributeValue> for Vec<u8> {
1686 type Error = ConversionError;
1687
1688 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1689 match value {
1690 AttributeValue::B(b) => Ok(b),
1691 other => Err(ConversionError {
1692 expected: "B",
1693 actual: other.type_name(),
1694 }),
1695 }
1696 }
1697}
1698
1699macro_rules! impl_try_from_av_integer {
1700 ($($t:ty),+) => {
1701 $(
1702 impl TryFrom<AttributeValue> for $t {
1703 type Error = ConversionError;
1704
1705 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1706 match value {
1707 AttributeValue::N(n) => n.parse::<$t>().map_err(|_| ConversionError {
1708 expected: stringify!($t),
1709 actual: "N (parse failed)",
1710 }),
1711 other => Err(ConversionError {
1712 expected: "N",
1713 actual: other.type_name(),
1714 }),
1715 }
1716 }
1717 }
1718 )+
1719 };
1720}
1721
1722impl_try_from_av_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1723
1724impl TryFrom<AttributeValue> for f64 {
1725 type Error = ConversionError;
1726
1727 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1728 match value {
1729 AttributeValue::N(n) => n.parse::<f64>().map_err(|_| ConversionError {
1730 expected: "f64",
1731 actual: "N (parse failed)",
1732 }),
1733 other => Err(ConversionError {
1734 expected: "N",
1735 actual: other.type_name(),
1736 }),
1737 }
1738 }
1739}
1740
1741impl TryFrom<AttributeValue> for f32 {
1742 type Error = ConversionError;
1743
1744 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1745 match value {
1746 AttributeValue::N(n) => n.parse::<f32>().map_err(|_| ConversionError {
1747 expected: "f32",
1748 actual: "N (parse failed)",
1749 }),
1750 other => Err(ConversionError {
1751 expected: "N",
1752 actual: other.type_name(),
1753 }),
1754 }
1755 }
1756}
1757
1758impl TryFrom<AttributeValue> for HashMap<String, AttributeValue> {
1759 type Error = ConversionError;
1760
1761 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1762 match value {
1763 AttributeValue::M(m) => Ok(m),
1764 other => Err(ConversionError {
1765 expected: "M",
1766 actual: other.type_name(),
1767 }),
1768 }
1769 }
1770}
1771
1772impl TryFrom<AttributeValue> for Vec<AttributeValue> {
1773 type Error = ConversionError;
1774
1775 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1776 match value {
1777 AttributeValue::L(l) => Ok(l),
1778 other => Err(ConversionError {
1779 expected: "L",
1780 actual: other.type_name(),
1781 }),
1782 }
1783 }
1784}
1785
1786impl TryFrom<AttributeValue> for Vec<String> {
1787 type Error = ConversionError;
1788
1789 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1790 match value {
1791 AttributeValue::SS(ss) => Ok(ss),
1792 AttributeValue::L(l) => {
1793 l.into_iter()
1795 .map(|av| match av {
1796 AttributeValue::S(s) => Ok(s),
1797 other => Err(ConversionError {
1798 expected: "S (within L)",
1799 actual: other.type_name(),
1800 }),
1801 })
1802 .collect()
1803 }
1804 other => Err(ConversionError {
1805 expected: "SS or L",
1806 actual: other.type_name(),
1807 }),
1808 }
1809 }
1810}
1811
1812#[cfg(test)]
1813mod tests {
1814 use super::*;
1815
1816 #[test]
1817 fn test_serialize_string() {
1818 let val = AttributeValue::S("hello".to_string());
1819 let json = serde_json::to_string(&val).unwrap();
1820 assert_eq!(json, r#"{"S":"hello"}"#);
1821 }
1822
1823 #[test]
1824 fn test_serialize_number() {
1825 let val = AttributeValue::N("42".to_string());
1826 let json = serde_json::to_string(&val).unwrap();
1827 assert_eq!(json, r#"{"N":"42"}"#);
1828 }
1829
1830 #[test]
1831 fn test_serialize_binary() {
1832 let val = AttributeValue::B(vec![1, 2, 3]);
1833 let json = serde_json::to_string(&val).unwrap();
1834 assert_eq!(json, r#"{"B":"AQID"}"#);
1835 }
1836
1837 #[test]
1838 fn test_serialize_bool() {
1839 let val = AttributeValue::BOOL(true);
1840 let json = serde_json::to_string(&val).unwrap();
1841 assert_eq!(json, r#"{"BOOL":true}"#);
1842 }
1843
1844 #[test]
1845 fn test_serialize_null() {
1846 let val = AttributeValue::NULL(true);
1847 let json = serde_json::to_string(&val).unwrap();
1848 assert_eq!(json, r#"{"NULL":true}"#);
1849 }
1850
1851 #[test]
1852 fn test_deserialize_null_true() {
1853 let val: AttributeValue = serde_json::from_str(r#"{"NULL":true}"#).unwrap();
1854 assert_eq!(val, AttributeValue::NULL(true));
1855 }
1856
1857 #[test]
1858 fn test_deserialize_null_false_rejected() {
1859 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":false}"#).unwrap_err();
1862 assert!(
1863 err.to_string().contains("must have the value of true"),
1864 "unexpected error: {err}"
1865 );
1866 }
1867
1868 #[test]
1869 fn test_deserialize_null_non_boolean_rejected() {
1870 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":"no"}"#).unwrap_err();
1872 assert!(
1873 err.to_string().contains("must have the value of true"),
1874 "unexpected error: {err}"
1875 );
1876 }
1877
1878 #[test]
1879 fn test_serialize_string_set() {
1880 let val = AttributeValue::SS(vec!["a".to_string(), "b".to_string()]);
1881 let json = serde_json::to_string(&val).unwrap();
1882 assert_eq!(json, r#"{"SS":["a","b"]}"#);
1883 }
1884
1885 #[test]
1886 fn test_serialize_list() {
1887 let val = AttributeValue::L(vec![
1888 AttributeValue::S("hello".to_string()),
1889 AttributeValue::N("42".to_string()),
1890 ]);
1891 let json = serde_json::to_string(&val).unwrap();
1892 assert_eq!(json, r#"{"L":[{"S":"hello"},{"N":"42"}]}"#);
1893 }
1894
1895 #[test]
1896 fn test_serialize_map() {
1897 let mut m = HashMap::new();
1898 m.insert("key".to_string(), AttributeValue::S("value".to_string()));
1899 let val = AttributeValue::M(m);
1900 let json = serde_json::to_string(&val).unwrap();
1901 assert_eq!(json, r#"{"M":{"key":{"S":"value"}}}"#);
1902 }
1903
1904 #[test]
1905 fn test_round_trip_all_types() {
1906 let values = vec![
1907 AttributeValue::S("hello".to_string()),
1908 AttributeValue::N("42.5".to_string()),
1909 AttributeValue::B(vec![0, 255, 128]),
1910 AttributeValue::BOOL(false),
1911 AttributeValue::NULL(true),
1912 AttributeValue::SS(vec!["x".to_string(), "y".to_string()]),
1913 AttributeValue::NS(vec!["1".to_string(), "2.5".to_string()]),
1914 AttributeValue::BS(vec![vec![1], vec![2, 3]]),
1915 AttributeValue::L(vec![
1916 AttributeValue::S("nested".to_string()),
1917 AttributeValue::N("99".to_string()),
1918 ]),
1919 ];
1920
1921 for val in values {
1922 let json = serde_json::to_string(&val).unwrap();
1923 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
1924 assert_eq!(val, deserialized, "Round-trip failed for {json}");
1925 }
1926 }
1927
1928 #[test]
1929 fn test_size_string() {
1930 let val = AttributeValue::S("hello".to_string());
1931 assert_eq!(val.size(), 5);
1932 }
1933
1934 #[test]
1935 fn test_size_number() {
1936 let val = AttributeValue::N("42".to_string());
1938 assert_eq!(val.size(), 2);
1939 }
1940
1941 #[test]
1942 fn test_size_number_matches_dynamodb() {
1943 let cases: &[(&str, usize)] = &[
1951 ("1", 2),
1953 ("12", 2),
1954 ("123", 3),
1955 ("1234", 3),
1956 ("12345", 4),
1957 ("123456", 4),
1958 ("1234567", 5),
1959 ("12345678", 5),
1960 ("123456789", 6),
1961 ("1234567891", 6),
1962 ("12345678912", 7),
1963 ("123456789123", 7),
1964 ("1234567890123456789", 11),
1965 ("12345678901234567890123456789012345678", 20),
1966 ("0042", 2),
1968 ("100", 2),
1969 ("1010", 3),
1970 ("0.0000001", 2),
1971 ("0", 1),
1972 ("1E125", 2),
1974 ("1E-100", 2),
1975 ("1.5", 3),
1978 ("1.2", 3),
1979 ("1.200", 3),
1980 ("1.234", 4),
1981 ("3.14159", 5),
1982 ("100.5", 4),
1983 ("0.15", 2),
1984 ("15", 2),
1985 ("-42", 3),
1987 ("-12345", 5),
1988 ("-1E125", 3),
1989 ("-0.15", 3),
1990 ("-0", 1),
1991 ];
1992 for (literal, expected) in cases {
1993 assert_eq!(
1994 AttributeValue::N((*literal).to_string()).size(),
1995 *expected,
1996 "size of N {literal:?}"
1997 );
1998 }
1999 }
2000
2001 #[test]
2002 fn test_size_number_survives_normalisation() {
2003 for literal in [
2007 "1E125",
2008 "1E-100",
2009 "0042",
2010 "1.200",
2011 "0.0000001",
2012 "100",
2013 "3.14159",
2014 "42",
2015 ] {
2016 let raw = AttributeValue::N(literal.to_string()).size();
2017 let normalised = AttributeValue::N(normalize_dynamo_number(literal)).size();
2018 assert_eq!(
2019 raw, normalised,
2020 "N {literal:?} changed size when normalised"
2021 );
2022 }
2023 }
2024
2025 #[test]
2026 fn test_size_bool() {
2027 assert_eq!(AttributeValue::BOOL(true).size(), 1);
2028 }
2029
2030 #[test]
2031 fn test_size_null() {
2032 assert_eq!(AttributeValue::NULL(true).size(), 1);
2033 }
2034
2035 #[test]
2036 fn test_key_string_s() {
2037 let val = AttributeValue::S("hello".to_string());
2038 assert_eq!(val.to_key_string(), Some("S:hello".to_string()));
2039 }
2040
2041 #[test]
2042 fn test_key_string_n() {
2043 let val = AttributeValue::N("42".to_string());
2044 let key = val.to_key_string().unwrap();
2045 assert!(key.starts_with("N:"));
2046 }
2047
2048 #[test]
2049 fn test_key_string_b() {
2050 let val = AttributeValue::B(vec![0xff, 0x00, 0xab]);
2051 assert_eq!(val.to_key_string(), Some("B:ff00ab".to_string()));
2052 }
2053
2054 #[test]
2055 fn test_key_string_non_key_type_returns_none() {
2056 assert_eq!(AttributeValue::BOOL(true).to_key_string(), None);
2057 assert_eq!(AttributeValue::L(vec![]).to_key_string(), None);
2058 }
2059
2060 #[test]
2062 fn test_number_sort_ordering() {
2063 let numbers = vec![
2064 "-1000", "-100", "-10", "-1", "-0.5", "-0.001", "0", "0.001", "0.5", "1", "10", "100",
2065 "1000",
2066 ];
2067 let encoded: Vec<String> = numbers
2068 .iter()
2069 .map(|n| normalize_number_for_sort(n))
2070 .collect();
2071
2072 for i in 0..encoded.len() - 1 {
2073 assert!(
2074 encoded[i] < encoded[i + 1],
2075 "Sort order broken: {} ({}) should be < {} ({})",
2076 numbers[i],
2077 encoded[i],
2078 numbers[i + 1],
2079 encoded[i + 1]
2080 );
2081 }
2082 }
2083
2084 #[test]
2085 fn test_number_sort_zero_variants() {
2086 let z1 = normalize_number_for_sort("0");
2087 let z2 = normalize_number_for_sort("-0");
2088 let z3 = normalize_number_for_sort("0.0");
2089 assert_eq!(z1, z2);
2090 assert_eq!(z2, z3);
2091 }
2092
2093 #[test]
2094 fn test_number_sort_decimals() {
2095 let a = normalize_number_for_sort("1.5");
2096 let b = normalize_number_for_sort("2.5");
2097 assert!(a < b);
2098
2099 let c = normalize_number_for_sort("0.001");
2100 let d = normalize_number_for_sort("0.01");
2101 assert!(c < d);
2102 }
2103
2104 #[test]
2105 fn test_number_sort_scientific() {
2106 let a = normalize_number_for_sort("1e10");
2107 let b = normalize_number_for_sort("1e11");
2108 assert!(a < b);
2109
2110 let c = normalize_number_for_sort("-1e11");
2111 let d = normalize_number_for_sort("-1e10");
2112 assert!(c < d);
2113 }
2114
2115 #[test]
2120 fn test_validate_number_accepts_dynamodb_grammar() {
2121 for input in [
2122 "+5", "+1.5", "+0", "-0", "+0.0", "+1e2", "1e+2", "1.5E+3", "-7", "+.5", ".5", "5.",
2123 "00042", "1.23E10", "+1e-2", "1E-130", "+1E-130",
2124 ] {
2125 assert!(
2126 validate_dynamo_number(input).is_ok(),
2127 "expected {input} to validate, got {:?}",
2128 validate_dynamo_number(input)
2129 );
2130 }
2131 }
2132
2133 #[test]
2134 fn test_validate_number_rejects_malformed() {
2135 for input in [
2138 "+e2", "e2", "+1+2", "1+2", "+1.2.3", "1.2.3", "++5", "+-5", "-+5", "+", "-", "1e",
2139 "1e+", ".", "1.2e3.4", "0x5", "NaN", "Infinity", "1_000", " 5", "5 ", "1 5", "",
2140 ] {
2141 assert!(
2142 matches!(
2143 validate_dynamo_number(input),
2144 Err(crate::errors::DynoxideError::ValidationException(_))
2145 ),
2146 "expected {input:?} to be rejected with ValidationException, got {:?}",
2147 validate_dynamo_number(input)
2148 );
2149 }
2150 }
2151
2152 #[test]
2153 fn test_normalize_number_matches_dynamodb() {
2154 for (input, stored) in [
2155 ("+5", "5"),
2156 ("+1.5", "1.5"),
2157 ("+0", "0"),
2158 ("-0", "0"),
2159 ("+0.0", "0"),
2160 ("+1e2", "100"),
2161 ("1e+2", "100"),
2162 ("1.5E+3", "1500"),
2163 ("-7", "-7"),
2164 ("+.5", "0.5"),
2165 (".5", "0.5"),
2166 ("5.", "5"),
2167 ("00042", "42"),
2168 ("1.23E10", "12300000000"),
2169 ("+1e-2", "0.01"),
2170 ] {
2171 assert_eq!(
2172 normalize_dynamo_number(input),
2173 stored,
2174 "{input} should normalise to {stored}"
2175 );
2176 }
2177 }
2178
2179 #[test]
2180 fn test_type_name() {
2181 assert_eq!(AttributeValue::S("".to_string()).type_name(), "S");
2182 assert_eq!(AttributeValue::N("0".to_string()).type_name(), "N");
2183 assert_eq!(AttributeValue::B(vec![]).type_name(), "B");
2184 assert_eq!(AttributeValue::BOOL(true).type_name(), "BOOL");
2185 assert_eq!(AttributeValue::NULL(true).type_name(), "NULL");
2186 assert_eq!(AttributeValue::SS(vec![]).type_name(), "SS");
2187 assert_eq!(AttributeValue::NS(vec![]).type_name(), "NS");
2188 assert_eq!(AttributeValue::BS(vec![]).type_name(), "BS");
2189 assert_eq!(AttributeValue::L(vec![]).type_name(), "L");
2190 assert_eq!(AttributeValue::M(HashMap::new()).type_name(), "M");
2191 }
2192}