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 if validate_dynamo_number(trimmed).is_err() {
574 return trimmed.to_string();
575 }
576
577 let negative = trimmed.starts_with('-');
578 let abs_str = if negative {
579 &trimmed[1..]
580 } else {
581 trimmed.trim_start_matches('+')
582 };
583
584 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
585
586 if mantissa_digits.is_empty() {
588 return "0".to_string();
589 }
590
591 let num_digits = mantissa_digits.len() as i32;
596 let int_digits = exponent; let mut result = String::new();
599 if negative {
600 result.push('-');
601 }
602
603 if int_digits <= 0 {
604 result.push_str("0.");
606 for _ in 0..(-int_digits) {
607 result.push('0');
608 }
609 for &d in &mantissa_digits {
610 result.push((b'0' + d) as char);
611 }
612 } else if int_digits >= num_digits {
613 for &d in &mantissa_digits {
615 result.push((b'0' + d) as char);
616 }
617 for _ in 0..(int_digits - num_digits) {
618 result.push('0');
619 }
620 } else {
621 let int_part = int_digits as usize;
623 for &d in &mantissa_digits[..int_part] {
624 result.push((b'0' + d) as char);
625 }
626 result.push('.');
627 for &d in &mantissa_digits[int_part..] {
628 result.push((b'0' + d) as char);
629 }
630 }
631
632 result
633}
634
635fn zero_encoding() -> String {
636 format!("1{}{}", "0".repeat(4), "0".repeat(40))
638}
639
640fn encode_positive(mantissa: &[u8], exponent: i32) -> String {
641 let exp_encoded = (exponent + 5000) as u16;
642 let mantissa_str = mantissa_to_string(mantissa, 40);
643 format!("2{exp_encoded:04}{mantissa_str}")
644}
645
646fn encode_negative(mantissa: &[u8], exponent: i32) -> String {
647 let exp_encoded = 9999 - (exponent + 5000) as u16;
649 let mantissa_str = complement_mantissa(mantissa, 40);
650 format!("0{exp_encoded:04}{mantissa_str}")
651}
652
653pub(crate) fn parse_number_parts(s: &str) -> (Vec<u8>, i32) {
657 let (coeff, exp_part) = if let Some(pos) = s.to_ascii_lowercase().find('e') {
659 let coeff = &s[..pos];
660 let exp: i32 = s[pos + 1..].parse().unwrap_or(0);
661 (coeff, exp)
662 } else {
663 (s, 0)
664 };
665
666 let (int_part, frac_part) = if let Some(dot) = coeff.find('.') {
668 (&coeff[..dot], &coeff[dot + 1..])
669 } else {
670 (coeff, "")
671 };
672
673 let mut digits: Vec<u8> = Vec::new();
675 for ch in int_part.chars().chain(frac_part.chars()) {
676 if ch.is_ascii_digit() {
677 digits.push(ch as u8 - b'0');
678 }
679 }
680
681 if digits.is_empty() {
682 return (vec![], 0);
683 }
684
685 let int_len = int_part.chars().filter(|c| c.is_ascii_digit()).count() as i32;
687
688 let leading_zeros = digits.iter().take_while(|&&d| d == 0).count();
690 digits.drain(..leading_zeros);
691
692 while digits.last() == Some(&0) {
694 digits.pop();
695 }
696
697 if digits.is_empty() {
698 return (vec![], 0);
699 }
700
701 let exponent = int_len - leading_zeros as i32 + exp_part;
704
705 (digits, exponent)
706}
707
708fn mantissa_to_string(digits: &[u8], width: usize) -> String {
709 let mut s = String::with_capacity(width);
710 for &d in digits.iter().take(width) {
711 s.push((b'0' + d) as char);
712 }
713 while s.len() < width {
714 s.push('0');
715 }
716 s
717}
718
719fn complement_mantissa(digits: &[u8], width: usize) -> String {
720 let mut s = String::with_capacity(width);
721 for i in 0..width {
722 let d = if i < digits.len() { digits[i] } else { 0 };
723 s.push((b'0' + (9 - d)) as char);
724 }
725 s
726}
727
728fn hex_encode(bytes: &[u8]) -> String {
730 let mut s = String::with_capacity(bytes.len() * 2);
731 for &b in bytes {
732 s.push_str(&format!("{b:02x}"));
733 }
734 s
735}
736
737pub type Item = HashMap<String, AttributeValue>;
743
744#[derive(Debug, Clone, Default, Serialize, Deserialize)]
746pub struct SseSpecification {
747 #[serde(rename = "Enabled", default)]
748 pub enabled: Option<bool>,
749 #[serde(rename = "SSEType", default)]
750 pub sse_type: Option<String>,
751 #[serde(rename = "KMSMasterKeyId", default)]
752 pub kms_master_key_id: Option<String>,
753}
754
755#[derive(Debug, Clone, Default, Serialize, Deserialize)]
757pub struct Tag {
758 #[serde(rename = "Key")]
759 pub key: String,
760 #[serde(rename = "Value")]
761 pub value: String,
762}
763
764pub fn item_size(item: &Item) -> usize {
766 item.iter()
767 .map(|(name, value)| name.len() + value.size())
768 .sum()
769}
770
771pub const MAX_ITEM_SIZE: usize = 400 * 1024;
773
774#[derive(Debug, Clone, Serialize, Deserialize)]
777pub struct ItemCollectionMetrics {
778 #[serde(rename = "ItemCollectionKey")]
779 pub item_collection_key: HashMap<String, AttributeValue>,
780 #[serde(rename = "SizeEstimateRangeGB")]
781 pub size_estimate_range_gb: Vec<f64>,
782}
783
784#[derive(Debug, Clone, Default, Serialize, Deserialize)]
786pub struct ConsumedCapacity {
787 #[serde(rename = "TableName")]
788 pub table_name: String,
789 #[serde(rename = "CapacityUnits")]
790 pub capacity_units: f64,
791 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
792 pub read_capacity_units: Option<f64>,
793 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
794 pub write_capacity_units: Option<f64>,
795 #[serde(rename = "Table", skip_serializing_if = "Option::is_none")]
796 pub table: Option<CapacityDetail>,
797 #[serde(
798 rename = "GlobalSecondaryIndexes",
799 skip_serializing_if = "Option::is_none"
800 )]
801 pub global_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
802 #[serde(
803 rename = "LocalSecondaryIndexes",
804 skip_serializing_if = "Option::is_none"
805 )]
806 pub local_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
807 #[serde(rename = "VectorIndexes", skip_serializing_if = "Option::is_none")]
811 pub vector_indexes: Option<HashMap<String, VectorCapacityDetail>>,
812}
813
814#[derive(Debug, Clone, Default, Serialize, Deserialize)]
816pub struct CapacityDetail {
817 #[serde(rename = "CapacityUnits")]
818 pub capacity_units: f64,
819 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
820 pub read_capacity_units: Option<f64>,
821 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
822 pub write_capacity_units: Option<f64>,
823}
824
825#[derive(Debug, Clone, Default, Serialize, Deserialize)]
832pub struct VectorCapacityDetail {
833 #[serde(rename = "VectorWriteRequestBytes")]
834 pub vector_write_request_bytes: f64,
835}
836
837#[derive(Debug, Clone, Default, Serialize, Deserialize)]
845pub struct VectorSearchCapacity {
846 #[serde(rename = "VectorSearchRequestBytes")]
847 pub vector_search_request_bytes: f64,
848}
849
850pub const MIN_VECTOR_REQUEST_BYTES: f64 = 1024.0;
864
865pub fn vector_request_bytes(bytes: usize) -> f64 {
867 (bytes as f64).max(MIN_VECTOR_REQUEST_BYTES)
868}
869
870pub const TRANSACTIONAL_CAPACITY_FACTOR: f64 = 2.0;
875
876pub fn write_capacity_units(item_size_bytes: usize) -> f64 {
878 ((item_size_bytes as f64) / 1024.0).ceil().max(1.0)
879}
880
881pub fn table_write_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
888 let old = old_size.map(write_capacity_units);
889 let new = new_size.map(write_capacity_units);
890 match (old, new) {
891 (Some(old), Some(new)) => old.max(new),
892 (Some(units), None) | (None, Some(units)) => units,
893 (None, None) => write_capacity_units(0),
896 }
897}
898
899pub fn table_read_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
907 let larger = old_size.unwrap_or(0).max(new_size.unwrap_or(0));
908 read_capacity_units(larger)
909}
910
911pub fn read_capacity_units(item_size_bytes: usize) -> f64 {
915 ((item_size_bytes as f64) / 4096.0).ceil().max(1.0)
916}
917
918pub fn read_capacity_units_with_consistency(item_size_bytes: usize, consistent: bool) -> f64 {
923 let strongly = read_capacity_units(item_size_bytes);
924 if consistent { strongly } else { strongly / 2.0 }
925}
926
927pub fn capacity_wanted(mode: Option<&str>) -> bool {
935 matches!(mode, Some("TOTAL") | Some("INDEXES"))
936}
937
938pub fn vector_capacity_wanted(mode: Option<&str>) -> bool {
948 matches!(mode, Some("INDEXES"))
949}
950
951pub fn consumed_capacity(
953 table_name: &str,
954 capacity_units: f64,
955 mode: &Option<String>,
956) -> Option<ConsumedCapacity> {
957 let mode = mode.as_deref().unwrap_or("NONE");
958 match mode {
959 "TOTAL" => Some(ConsumedCapacity {
960 table_name: table_name.to_string(),
961 capacity_units,
962 table: None,
963 global_secondary_indexes: None,
964 local_secondary_indexes: None,
965 ..Default::default()
966 }),
967 "INDEXES" => Some(ConsumedCapacity {
968 table_name: table_name.to_string(),
969 capacity_units,
970 table: Some(CapacityDetail {
971 capacity_units,
972 ..Default::default()
973 }),
974 global_secondary_indexes: None,
975 local_secondary_indexes: None,
976 ..Default::default()
977 }),
978 _ => None,
979 }
980}
981
982pub fn consumed_capacity_with_indexes(
984 table_name: &str,
985 table_units: f64,
986 gsi_units: &HashMap<String, f64>,
987 mode: &Option<String>,
988) -> Option<ConsumedCapacity> {
989 consumed_capacity_with_secondary_indexes(
990 table_name,
991 table_units,
992 gsi_units,
993 &HashMap::new(),
994 mode,
995 )
996}
997
998pub fn consumed_capacity_with_secondary_indexes(
1000 table_name: &str,
1001 table_units: f64,
1002 gsi_units: &HashMap<String, f64>,
1003 lsi_units: &HashMap<String, f64>,
1004 mode: &Option<String>,
1005) -> Option<ConsumedCapacity> {
1006 secondary_index_capacity(
1007 table_name,
1008 table_units,
1009 gsi_units,
1010 lsi_units,
1011 mode,
1012 CapacityAxis::None,
1013 )
1014}
1015
1016#[derive(Clone, Copy, PartialEq)]
1024enum CapacityAxis {
1025 None,
1026 Write,
1027}
1028
1029impl CapacityAxis {
1030 fn write_units(self, units: f64) -> Option<f64> {
1032 match self {
1033 Self::None => None,
1034 Self::Write => Some(units),
1035 }
1036 }
1037}
1038
1039fn secondary_index_capacity(
1042 table_name: &str,
1043 table_units: f64,
1044 gsi_units: &HashMap<String, f64>,
1045 lsi_units: &HashMap<String, f64>,
1046 mode: &Option<String>,
1047 axis: CapacityAxis,
1048) -> Option<ConsumedCapacity> {
1049 let total = table_units + gsi_units.values().sum::<f64>() + lsi_units.values().sum::<f64>();
1050
1051 match mode.as_deref().unwrap_or("NONE") {
1052 "INDEXES" => Some(ConsumedCapacity {
1053 table_name: table_name.to_string(),
1054 capacity_units: total,
1055 write_capacity_units: axis.write_units(total),
1056 table: Some(CapacityDetail {
1057 capacity_units: table_units,
1058 write_capacity_units: axis.write_units(table_units),
1059 ..Default::default()
1060 }),
1061 global_secondary_indexes: capacity_detail_map(gsi_units, axis),
1062 local_secondary_indexes: capacity_detail_map(lsi_units, axis),
1063 ..Default::default()
1064 }),
1065 "TOTAL" => Some(ConsumedCapacity {
1066 table_name: table_name.to_string(),
1067 capacity_units: total,
1068 write_capacity_units: axis.write_units(total),
1069 ..Default::default()
1070 }),
1071 _ => None,
1072 }
1073}
1074
1075pub fn consumed_capacity_with_vector_indexes(
1083 table_name: &str,
1084 table_units: f64,
1085 gsi_units: &HashMap<String, f64>,
1086 lsi_units: &HashMap<String, f64>,
1087 vector_bytes: &HashMap<String, f64>,
1088 mode: &Option<String>,
1089) -> Option<ConsumedCapacity> {
1090 attach_vector_index_capacity(
1091 consumed_capacity_with_secondary_indexes(
1092 table_name,
1093 table_units,
1094 gsi_units,
1095 lsi_units,
1096 mode,
1097 ),
1098 vector_bytes,
1099 mode,
1100 )
1101}
1102
1103pub(crate) fn attach_vector_index_capacity(
1112 capacity: Option<ConsumedCapacity>,
1113 vector_bytes: &HashMap<String, f64>,
1114 mode: &Option<String>,
1115) -> Option<ConsumedCapacity> {
1116 let mut capacity = capacity?;
1117 if vector_bytes.is_empty() || !vector_capacity_wanted(mode.as_deref()) {
1118 return Some(capacity);
1119 }
1120 capacity.vector_indexes = Some(
1121 vector_bytes
1122 .iter()
1123 .map(|(name, bytes)| {
1124 (
1125 name.clone(),
1126 VectorCapacityDetail {
1127 vector_write_request_bytes: *bytes,
1128 },
1129 )
1130 })
1131 .collect(),
1132 );
1133 Some(capacity)
1134}
1135
1136pub fn transactional_read_capacity(
1141 table_name: &str,
1142 units: f64,
1143 mode: &Option<String>,
1144) -> Option<ConsumedCapacity> {
1145 match mode.as_deref().unwrap_or("NONE") {
1146 "TOTAL" => Some(ConsumedCapacity {
1147 table_name: table_name.to_string(),
1148 capacity_units: units,
1149 read_capacity_units: Some(units),
1150 ..Default::default()
1151 }),
1152 "INDEXES" => Some(ConsumedCapacity {
1153 table_name: table_name.to_string(),
1154 capacity_units: units,
1155 read_capacity_units: Some(units),
1156 table: Some(CapacityDetail {
1157 capacity_units: units,
1158 read_capacity_units: Some(units),
1159 ..Default::default()
1160 }),
1161 ..Default::default()
1162 }),
1163 _ => None,
1164 }
1165}
1166
1167pub fn build_transactional_capacity(
1174 table_units: &HashMap<String, f64>,
1175 mode: &Option<String>,
1176 builder: fn(&str, f64, &Option<String>) -> Option<ConsumedCapacity>,
1177) -> Option<Vec<ConsumedCapacity>> {
1178 if !capacity_wanted(mode.as_deref()) {
1179 return None;
1180 }
1181
1182 let mut tables: Vec<&String> = table_units.keys().collect();
1187 tables.sort();
1188
1189 Some(
1190 tables
1191 .into_iter()
1192 .filter_map(|table| builder(table, *table_units.get(table)?, mode))
1193 .collect(),
1194 )
1195}
1196
1197pub fn transactional_write_capacity_with_indexes(
1209 table_name: &str,
1210 table_units: f64,
1211 gsi_units: &HashMap<String, f64>,
1212 lsi_units: &HashMap<String, f64>,
1213 mode: &Option<String>,
1214) -> Option<ConsumedCapacity> {
1215 secondary_index_capacity(
1216 table_name,
1217 table_units,
1218 gsi_units,
1219 lsi_units,
1220 mode,
1221 CapacityAxis::Write,
1222 )
1223}
1224
1225fn capacity_detail_map(
1228 units: &HashMap<String, f64>,
1229 axis: CapacityAxis,
1230) -> Option<HashMap<String, CapacityDetail>> {
1231 if units.is_empty() {
1232 return None;
1233 }
1234 Some(
1235 units
1236 .iter()
1237 .map(|(name, &u)| {
1238 (
1239 name.clone(),
1240 CapacityDetail {
1241 capacity_units: u,
1242 write_capacity_units: axis.write_units(u),
1243 ..Default::default()
1244 },
1245 )
1246 })
1247 .collect(),
1248 )
1249}
1250
1251pub fn transactional_write_capacity(
1256 table_name: &str,
1257 units: f64,
1258 mode: &Option<String>,
1259) -> Option<ConsumedCapacity> {
1260 match mode.as_deref().unwrap_or("NONE") {
1261 "TOTAL" => Some(ConsumedCapacity {
1262 table_name: table_name.to_string(),
1263 capacity_units: units,
1264 write_capacity_units: Some(units),
1265 ..Default::default()
1266 }),
1267 "INDEXES" => Some(ConsumedCapacity {
1268 table_name: table_name.to_string(),
1269 capacity_units: units,
1270 write_capacity_units: Some(units),
1271 table: Some(CapacityDetail {
1272 capacity_units: units,
1273 write_capacity_units: Some(units),
1274 ..Default::default()
1275 }),
1276 ..Default::default()
1277 }),
1278 _ => None,
1279 }
1280}
1281
1282#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1284pub struct KeySchemaElement {
1285 #[serde(rename = "AttributeName", alias = "attribute_name")]
1286 pub attribute_name: String,
1287 #[serde(rename = "KeyType", alias = "key_type")]
1288 pub key_type: KeyType,
1289}
1290
1291#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1293pub enum KeyType {
1294 #[default]
1295 HASH,
1296 RANGE,
1297}
1298
1299#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1301pub struct AttributeDefinition {
1302 #[serde(rename = "AttributeName", alias = "attribute_name")]
1303 pub attribute_name: String,
1304 #[serde(rename = "AttributeType", alias = "attribute_type")]
1305 pub attribute_type: ScalarAttributeType,
1306}
1307
1308#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1310pub enum ScalarAttributeType {
1311 #[default]
1312 S,
1313 N,
1314 B,
1315}
1316
1317#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1319pub struct Projection {
1320 #[serde(
1321 rename = "ProjectionType",
1322 alias = "projection_type",
1323 default,
1324 skip_serializing_if = "Option::is_none"
1325 )]
1326 pub projection_type: Option<ProjectionType>,
1327 #[serde(
1328 rename = "NonKeyAttributes",
1329 alias = "non_key_attributes",
1330 skip_serializing_if = "Option::is_none"
1331 )]
1332 pub non_key_attributes: Option<Vec<String>>,
1333}
1334
1335#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1337#[allow(non_camel_case_types)]
1338pub enum ProjectionType {
1339 #[default]
1340 ALL,
1341 KEYS_ONLY,
1342 INCLUDE,
1343}
1344
1345#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1347pub struct GlobalSecondaryIndex {
1348 #[serde(rename = "IndexName", alias = "index_name")]
1349 pub index_name: String,
1350 #[serde(rename = "KeySchema", alias = "key_schema")]
1351 pub key_schema: Vec<KeySchemaElement>,
1352 #[serde(rename = "Projection", alias = "projection")]
1353 pub projection: Projection,
1354 #[serde(
1355 rename = "ProvisionedThroughput",
1356 alias = "provisioned_throughput",
1357 skip_serializing_if = "Option::is_none"
1358 )]
1359 pub provisioned_throughput: Option<ProvisionedThroughput>,
1360}
1361
1362#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1364pub struct LocalSecondaryIndex {
1365 #[serde(rename = "IndexName", alias = "index_name")]
1366 pub index_name: String,
1367 #[serde(rename = "KeySchema", alias = "key_schema")]
1368 pub key_schema: Vec<KeySchemaElement>,
1369 #[serde(rename = "Projection", alias = "projection")]
1370 pub projection: Projection,
1371}
1372
1373#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1376pub struct VectorAttributeDefinition {
1377 #[serde(rename = "AttributeName", alias = "attribute_name")]
1378 pub attribute_name: String,
1379}
1380
1381#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1384pub struct SearchSchemaElement {
1385 #[serde(rename = "AttributeName", alias = "attribute_name")]
1386 pub attribute_name: String,
1387 #[serde(
1388 rename = "SearchSchemaElementType",
1389 alias = "search_schema_element_type"
1390 )]
1391 pub search_schema_element_type: String,
1392}
1393
1394#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1399pub struct VectorIndex {
1400 #[serde(rename = "IndexName", alias = "index_name")]
1401 pub index_name: String,
1402 #[serde(rename = "VectorAttribute", alias = "vector_attribute")]
1403 pub vector_attribute: VectorAttributeDefinition,
1404 #[serde(
1405 rename = "SearchSchema",
1406 alias = "search_schema",
1407 default,
1408 skip_serializing_if = "Option::is_none"
1409 )]
1410 pub search_schema: Option<Vec<SearchSchemaElement>>,
1411 #[serde(rename = "Projection", alias = "projection")]
1412 pub projection: Projection,
1413 #[serde(rename = "Dimensions", alias = "dimensions")]
1414 pub dimensions: u32,
1415 #[serde(rename = "DistanceFunction", alias = "distance_function")]
1416 pub distance_function: String,
1417}
1418
1419#[derive(Debug, Clone, PartialEq)]
1424pub enum VectorValueError {
1425 NotAList,
1427 WrongDimensions {
1429 actual: usize,
1431 },
1432 ElementNotANumber {
1434 position: usize,
1436 actual: &'static str,
1438 },
1439 ElementOutOfRange {
1443 position: usize,
1445 value: String,
1447 },
1448}
1449
1450pub fn check_vector_f32_values(
1460 value: &AttributeValue,
1461 dimensions: u32,
1462) -> std::result::Result<Vec<f32>, VectorValueError> {
1463 let AttributeValue::L(elems) = value else {
1464 return Err(VectorValueError::NotAList);
1465 };
1466 if elems.len() != dimensions as usize {
1467 return Err(VectorValueError::WrongDimensions {
1468 actual: elems.len(),
1469 });
1470 }
1471 let mut out = Vec::with_capacity(elems.len());
1472 for (position, elem) in elems.iter().enumerate() {
1473 let AttributeValue::N(n) = elem else {
1474 return Err(VectorValueError::ElementNotANumber {
1475 position,
1476 actual: elem.type_name(),
1477 });
1478 };
1479 match n.parse::<f32>() {
1482 Ok(v) if v.is_finite() => out.push(v),
1483 _ => {
1484 return Err(VectorValueError::ElementOutOfRange {
1485 position,
1486 value: n.clone(),
1487 });
1488 }
1489 }
1490 }
1491 Ok(out)
1492}
1493
1494pub fn vector_f32_values(value: &AttributeValue, dimensions: u32) -> Option<Vec<f32>> {
1499 check_vector_f32_values(value, dimensions).ok()
1500}
1501
1502pub fn f32_number_string(v: f32) -> String {
1507 serde_json::to_string(&v).unwrap_or_else(|_| "0.0".to_string())
1508}
1509
1510#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1512pub struct ProvisionedThroughput {
1513 #[serde(rename = "ReadCapacityUnits", alias = "read_capacity_units", default)]
1514 pub read_capacity_units: Option<i64>,
1515 #[serde(rename = "WriteCapacityUnits", alias = "write_capacity_units", default)]
1516 pub write_capacity_units: Option<i64>,
1517}
1518
1519#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1521pub struct OnDemandThroughput {
1522 #[serde(
1523 rename = "MaxReadRequestUnits",
1524 alias = "max_read_request_units",
1525 default,
1526 skip_serializing_if = "Option::is_none"
1527 )]
1528 pub max_read_request_units: Option<i64>,
1529 #[serde(
1530 rename = "MaxWriteRequestUnits",
1531 alias = "max_write_request_units",
1532 default,
1533 skip_serializing_if = "Option::is_none"
1534 )]
1535 pub max_write_request_units: Option<i64>,
1536}
1537
1538#[derive(Debug, Clone, PartialEq)]
1544pub struct ConversionError {
1545 pub expected: &'static str,
1547 pub actual: &'static str,
1549}
1550
1551impl fmt::Display for ConversionError {
1552 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1553 write!(f, "expected {}, got {}", self.expected, self.actual)
1554 }
1555}
1556
1557impl std::error::Error for ConversionError {}
1558
1559impl From<String> for AttributeValue {
1562 fn from(value: String) -> Self {
1563 AttributeValue::S(value)
1564 }
1565}
1566
1567impl From<&str> for AttributeValue {
1568 fn from(value: &str) -> Self {
1569 AttributeValue::S(value.to_string())
1570 }
1571}
1572
1573impl From<bool> for AttributeValue {
1574 fn from(value: bool) -> Self {
1575 AttributeValue::BOOL(value)
1576 }
1577}
1578
1579impl From<Vec<u8>> for AttributeValue {
1580 fn from(value: Vec<u8>) -> Self {
1581 AttributeValue::B(value)
1582 }
1583}
1584
1585impl From<&[u8]> for AttributeValue {
1586 fn from(value: &[u8]) -> Self {
1587 AttributeValue::B(value.to_vec())
1588 }
1589}
1590
1591macro_rules! impl_from_integer {
1593 ($($t:ty),+) => {
1594 $(
1595 impl From<$t> for AttributeValue {
1596 fn from(value: $t) -> Self {
1597 AttributeValue::N(value.to_string())
1598 }
1599 }
1600 )+
1601 };
1602}
1603
1604impl_from_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1605
1606impl From<HashMap<String, AttributeValue>> for AttributeValue {
1608 fn from(value: HashMap<String, AttributeValue>) -> Self {
1609 AttributeValue::M(value)
1610 }
1611}
1612
1613impl From<Vec<AttributeValue>> for AttributeValue {
1614 fn from(value: Vec<AttributeValue>) -> Self {
1615 AttributeValue::L(value)
1616 }
1617}
1618
1619impl From<HashSet<String>> for AttributeValue {
1620 fn from(value: HashSet<String>) -> Self {
1621 AttributeValue::SS(value.into_iter().collect())
1622 }
1623}
1624
1625impl From<BTreeSet<String>> for AttributeValue {
1626 fn from(value: BTreeSet<String>) -> Self {
1627 AttributeValue::SS(value.into_iter().collect())
1628 }
1629}
1630
1631impl TryFrom<f64> for AttributeValue {
1634 type Error = ConversionError;
1635
1636 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
1637 if value.is_finite() {
1638 Ok(AttributeValue::N(value.to_string()))
1639 } else {
1640 Err(ConversionError {
1641 expected: "finite f64",
1642 actual: "NaN or Infinity",
1643 })
1644 }
1645 }
1646}
1647
1648impl TryFrom<f32> for AttributeValue {
1649 type Error = ConversionError;
1650
1651 fn try_from(value: f32) -> std::result::Result<Self, Self::Error> {
1652 if value.is_finite() {
1653 Ok(AttributeValue::N(value.to_string()))
1654 } else {
1655 Err(ConversionError {
1656 expected: "finite f32",
1657 actual: "NaN or Infinity",
1658 })
1659 }
1660 }
1661}
1662
1663impl TryFrom<AttributeValue> for String {
1666 type Error = ConversionError;
1667
1668 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1669 match value {
1670 AttributeValue::S(s) => Ok(s),
1671 other => Err(ConversionError {
1672 expected: "S",
1673 actual: other.type_name(),
1674 }),
1675 }
1676 }
1677}
1678
1679impl TryFrom<AttributeValue> for bool {
1680 type Error = ConversionError;
1681
1682 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1683 match value {
1684 AttributeValue::BOOL(b) => Ok(b),
1685 other => Err(ConversionError {
1686 expected: "BOOL",
1687 actual: other.type_name(),
1688 }),
1689 }
1690 }
1691}
1692
1693impl TryFrom<AttributeValue> for Vec<u8> {
1694 type Error = ConversionError;
1695
1696 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1697 match value {
1698 AttributeValue::B(b) => Ok(b),
1699 other => Err(ConversionError {
1700 expected: "B",
1701 actual: other.type_name(),
1702 }),
1703 }
1704 }
1705}
1706
1707macro_rules! impl_try_from_av_integer {
1708 ($($t:ty),+) => {
1709 $(
1710 impl TryFrom<AttributeValue> for $t {
1711 type Error = ConversionError;
1712
1713 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1714 match value {
1715 AttributeValue::N(n) => n.parse::<$t>().map_err(|_| ConversionError {
1716 expected: stringify!($t),
1717 actual: "N (parse failed)",
1718 }),
1719 other => Err(ConversionError {
1720 expected: "N",
1721 actual: other.type_name(),
1722 }),
1723 }
1724 }
1725 }
1726 )+
1727 };
1728}
1729
1730impl_try_from_av_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
1731
1732impl TryFrom<AttributeValue> for f64 {
1733 type Error = ConversionError;
1734
1735 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1736 match value {
1737 AttributeValue::N(n) => n.parse::<f64>().map_err(|_| ConversionError {
1738 expected: "f64",
1739 actual: "N (parse failed)",
1740 }),
1741 other => Err(ConversionError {
1742 expected: "N",
1743 actual: other.type_name(),
1744 }),
1745 }
1746 }
1747}
1748
1749impl TryFrom<AttributeValue> for f32 {
1750 type Error = ConversionError;
1751
1752 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1753 match value {
1754 AttributeValue::N(n) => n.parse::<f32>().map_err(|_| ConversionError {
1755 expected: "f32",
1756 actual: "N (parse failed)",
1757 }),
1758 other => Err(ConversionError {
1759 expected: "N",
1760 actual: other.type_name(),
1761 }),
1762 }
1763 }
1764}
1765
1766impl TryFrom<AttributeValue> for HashMap<String, AttributeValue> {
1767 type Error = ConversionError;
1768
1769 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1770 match value {
1771 AttributeValue::M(m) => Ok(m),
1772 other => Err(ConversionError {
1773 expected: "M",
1774 actual: other.type_name(),
1775 }),
1776 }
1777 }
1778}
1779
1780impl TryFrom<AttributeValue> for Vec<AttributeValue> {
1781 type Error = ConversionError;
1782
1783 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1784 match value {
1785 AttributeValue::L(l) => Ok(l),
1786 other => Err(ConversionError {
1787 expected: "L",
1788 actual: other.type_name(),
1789 }),
1790 }
1791 }
1792}
1793
1794impl TryFrom<AttributeValue> for Vec<String> {
1795 type Error = ConversionError;
1796
1797 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
1798 match value {
1799 AttributeValue::SS(ss) => Ok(ss),
1800 AttributeValue::L(l) => {
1801 l.into_iter()
1803 .map(|av| match av {
1804 AttributeValue::S(s) => Ok(s),
1805 other => Err(ConversionError {
1806 expected: "S (within L)",
1807 actual: other.type_name(),
1808 }),
1809 })
1810 .collect()
1811 }
1812 other => Err(ConversionError {
1813 expected: "SS or L",
1814 actual: other.type_name(),
1815 }),
1816 }
1817 }
1818}
1819
1820#[cfg(test)]
1821mod tests {
1822 use super::*;
1823
1824 #[test]
1825 fn test_serialize_string() {
1826 let val = AttributeValue::S("hello".to_string());
1827 let json = serde_json::to_string(&val).unwrap();
1828 assert_eq!(json, r#"{"S":"hello"}"#);
1829 }
1830
1831 #[test]
1832 fn test_serialize_number() {
1833 let val = AttributeValue::N("42".to_string());
1834 let json = serde_json::to_string(&val).unwrap();
1835 assert_eq!(json, r#"{"N":"42"}"#);
1836 }
1837
1838 #[test]
1839 fn test_serialize_binary() {
1840 let val = AttributeValue::B(vec![1, 2, 3]);
1841 let json = serde_json::to_string(&val).unwrap();
1842 assert_eq!(json, r#"{"B":"AQID"}"#);
1843 }
1844
1845 #[test]
1846 fn test_serialize_bool() {
1847 let val = AttributeValue::BOOL(true);
1848 let json = serde_json::to_string(&val).unwrap();
1849 assert_eq!(json, r#"{"BOOL":true}"#);
1850 }
1851
1852 #[test]
1853 fn test_serialize_null() {
1854 let val = AttributeValue::NULL(true);
1855 let json = serde_json::to_string(&val).unwrap();
1856 assert_eq!(json, r#"{"NULL":true}"#);
1857 }
1858
1859 #[test]
1860 fn test_deserialize_null_true() {
1861 let val: AttributeValue = serde_json::from_str(r#"{"NULL":true}"#).unwrap();
1862 assert_eq!(val, AttributeValue::NULL(true));
1863 }
1864
1865 #[test]
1866 fn test_deserialize_null_false_rejected() {
1867 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":false}"#).unwrap_err();
1870 assert!(
1871 err.to_string().contains("must have the value of true"),
1872 "unexpected error: {err}"
1873 );
1874 }
1875
1876 #[test]
1877 fn test_deserialize_null_non_boolean_rejected() {
1878 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":"no"}"#).unwrap_err();
1880 assert!(
1881 err.to_string().contains("must have the value of true"),
1882 "unexpected error: {err}"
1883 );
1884 }
1885
1886 #[test]
1887 fn test_serialize_string_set() {
1888 let val = AttributeValue::SS(vec!["a".to_string(), "b".to_string()]);
1889 let json = serde_json::to_string(&val).unwrap();
1890 assert_eq!(json, r#"{"SS":["a","b"]}"#);
1891 }
1892
1893 #[test]
1894 fn test_serialize_list() {
1895 let val = AttributeValue::L(vec![
1896 AttributeValue::S("hello".to_string()),
1897 AttributeValue::N("42".to_string()),
1898 ]);
1899 let json = serde_json::to_string(&val).unwrap();
1900 assert_eq!(json, r#"{"L":[{"S":"hello"},{"N":"42"}]}"#);
1901 }
1902
1903 #[test]
1904 fn test_serialize_map() {
1905 let mut m = HashMap::new();
1906 m.insert("key".to_string(), AttributeValue::S("value".to_string()));
1907 let val = AttributeValue::M(m);
1908 let json = serde_json::to_string(&val).unwrap();
1909 assert_eq!(json, r#"{"M":{"key":{"S":"value"}}}"#);
1910 }
1911
1912 #[test]
1913 fn test_round_trip_all_types() {
1914 let values = vec![
1915 AttributeValue::S("hello".to_string()),
1916 AttributeValue::N("42.5".to_string()),
1917 AttributeValue::B(vec![0, 255, 128]),
1918 AttributeValue::BOOL(false),
1919 AttributeValue::NULL(true),
1920 AttributeValue::SS(vec!["x".to_string(), "y".to_string()]),
1921 AttributeValue::NS(vec!["1".to_string(), "2.5".to_string()]),
1922 AttributeValue::BS(vec![vec![1], vec![2, 3]]),
1923 AttributeValue::L(vec![
1924 AttributeValue::S("nested".to_string()),
1925 AttributeValue::N("99".to_string()),
1926 ]),
1927 ];
1928
1929 for val in values {
1930 let json = serde_json::to_string(&val).unwrap();
1931 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
1932 assert_eq!(val, deserialized, "Round-trip failed for {json}");
1933 }
1934 }
1935
1936 #[test]
1937 fn test_size_string() {
1938 let val = AttributeValue::S("hello".to_string());
1939 assert_eq!(val.size(), 5);
1940 }
1941
1942 #[test]
1943 fn test_size_number() {
1944 let val = AttributeValue::N("42".to_string());
1946 assert_eq!(val.size(), 2);
1947 }
1948
1949 #[test]
1950 fn test_size_number_matches_dynamodb() {
1951 let cases: &[(&str, usize)] = &[
1959 ("1", 2),
1961 ("12", 2),
1962 ("123", 3),
1963 ("1234", 3),
1964 ("12345", 4),
1965 ("123456", 4),
1966 ("1234567", 5),
1967 ("12345678", 5),
1968 ("123456789", 6),
1969 ("1234567891", 6),
1970 ("12345678912", 7),
1971 ("123456789123", 7),
1972 ("1234567890123456789", 11),
1973 ("12345678901234567890123456789012345678", 20),
1974 ("0042", 2),
1976 ("100", 2),
1977 ("1010", 3),
1978 ("0.0000001", 2),
1979 ("0", 1),
1980 ("1E125", 2),
1982 ("1E-100", 2),
1983 ("1.5", 3),
1986 ("1.2", 3),
1987 ("1.200", 3),
1988 ("1.234", 4),
1989 ("3.14159", 5),
1990 ("100.5", 4),
1991 ("0.15", 2),
1992 ("15", 2),
1993 ("-42", 3),
1995 ("-12345", 5),
1996 ("-1E125", 3),
1997 ("-0.15", 3),
1998 ("-0", 1),
1999 ];
2000 for (literal, expected) in cases {
2001 assert_eq!(
2002 AttributeValue::N((*literal).to_string()).size(),
2003 *expected,
2004 "size of N {literal:?}"
2005 );
2006 }
2007 }
2008
2009 #[test]
2010 fn test_size_number_survives_normalisation() {
2011 for literal in [
2015 "1E125",
2016 "1E-100",
2017 "0042",
2018 "1.200",
2019 "0.0000001",
2020 "100",
2021 "3.14159",
2022 "42",
2023 ] {
2024 let raw = AttributeValue::N(literal.to_string()).size();
2025 let normalised = AttributeValue::N(normalize_dynamo_number(literal)).size();
2026 assert_eq!(
2027 raw, normalised,
2028 "N {literal:?} changed size when normalised"
2029 );
2030 }
2031 }
2032
2033 #[test]
2034 fn test_size_bool() {
2035 assert_eq!(AttributeValue::BOOL(true).size(), 1);
2036 }
2037
2038 #[test]
2039 fn test_size_null() {
2040 assert_eq!(AttributeValue::NULL(true).size(), 1);
2041 }
2042
2043 #[test]
2044 fn test_key_string_s() {
2045 let val = AttributeValue::S("hello".to_string());
2046 assert_eq!(val.to_key_string(), Some("S:hello".to_string()));
2047 }
2048
2049 #[test]
2050 fn test_key_string_n() {
2051 let val = AttributeValue::N("42".to_string());
2052 let key = val.to_key_string().unwrap();
2053 assert!(key.starts_with("N:"));
2054 }
2055
2056 #[test]
2057 fn test_key_string_b() {
2058 let val = AttributeValue::B(vec![0xff, 0x00, 0xab]);
2059 assert_eq!(val.to_key_string(), Some("B:ff00ab".to_string()));
2060 }
2061
2062 #[test]
2063 fn test_key_string_non_key_type_returns_none() {
2064 assert_eq!(AttributeValue::BOOL(true).to_key_string(), None);
2065 assert_eq!(AttributeValue::L(vec![]).to_key_string(), None);
2066 }
2067
2068 #[test]
2070 fn test_number_sort_ordering() {
2071 let numbers = vec![
2072 "-1000", "-100", "-10", "-1", "-0.5", "-0.001", "0", "0.001", "0.5", "1", "10", "100",
2073 "1000",
2074 ];
2075 let encoded: Vec<String> = numbers
2076 .iter()
2077 .map(|n| normalize_number_for_sort(n))
2078 .collect();
2079
2080 for i in 0..encoded.len() - 1 {
2081 assert!(
2082 encoded[i] < encoded[i + 1],
2083 "Sort order broken: {} ({}) should be < {} ({})",
2084 numbers[i],
2085 encoded[i],
2086 numbers[i + 1],
2087 encoded[i + 1]
2088 );
2089 }
2090 }
2091
2092 #[test]
2093 fn test_number_sort_zero_variants() {
2094 let z1 = normalize_number_for_sort("0");
2095 let z2 = normalize_number_for_sort("-0");
2096 let z3 = normalize_number_for_sort("0.0");
2097 assert_eq!(z1, z2);
2098 assert_eq!(z2, z3);
2099 }
2100
2101 #[test]
2102 fn test_number_sort_decimals() {
2103 let a = normalize_number_for_sort("1.5");
2104 let b = normalize_number_for_sort("2.5");
2105 assert!(a < b);
2106
2107 let c = normalize_number_for_sort("0.001");
2108 let d = normalize_number_for_sort("0.01");
2109 assert!(c < d);
2110 }
2111
2112 #[test]
2113 fn test_number_sort_scientific() {
2114 let a = normalize_number_for_sort("1e10");
2115 let b = normalize_number_for_sort("1e11");
2116 assert!(a < b);
2117
2118 let c = normalize_number_for_sort("-1e11");
2119 let d = normalize_number_for_sort("-1e10");
2120 assert!(c < d);
2121 }
2122
2123 #[test]
2128 fn test_validate_number_accepts_dynamodb_grammar() {
2129 for input in [
2130 "+5", "+1.5", "+0", "-0", "+0.0", "+1e2", "1e+2", "1.5E+3", "-7", "+.5", ".5", "5.",
2131 "00042", "1.23E10", "+1e-2", "1E-130", "+1E-130",
2132 ] {
2133 assert!(
2134 validate_dynamo_number(input).is_ok(),
2135 "expected {input} to validate, got {:?}",
2136 validate_dynamo_number(input)
2137 );
2138 }
2139 }
2140
2141 #[test]
2142 fn test_validate_number_rejects_malformed() {
2143 for input in [
2146 "+e2", "e2", "+1+2", "1+2", "+1.2.3", "1.2.3", "++5", "+-5", "-+5", "+", "-", "1e",
2147 "1e+", ".", "1.2e3.4", "0x5", "NaN", "Infinity", "1_000", " 5", "5 ", "1 5", "",
2148 ] {
2149 assert!(
2150 matches!(
2151 validate_dynamo_number(input),
2152 Err(crate::errors::DynoxideError::ValidationException(_))
2153 ),
2154 "expected {input:?} to be rejected with ValidationException, got {:?}",
2155 validate_dynamo_number(input)
2156 );
2157 }
2158 }
2159
2160 #[test]
2161 fn test_normalize_number_matches_dynamodb() {
2162 for (input, stored) in [
2163 ("+5", "5"),
2164 ("+1.5", "1.5"),
2165 ("+0", "0"),
2166 ("-0", "0"),
2167 ("+0.0", "0"),
2168 ("+1e2", "100"),
2169 ("1e+2", "100"),
2170 ("1.5E+3", "1500"),
2171 ("-7", "-7"),
2172 ("+.5", "0.5"),
2173 (".5", "0.5"),
2174 ("5.", "5"),
2175 ("00042", "42"),
2176 ("1.23E10", "12300000000"),
2177 ("+1e-2", "0.01"),
2178 ] {
2179 assert_eq!(
2180 normalize_dynamo_number(input),
2181 stored,
2182 "{input} should normalise to {stored}"
2183 );
2184 }
2185 }
2186
2187 #[test]
2188 fn test_type_name() {
2189 assert_eq!(AttributeValue::S("".to_string()).type_name(), "S");
2190 assert_eq!(AttributeValue::N("0".to_string()).type_name(), "N");
2191 assert_eq!(AttributeValue::B(vec![]).type_name(), "B");
2192 assert_eq!(AttributeValue::BOOL(true).type_name(), "BOOL");
2193 assert_eq!(AttributeValue::NULL(true).type_name(), "NULL");
2194 assert_eq!(AttributeValue::SS(vec![]).type_name(), "SS");
2195 assert_eq!(AttributeValue::NS(vec![]).type_name(), "NS");
2196 assert_eq!(AttributeValue::BS(vec![]).type_name(), "BS");
2197 assert_eq!(AttributeValue::L(vec![]).type_name(), "L");
2198 assert_eq!(AttributeValue::M(HashMap::new()).type_name(), "M");
2199 }
2200}