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(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 fmt::Debug for AttributeValue {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 AttributeValue::S(v) => f.debug_tuple("S").field(v).finish(),
47 AttributeValue::N(v) => f.debug_tuple("N").field(v).finish(),
48 AttributeValue::B(v) => f.debug_tuple("B").field(v).finish(),
49 AttributeValue::BOOL(v) => f.debug_tuple("BOOL").field(v).finish(),
50 AttributeValue::NULL(v) => f.debug_tuple("NULL").field(v).finish(),
51 AttributeValue::SS(v) => f.debug_tuple("SS").field(v).finish(),
52 AttributeValue::NS(v) => f.debug_tuple("NS").field(v).finish(),
53 AttributeValue::BS(v) => f.debug_tuple("BS").field(v).finish(),
54 AttributeValue::L(v) => f.debug_tuple("L").field(v).finish(),
55 AttributeValue::M(map) => {
56 struct Sorted<'a>(&'a HashMap<String, AttributeValue>);
57 impl fmt::Debug for Sorted<'_> {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 let mut entries: Vec<_> = self.0.iter().collect();
60 entries.sort_unstable_by(|a, b| a.0.cmp(b.0));
61 f.debug_map().entries(entries).finish()
62 }
63 }
64 f.debug_tuple("M").field(&Sorted(map)).finish()
65 }
66 }
67 }
68}
69
70impl AttributeValue {
71 pub fn size(&self) -> usize {
77 match self {
78 AttributeValue::S(s) => s.len(),
79 AttributeValue::N(n) => number_size(n),
80 AttributeValue::B(b) => b.len(),
81 AttributeValue::BOOL(_) => 1,
82 AttributeValue::NULL(_) => 1,
83 AttributeValue::SS(ss) => ss.iter().map(|s| s.len()).sum(),
84 AttributeValue::NS(ns) => ns.iter().map(|n| number_size(n)).sum(),
85 AttributeValue::BS(bs) => bs.iter().map(|b| b.len()).sum(),
86 AttributeValue::L(items) => {
87 3 + items.len() + items.iter().map(|v| v.size()).sum::<usize>()
89 }
90 AttributeValue::M(map) => {
91 3 + map
93 .iter()
94 .map(|(k, v)| k.len() + 1 + v.size())
95 .sum::<usize>()
96 }
97 }
98 }
99
100 pub fn type_name(&self) -> &'static str {
102 match self {
103 AttributeValue::S(_) => "S",
104 AttributeValue::N(_) => "N",
105 AttributeValue::B(_) => "B",
106 AttributeValue::BOOL(_) => "BOOL",
107 AttributeValue::NULL(_) => "NULL",
108 AttributeValue::SS(_) => "SS",
109 AttributeValue::NS(_) => "NS",
110 AttributeValue::BS(_) => "BS",
111 AttributeValue::L(_) => "L",
112 AttributeValue::M(_) => "M",
113 }
114 }
115
116 pub fn is_scalar(&self) -> bool {
118 matches!(
119 self,
120 AttributeValue::S(_)
121 | AttributeValue::N(_)
122 | AttributeValue::B(_)
123 | AttributeValue::BOOL(_)
124 | AttributeValue::NULL(_)
125 )
126 }
127
128 pub fn is_set(&self) -> bool {
130 matches!(
131 self,
132 AttributeValue::SS(_) | AttributeValue::NS(_) | AttributeValue::BS(_)
133 )
134 }
135
136 pub fn to_key_string(&self) -> Option<String> {
143 match self {
144 AttributeValue::S(s) => Some(format!("S:{s}")),
145 AttributeValue::N(n) => Some(format!("N:{}", normalize_number_for_sort(n))),
146 AttributeValue::B(b) => Some(format!("B:{}", hex_encode(b))),
147 _ => None, }
149 }
150}
151
152impl fmt::Display for AttributeValue {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 AttributeValue::S(s) => write!(f, "\"{s}\""),
156 AttributeValue::N(n) => write!(f, "{n}"),
157 AttributeValue::B(b) => write!(f, "<binary {} bytes>", b.len()),
158 AttributeValue::BOOL(b) => write!(f, "{b}"),
159 AttributeValue::NULL(_) => write!(f, "null"),
160 AttributeValue::SS(ss) => write!(f, "{ss:?}"),
161 AttributeValue::NS(ns) => write!(f, "{ns:?}"),
162 AttributeValue::BS(bs) => write!(f, "<binary set {} items>", bs.len()),
163 AttributeValue::L(items) => write!(f, "<list {} items>", items.len()),
164 AttributeValue::M(map) => write!(f, "<map {} keys>", map.len()),
165 }
166 }
167}
168
169impl Serialize for AttributeValue {
174 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175 where
176 S: Serializer,
177 {
178 let mut map = serializer.serialize_map(Some(1))?;
179 match self {
180 AttributeValue::S(s) => map.serialize_entry("S", s)?,
181 AttributeValue::N(n) => map.serialize_entry("N", n)?,
182 AttributeValue::B(b) => {
183 map.serialize_entry("B", &BASE64.encode(b))?;
184 }
185 AttributeValue::BOOL(b) => map.serialize_entry("BOOL", b)?,
186 AttributeValue::NULL(n) => map.serialize_entry("NULL", n)?,
187 AttributeValue::SS(ss) => {
191 map.serialize_entry("SS", &crate::set_order::sorted_strings(ss))?
192 }
193 AttributeValue::NS(ns) => {
194 map.serialize_entry("NS", &crate::set_order::sorted_numbers(ns))?
195 }
196 AttributeValue::BS(bs) => {
197 let encoded: Vec<String> = crate::set_order::sorted_binaries(bs)
198 .into_iter()
199 .map(|b| BASE64.encode(b))
200 .collect();
201 map.serialize_entry("BS", &encoded)?;
202 }
203 AttributeValue::L(items) => map.serialize_entry("L", items)?,
204 AttributeValue::M(m) => map.serialize_entry("M", m)?,
205 }
206 map.end()
207 }
208}
209
210impl<'de> Deserialize<'de> for AttributeValue {
211 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
212 where
213 D: Deserializer<'de>,
214 {
215 let raw = serde_json::Value::deserialize(deserializer)?;
217
218 let obj = raw
219 .as_object()
220 .ok_or_else(|| de::Error::custom("empty AttributeValue object"))?;
221
222 if obj.is_empty() {
223 return Err(de::Error::custom("empty AttributeValue object"));
224 }
225
226 let present: Vec<&str> = obj
230 .iter()
231 .filter(|(k, v)| TYPE_MEMBERS.contains(&k.as_str()) && !v.is_null())
232 .map(|(k, _)| k.as_str())
233 .collect();
234
235 if present.is_empty() {
236 return Err(de::Error::custom(
237 "Supplied AttributeValue is empty, must contain exactly one of the supported datatypes",
238 ));
239 }
240
241 let boolean = match obj.get("BOOL").filter(|v| !v.is_null()) {
245 Some(v) => Some(read_lenient_bool(v).map_err(de::Error::custom)?),
246 None => None,
247 };
248 let null = match obj.get("NULL").filter(|v| !v.is_null()) {
249 Some(v) => Some(read_lenient_bool(v).map_err(de::Error::custom)?),
250 None => None,
251 };
252
253 let layer = if present.len() > 1 {
260 crate::validation::ValueLayer::Older
261 } else {
262 current_value_layer()
263 };
264 if present.len() > 1
265 && let Some(refusal) = present
266 .iter()
267 .find_map(|&type_key| empty_set_refusal(type_key, &obj[type_key]))
268 {
269 return Err(de::Error::custom(format!(
270 "{}{refusal}",
271 crate::serde_errors::VALIDATION_MARKER
272 )));
273 }
274
275 for &type_key in &present {
278 match type_key {
279 "N" => {
280 if let Some(n) = obj.get("N").and_then(|v| v.as_str()) {
281 validate_number_in_deser(n, layer).map_err(de::Error::custom)?;
282 }
283 }
284 "NS" => {
285 if let Some(arr) = obj.get("NS").and_then(|v| v.as_array()) {
286 for item in arr {
287 if let Some(n) = item.as_str() {
288 validate_number_in_deser(n, layer).map_err(de::Error::custom)?;
289 }
290 }
291 }
292 }
293 _ => {}
294 }
295 }
296
297 if let Some(LenientBool {
305 value: false,
306 from_string,
307 }) = null
308 {
309 let marker = if from_string || layer == crate::validation::ValueLayer::Older {
310 crate::serde_errors::VALIDATION_MARKER
311 } else {
312 crate::serde_errors::REQUEST_VALIDATION_MARKER
313 };
314 return Err(de::Error::custom(format!(
315 "{marker}{}",
316 crate::serde_errors::NULL_NOT_TRUE
317 )));
318 }
319
320 if present.len() > 1 {
322 return Err(de::Error::custom(
323 "VALIDATION:Supplied AttributeValue has more than one datatypes set, \
324 must contain exactly one of the supported datatypes",
325 ));
326 }
327
328 let type_key = present[0];
329 let val = &obj[type_key];
330
331 match type_key {
332 "S" => {
333 let s = val
334 .as_str()
335 .ok_or_else(|| de::Error::custom("expected string for S"))?;
336 Ok(AttributeValue::S(s.to_string()))
337 }
338 "N" => {
339 let n = val
340 .as_str()
341 .ok_or_else(|| de::Error::custom("expected string for N"))?;
342 Ok(AttributeValue::N(n.to_string()))
343 }
344 "B" => {
345 let encoded = val
346 .as_str()
347 .ok_or_else(|| de::Error::custom("expected string for B"))?;
348 let bytes = BASE64
349 .decode(encoded)
350 .map_err(|e| de::Error::custom(format!("invalid base64: {e}")))?;
351 Ok(AttributeValue::B(bytes))
352 }
353 "BOOL" => Ok(AttributeValue::BOOL(boolean.is_some_and(|b| b.value))),
355 "NULL" => Ok(AttributeValue::NULL(true)),
356 "SS" => {
357 let arr = val
358 .as_array()
359 .ok_or_else(|| de::Error::custom("expected array for SS"))?;
360 let ss: Result<Vec<String>, _> = arr
361 .iter()
362 .map(|v| {
363 v.as_str()
364 .map(|s| s.to_string())
365 .ok_or_else(|| de::Error::custom("expected string in SS"))
366 })
367 .collect();
368 Ok(AttributeValue::SS(ss?))
369 }
370 "NS" => {
371 let arr = val
372 .as_array()
373 .ok_or_else(|| de::Error::custom("expected array for NS"))?;
374 let ns: Result<Vec<String>, _> = arr
375 .iter()
376 .map(|v| {
377 v.as_str()
378 .map(|s| s.to_string())
379 .ok_or_else(|| de::Error::custom("expected string in NS"))
380 })
381 .collect();
382 Ok(AttributeValue::NS(ns?))
383 }
384 "BS" => {
385 let arr = val
386 .as_array()
387 .ok_or_else(|| de::Error::custom("expected array for BS"))?;
388 let mut decoded = Vec::with_capacity(arr.len());
389 for item in arr {
390 let encoded = item
391 .as_str()
392 .ok_or_else(|| de::Error::custom("expected string in BS"))?;
393 decoded.push(
394 BASE64
395 .decode(encoded)
396 .map_err(|e| de::Error::custom(format!("invalid base64: {e}")))?,
397 );
398 }
399 Ok(AttributeValue::BS(decoded))
400 }
401 "L" => {
402 let arr = val
403 .as_array()
404 .ok_or_else(|| de::Error::custom("expected array for L"))?;
405 let list: Result<Vec<AttributeValue>, _> = arr
406 .iter()
407 .map(|v| serde_json::from_value(v.clone()).map_err(de::Error::custom))
408 .collect();
409 Ok(AttributeValue::L(list?))
410 }
411 "M" => {
412 let map_val = val
413 .as_object()
414 .ok_or_else(|| de::Error::custom("expected object for M"))?;
415 let mut result = std::collections::HashMap::new();
416 for (k, v) in map_val {
417 let av: AttributeValue =
418 serde_json::from_value(v.clone()).map_err(de::Error::custom)?;
419 result.insert(k.clone(), av);
420 }
421 Ok(AttributeValue::M(result))
422 }
423 _ => unreachable!(),
424 }
425 }
426}
427
428#[derive(Clone, Copy)]
431struct LenientBool {
432 value: bool,
433 from_string: bool,
434}
435
436fn read_lenient_bool(value: &serde_json::Value) -> Result<LenientBool, String> {
443 let refused = |message: &str| format!("{}{message}", crate::serde_errors::SERIALIZATION_MARKER);
444 match value {
445 serde_json::Value::Bool(value) => Ok(LenientBool {
446 value: *value,
447 from_string: false,
448 }),
449 serde_json::Value::String(text) => match lenient_bool_text(text) {
450 Some(value) => Ok(LenientBool {
451 value,
452 from_string: true,
453 }),
454 None => Err(refused("Unexpected token received from parser")),
455 },
456 serde_json::Value::Number(n) if n.is_f64() => {
463 Err(refused("DECIMAL_VALUE cannot be converted to Boolean"))
464 }
465 serde_json::Value::Number(_) => Err(refused("NUMBER_VALUE cannot be converted to Boolean")),
466 serde_json::Value::Array(_) => Err(refused("")),
467 serde_json::Value::Object(_) => Err(refused(
468 "Start of structure or map found where not expected",
469 )),
470 serde_json::Value::Null => Err(refused("Unexpected token received from parser")),
472 }
473}
474
475pub(crate) fn deserialize_lenient_flag<'de, D: serde::Deserializer<'de>>(
482 deserializer: D,
483) -> std::result::Result<Option<bool>, D::Error> {
484 match serde_json::Value::deserialize(deserializer)? {
485 serde_json::Value::Null => Ok(None),
486 serde_json::Value::Array(_) => Err(de::Error::custom(format!(
487 "{}Unrecognized collection type class java.lang.Boolean",
488 crate::serde_errors::SERIALIZATION_MARKER
489 ))),
490 value => read_lenient_bool(&value)
491 .map(|flag| Some(flag.value))
492 .map_err(de::Error::custom),
493 }
494}
495
496pub(crate) fn malformed_condition_boolean(
504 conditions: Option<&serde_json::Value>,
505) -> Option<String> {
506 let malformed = |message: String| {
507 message
508 .starts_with(crate::serde_errors::SERIALIZATION_MARKER)
509 .then_some(message)
510 };
511 for condition in conditions?.as_object()?.values() {
512 let Some(condition) = condition.as_object() else {
513 continue;
514 };
515 if let Some(exists) = condition.get("Exists")
516 && let Err(e) = deserialize_lenient_flag(exists.clone())
517 && let Some(message) = malformed(e.to_string())
518 {
519 return Some(message);
520 }
521 let values = condition.get("Value").into_iter().chain(
522 condition
523 .get("AttributeValueList")
524 .and_then(serde_json::Value::as_array)
525 .into_iter()
526 .flatten(),
527 );
528 for value in values {
529 if let Err(e) = serde_json::from_value::<AttributeValue>(value.clone())
530 && let Some(message) = malformed(e.to_string())
531 {
532 return Some(message);
533 }
534 }
535 }
536 None
537}
538
539#[cfg(any(feature = "http-server", feature = "wasm-sqlite", test))]
542pub(crate) fn reads_as_flag(text: &str) -> bool {
543 lenient_bool_text(text).is_some()
544}
545
546fn lenient_bool_text(text: &str) -> Option<bool> {
549 match text.to_ascii_lowercase().as_str() {
550 "true" | "t" | "yes" | "y" | "1" => Some(true),
551 "false" | "f" | "no" | "n" | "0" => Some(false),
552 _ => None,
553 }
554}
555
556fn validate_number_in_deser(n: &str, layer: crate::validation::ValueLayer) -> Result<(), String> {
563 let marker = match layer {
568 crate::validation::ValueLayer::Current => crate::serde_errors::REQUEST_VALIDATION_MARKER,
569 crate::validation::ValueLayer::Older => crate::serde_errors::VALIDATION_MARKER,
570 };
571 match layer.check_number(n) {
572 Ok(()) => Ok(()),
573 Err(crate::errors::DynoxideError::ValidationException(m)) => Err(format!("{marker}{m}")),
574 Err(e) => Err(format!("{marker}{e}")),
575 }
576}
577
578pub(crate) fn validate_current_number(
582 num_str: &str,
583) -> std::result::Result<(), crate::errors::DynoxideError> {
584 if num_str.is_empty() {
585 return Err(crate::errors::DynoxideError::ValidationException(
586 "The parameter cannot be converted to a numeric value: ".to_string(),
587 ));
588 }
589 validate_dynamo_number(num_str)
590}
591
592pub fn normalize_number_for_sort(num_str: &str) -> String {
606 let trimmed = num_str.trim();
607
608 if trimmed.is_empty() || trimmed == "0" || trimmed == "-0" || trimmed == "0.0" {
609 return zero_encoding();
610 }
611
612 let negative = trimmed.starts_with('-');
613 let abs_str = if negative { &trimmed[1..] } else { trimmed };
614
615 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
617
618 if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
619 return zero_encoding();
620 }
621
622 if negative {
623 encode_negative(&mantissa_digits, exponent)
624 } else {
625 encode_positive(&mantissa_digits, exponent)
626 }
627}
628
629fn empty_set_refusal(type_key: &str, member: &serde_json::Value) -> Option<&'static str> {
631 if !member.as_array().is_some_and(Vec::is_empty) {
632 return None;
633 }
634 match type_key {
635 "SS" => Some("One or more parameter values were invalid: An string set may not be empty"),
636 "NS" => Some("One or more parameter values were invalid: An number set may not be empty"),
637 "BS" => Some("One or more parameter values were invalid: Binary sets should not be empty"),
638 _ => None,
639 }
640}
641
642const TYPE_MEMBERS: [&str; 10] = ["S", "N", "B", "BOOL", "NULL", "SS", "NS", "BS", "L", "M"];
644
645pub(crate) fn reads_on_older_layer(value: &serde_json::Value) -> bool {
656 match value {
657 serde_json::Value::Object(map) => {
658 let string_flag = ["BOOL", "NULL", "Exists"]
659 .iter()
660 .any(|member| map.get(*member).is_some_and(serde_json::Value::is_string));
661 let members: Vec<&serde_json::Value> = TYPE_MEMBERS
662 .iter()
663 .filter_map(|member| map.get(*member))
664 .filter(|member| !member.is_null())
665 .collect();
666 string_flag
667 || (members.len() > 1 && members.iter().any(|member| !member.is_object()))
668 || map.values().any(reads_on_older_layer)
669 }
670 serde_json::Value::Array(items) => items.iter().any(reads_on_older_layer),
671 _ => false,
672 }
673}
674
675pub(crate) fn current_value_layer() -> crate::validation::ValueLayer {
678 VALUE_LAYER.with(std::cell::Cell::get)
679}
680
681thread_local! {
682 static VALUE_LAYER: std::cell::Cell<crate::validation::ValueLayer> =
686 const { std::cell::Cell::new(crate::validation::ValueLayer::Current) };
687}
688
689pub(crate) fn with_value_layer<T>(
693 layer: crate::validation::ValueLayer,
694 read: impl FnOnce() -> T,
695) -> T {
696 struct Restore(crate::validation::ValueLayer);
697 impl Drop for Restore {
698 fn drop(&mut self) {
699 VALUE_LAYER.with(|cell| cell.set(self.0));
700 }
701 }
702 let _restore = Restore(VALUE_LAYER.with(|cell| cell.replace(layer)));
703 read()
704}
705
706#[cfg(any(feature = "wasm-sqlite", test))]
711pub(crate) async fn with_value_layer_async<F: std::future::Future>(
712 layer: crate::validation::ValueLayer,
713 future: F,
714) -> F::Output {
715 struct Layered<F> {
716 layer: crate::validation::ValueLayer,
717 future: std::pin::Pin<Box<F>>,
718 }
719 impl<F: std::future::Future> std::future::Future for Layered<F> {
720 type Output = F::Output;
721 fn poll(
722 mut self: std::pin::Pin<&mut Self>,
723 cx: &mut std::task::Context<'_>,
724 ) -> std::task::Poll<F::Output> {
725 let layer = self.layer;
726 with_value_layer(layer, || self.future.as_mut().poll(cx))
727 }
728 }
729 Layered {
730 layer,
731 future: Box::pin(future),
732 }
733 .await
734}
735
736pub(crate) fn reading_parameters<T>(read: impl FnOnce() -> T) -> T {
739 with_value_layer(crate::validation::ValueLayer::Older, read)
740}
741
742pub(crate) fn deserialize_older_expression_values<'de, D: serde::Deserializer<'de>>(
748 deserializer: D,
749) -> std::result::Result<Option<HashMap<String, AttributeValue>>, D::Error> {
750 let Some(raw) =
751 Option::<serde_json::Map<String, serde_json::Value>>::deserialize(deserializer)?
752 else {
753 return Ok(None);
754 };
755 let mut values = HashMap::with_capacity(raw.len());
756 for (key, value) in raw {
757 let read = with_value_layer(crate::validation::ValueLayer::Older, || {
758 serde_json::from_value::<AttributeValue>(value)
759 });
760 match read {
761 Ok(av) => {
762 values.insert(key, av);
763 }
764 Err(e) => {
765 use crate::serde_errors::{ValueRefusal, named_value_refusal, value_refusal};
766 let msg = e.to_string();
767 return Err(de::Error::custom(match value_refusal(&msg) {
768 ValueRefusal::Number(inner) => format!(
769 "{}{}",
770 crate::serde_errors::VALIDATION_MARKER,
771 named_value_refusal(inner, &key)
772 ),
773 _ => msg,
774 }));
775 }
776 }
777 }
778 Ok(Some(values))
779}
780
781pub(crate) fn deserialize_named_null_refusals<'de, D: serde::Deserializer<'de>>(
787 deserializer: D,
788) -> std::result::Result<Option<HashMap<String, AttributeValue>>, D::Error> {
789 deserialize_named_refusals(deserializer, false, true)
790}
791
792pub(crate) fn deserialize_named_empty_and_null_refusals<'de, D: serde::Deserializer<'de>>(
796 deserializer: D,
797) -> std::result::Result<Option<HashMap<String, AttributeValue>>, D::Error> {
798 deserialize_named_refusals(deserializer, true, false)
799}
800
801fn deserialize_named_refusals<'de, D: serde::Deserializer<'de>>(
805 deserializer: D,
806 name_empty: bool,
807 name_older: bool,
808) -> std::result::Result<Option<HashMap<String, AttributeValue>>, D::Error> {
809 let Some(raw) =
810 Option::<serde_json::Map<String, serde_json::Value>>::deserialize(deserializer)?
811 else {
812 return Ok(None);
813 };
814 let mut values = HashMap::with_capacity(raw.len());
815 for (key, value) in raw {
816 match serde_json::from_value::<AttributeValue>(value) {
817 Ok(av) => {
818 values.insert(key, av);
819 }
820 Err(e) => {
821 use crate::serde_errors::{
822 EMPTY_VALUE, MORE_THAN_ONE_TYPE, VALIDATION_MARKER, ValueRefusal,
823 named_value_refusal, value_refusal,
824 };
825 let msg = e.to_string();
826 let older =
832 name_older && current_value_layer() == crate::validation::ValueLayer::Older;
833 return Err(de::Error::custom(match value_refusal(&msg) {
834 ValueRefusal::ParsedNull(inner) => {
835 format!("{VALIDATION_MARKER}{}", named_value_refusal(inner, &key))
836 }
837 ValueRefusal::Empty if name_empty => {
838 format!(
839 "{VALIDATION_MARKER}{}",
840 named_value_refusal(EMPTY_VALUE, &key)
841 )
842 }
843 ValueRefusal::MoreThanOneType if name_older => format!(
844 "{VALIDATION_MARKER}{}",
845 named_value_refusal(MORE_THAN_ONE_TYPE, &key)
846 ),
847 ValueRefusal::EmptySet(inner) if name_older => {
848 format!("{VALIDATION_MARKER}{}", named_value_refusal(inner, &key))
849 }
850 ValueRefusal::Number(inner) if older => {
851 format!("{VALIDATION_MARKER}{}", named_value_refusal(inner, &key))
852 }
853 _ => msg,
854 }));
855 }
856 }
857 }
858 Ok(Some(values))
859}
860
861pub(crate) fn deserialize_parameters<'de, D: serde::Deserializer<'de>>(
864 deserializer: D,
865) -> std::result::Result<Option<Vec<AttributeValue>>, D::Error> {
866 reading_parameters(|| Option::<Vec<AttributeValue>>::deserialize(deserializer))
867}
868
869pub(crate) fn validate_parameter_number(
877 num_str: &str,
878) -> std::result::Result<(), crate::errors::DynoxideError> {
879 if num_str.is_empty() || !is_well_formed_dynamo_number(num_str) {
880 return validate_dynamo_number(num_str);
881 }
882 let unconvertible = || {
883 crate::errors::DynoxideError::ValidationException(format!(
884 "The parameter cannot be converted to a numeric value: {num_str}"
885 ))
886 };
887 let unsigned = num_str.trim_start_matches(['+', '-']);
888 let (coefficient, exponent) = match unsigned.find(['e', 'E']) {
889 Some(at) => (
890 &unsigned[..at],
891 unsigned[at + 1..]
892 .parse::<i32>()
893 .map_err(|_| unconvertible())?,
894 ),
895 None => (unsigned, 0),
896 };
897 let fraction_digits = coefficient
898 .split_once('.')
899 .map_or(0, |(_, fraction)| fraction.len());
900 let scale = i64::try_from(fraction_digits).unwrap_or(i64::MAX) - i64::from(exponent);
901 if i32::try_from(scale).is_err() {
902 return Err(unconvertible());
903 }
904 let significant = coefficient
905 .chars()
906 .filter(char::is_ascii_digit)
907 .skip_while(|&d| d == '0')
908 .count()
909 .max(1);
910 if i64::try_from(significant).unwrap_or(i64::MAX) - scale >= i64::from(i32::MAX) {
911 return Err(crate::errors::DynoxideError::ValidationException(
912 "Number underflow. Attempting to store a number with magnitude smaller than \
913 supported range"
914 .to_string(),
915 ));
916 }
917 validate_dynamo_number(num_str)
918}
919
920pub fn validate_dynamo_number(
927 num_str: &str,
928) -> std::result::Result<(), crate::errors::DynoxideError> {
929 if num_str.is_empty() {
930 return Err(crate::errors::DynoxideError::ValidationException(
931 "The parameter cannot be converted to a numeric value".to_string(),
932 ));
933 }
934
935 if !is_well_formed_dynamo_number(num_str) {
944 return Err(crate::errors::DynoxideError::ValidationException(format!(
945 "The parameter cannot be converted to a numeric value: {num_str}"
946 )));
947 }
948
949 if num_str
954 .find(['e', 'E'])
955 .is_some_and(|pos| num_str[pos + 1..].parse::<i64>().is_err())
956 {
957 return Err(crate::errors::DynoxideError::ValidationException(format!(
958 "The parameter cannot be converted to a numeric value: {num_str}"
959 )));
960 }
961
962 let (mantissa_digits, exponent) = parse_number_parts(num_str);
965
966 let exponent = if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
967 let (coeff, written_exponent) = split_exponent(num_str);
972 let frac_digits = coeff.find('.').map_or(0, |dot| coeff.len() - dot - 1);
973 written_exponent
974 .saturating_sub(frac_digits as i64)
975 .saturating_add(1)
976 } else {
977 if mantissa_digits.len() > 38 {
979 return Err(crate::errors::DynoxideError::ValidationException(
980 "Attempting to store more than 38 significant digits in a Number".to_string(),
981 ));
982 }
983 i64::from(exponent)
984 };
985
986 if exponent > 126 {
989 return Err(crate::errors::DynoxideError::ValidationException(
990 "Number overflow. Attempting to store a number with magnitude larger than supported range"
991 .to_string(),
992 ));
993 }
994
995 if exponent < -129 {
1002 return Err(crate::errors::DynoxideError::ValidationException(
1003 "Number underflow. Attempting to store a number with magnitude smaller than supported range"
1004 .to_string(),
1005 ));
1006 }
1007
1008 Ok(())
1009}
1010
1011fn is_well_formed_dynamo_number(s: &str) -> bool {
1017 let bytes = s.as_bytes();
1018 let n = bytes.len();
1019 let mut i = 0;
1020
1021 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
1023 i += 1;
1024 }
1025
1026 let mut coeff_digits = 0usize;
1028 let mut dots = 0usize;
1029 while i < n && (bytes[i].is_ascii_digit() || bytes[i] == b'.') {
1030 if bytes[i] == b'.' {
1031 dots += 1;
1032 if dots > 1 {
1033 return false;
1034 }
1035 } else {
1036 coeff_digits += 1;
1037 }
1038 i += 1;
1039 }
1040 if coeff_digits == 0 {
1041 return false;
1042 }
1043
1044 if i < n && (bytes[i] == b'e' || bytes[i] == b'E') {
1046 i += 1;
1047 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
1048 i += 1;
1049 }
1050 let mut exp_digits = 0usize;
1051 while i < n && bytes[i].is_ascii_digit() {
1052 exp_digits += 1;
1053 i += 1;
1054 }
1055 if exp_digits == 0 {
1056 return false;
1057 }
1058 }
1059
1060 i == n
1062}
1063
1064pub fn number_size(num_str: &str) -> usize {
1078 let trimmed = num_str.trim();
1079 let negative = trimmed.starts_with('-');
1080 let abs = trimmed.strip_prefix(['-', '+']).unwrap_or(trimmed);
1081 let (mantissa, exponent) = parse_number_parts(abs);
1082
1083 let int_digits = exponent.clamp(0, mantissa.len() as i32) as usize;
1087 let frac_digits = mantissa.len() - int_digits;
1088
1089 let sign = usize::from(negative && !mantissa.is_empty());
1093
1094 int_digits.div_ceil(2) + frac_digits.div_ceil(2) + 1 + sign
1095}
1096
1097pub fn normalize_dynamo_number(num_str: &str) -> String {
1105 let trimmed = num_str.trim();
1106 if trimmed.is_empty() {
1107 return "0".to_string();
1108 }
1109 if validate_dynamo_number(trimmed).is_err() {
1115 return trimmed.to_string();
1116 }
1117
1118 let negative = trimmed.starts_with('-');
1119 let abs_str = if negative {
1120 &trimmed[1..]
1121 } else {
1122 trimmed.trim_start_matches('+')
1123 };
1124
1125 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
1126
1127 if mantissa_digits.is_empty() {
1129 return "0".to_string();
1130 }
1131
1132 let num_digits = mantissa_digits.len() as i32;
1137 let int_digits = exponent; let mut result = String::new();
1140 if negative {
1141 result.push('-');
1142 }
1143
1144 if int_digits <= 0 {
1145 result.push_str("0.");
1147 for _ in 0..(-int_digits) {
1148 result.push('0');
1149 }
1150 for &d in &mantissa_digits {
1151 result.push((b'0' + d) as char);
1152 }
1153 } else if int_digits >= num_digits {
1154 for &d in &mantissa_digits {
1156 result.push((b'0' + d) as char);
1157 }
1158 for _ in 0..(int_digits - num_digits) {
1159 result.push('0');
1160 }
1161 } else {
1162 let int_part = int_digits as usize;
1164 for &d in &mantissa_digits[..int_part] {
1165 result.push((b'0' + d) as char);
1166 }
1167 result.push('.');
1168 for &d in &mantissa_digits[int_part..] {
1169 result.push((b'0' + d) as char);
1170 }
1171 }
1172
1173 result
1174}
1175
1176fn zero_encoding() -> String {
1177 format!("1{}{}", "0".repeat(4), "0".repeat(40))
1179}
1180
1181fn encoded_exponent(exponent: i32) -> u16 {
1187 (exponent.clamp(-5000, 4999) + 5000) as u16
1188}
1189
1190fn encode_positive(mantissa: &[u8], exponent: i32) -> String {
1191 let exp_encoded = encoded_exponent(exponent);
1192 let mantissa_str = mantissa_to_string(mantissa, 40);
1193 format!("2{exp_encoded:04}{mantissa_str}")
1194}
1195
1196fn encode_negative(mantissa: &[u8], exponent: i32) -> String {
1197 let exp_encoded = 9999 - encoded_exponent(exponent);
1199 let mantissa_str = complement_mantissa(mantissa, 40);
1200 format!("0{exp_encoded:04}{mantissa_str}")
1201}
1202
1203pub(crate) fn parse_number_parts(s: &str) -> (Vec<u8>, i32) {
1210 let (coeff, exp_part) = split_exponent(s);
1212
1213 let (int_part, frac_part) = if let Some(dot) = coeff.find('.') {
1215 (&coeff[..dot], &coeff[dot + 1..])
1216 } else {
1217 (coeff, "")
1218 };
1219
1220 let mut digits: Vec<u8> = Vec::new();
1222 for ch in int_part.chars().chain(frac_part.chars()) {
1223 if ch.is_ascii_digit() {
1224 digits.push(ch as u8 - b'0');
1225 }
1226 }
1227
1228 if digits.is_empty() {
1229 return (vec![], 0);
1230 }
1231
1232 let int_len = int_part.chars().filter(|c| c.is_ascii_digit()).count() as i64;
1234
1235 let leading_zeros = digits.iter().take_while(|&&d| d == 0).count();
1237 digits.drain(..leading_zeros);
1238
1239 while digits.last() == Some(&0) {
1241 digits.pop();
1242 }
1243
1244 if digits.is_empty() {
1245 return (vec![], 0);
1246 }
1247
1248 let exponent = (int_len - leading_zeros as i64).saturating_add(exp_part);
1251 let exponent = exponent.clamp(i32::MIN.into(), i32::MAX.into()) as i32;
1252
1253 (digits, exponent)
1254}
1255
1256fn split_exponent(s: &str) -> (&str, i64) {
1264 let Some(pos) = s.find(['e', 'E']) else {
1265 return (s, 0);
1266 };
1267 let exponent = match s[pos + 1..].parse::<i64>() {
1268 Ok(exponent) => exponent,
1269 Err(e) => match e.kind() {
1270 std::num::IntErrorKind::PosOverflow => i64::MAX,
1271 std::num::IntErrorKind::NegOverflow => i64::MIN,
1272 _ => 0,
1273 },
1274 };
1275 (&s[..pos], exponent)
1276}
1277
1278fn mantissa_to_string(digits: &[u8], width: usize) -> String {
1279 let mut s = String::with_capacity(width);
1280 for &d in digits.iter().take(width) {
1281 s.push((b'0' + d) as char);
1282 }
1283 while s.len() < width {
1284 s.push('0');
1285 }
1286 s
1287}
1288
1289fn complement_mantissa(digits: &[u8], width: usize) -> String {
1290 let mut s = String::with_capacity(width);
1291 for i in 0..width {
1292 let d = if i < digits.len() { digits[i] } else { 0 };
1293 s.push((b'0' + (9 - d)) as char);
1294 }
1295 s
1296}
1297
1298fn hex_encode(bytes: &[u8]) -> String {
1300 let mut s = String::with_capacity(bytes.len() * 2);
1301 for &b in bytes {
1302 s.push_str(&format!("{b:02x}"));
1303 }
1304 s
1305}
1306
1307pub type Item = HashMap<String, AttributeValue>;
1313
1314#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1316pub struct SseSpecification {
1317 #[serde(rename = "Enabled", default)]
1318 pub enabled: Option<bool>,
1319 #[serde(rename = "SSEType", default)]
1320 pub sse_type: Option<String>,
1321 #[serde(rename = "KMSMasterKeyId", default)]
1322 pub kms_master_key_id: Option<String>,
1323}
1324
1325#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1327pub struct Tag {
1328 #[serde(rename = "Key")]
1329 pub key: String,
1330 #[serde(rename = "Value")]
1331 pub value: String,
1332}
1333
1334pub fn item_size(item: &Item) -> usize {
1336 item.iter()
1337 .map(|(name, value)| name.len() + value.size())
1338 .sum()
1339}
1340
1341pub(crate) fn vector_billed_bytes(dimensions: u32) -> usize {
1345 4 * dimensions as usize
1346}
1347
1348pub(crate) fn billed_item_size(item: &Item, vector_indexes: &[VectorIndex]) -> usize {
1363 if vector_indexes.is_empty() {
1364 return item_size(item);
1365 }
1366 item.iter()
1367 .map(|(name, value)| {
1368 let billed = vector_indexes
1369 .iter()
1370 .filter(|vix| vix.vector_attribute.attribute_name == *name)
1371 .find(|vix| vector_f32_values(value, vix.dimensions).is_some())
1372 .map(|vix| vector_billed_bytes(vix.dimensions));
1373 name.len() + billed.unwrap_or_else(|| value.size())
1374 })
1375 .sum()
1376}
1377
1378pub const MAX_ITEM_SIZE: usize = 400 * 1024;
1380
1381#[derive(Debug, Clone, Serialize, Deserialize)]
1384pub struct ItemCollectionMetrics {
1385 #[serde(rename = "ItemCollectionKey")]
1386 pub item_collection_key: HashMap<String, AttributeValue>,
1387 #[serde(rename = "SizeEstimateRangeGB")]
1388 pub size_estimate_range_gb: Vec<f64>,
1389}
1390
1391#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1393pub struct ConsumedCapacity {
1394 #[serde(rename = "TableName")]
1395 pub table_name: String,
1396 #[serde(rename = "CapacityUnits")]
1397 pub capacity_units: f64,
1398 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
1399 pub read_capacity_units: Option<f64>,
1400 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
1401 pub write_capacity_units: Option<f64>,
1402 #[serde(rename = "Table", skip_serializing_if = "Option::is_none")]
1403 pub table: Option<CapacityDetail>,
1404 #[serde(
1405 rename = "GlobalSecondaryIndexes",
1406 skip_serializing_if = "Option::is_none"
1407 )]
1408 pub global_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
1409 #[serde(
1410 rename = "LocalSecondaryIndexes",
1411 skip_serializing_if = "Option::is_none"
1412 )]
1413 pub local_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
1414 #[serde(rename = "VectorIndexes", skip_serializing_if = "Option::is_none")]
1418 pub vector_indexes: Option<HashMap<String, VectorCapacityDetail>>,
1419}
1420
1421#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1423pub struct CapacityDetail {
1424 #[serde(rename = "CapacityUnits")]
1425 pub capacity_units: f64,
1426 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
1427 pub read_capacity_units: Option<f64>,
1428 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
1429 pub write_capacity_units: Option<f64>,
1430}
1431
1432#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1439pub struct VectorCapacityDetail {
1440 #[serde(rename = "VectorWriteRequestBytes")]
1441 pub vector_write_request_bytes: f64,
1442}
1443
1444#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1452pub struct VectorSearchCapacity {
1453 #[serde(rename = "VectorSearchRequestBytes")]
1454 pub vector_search_request_bytes: f64,
1455}
1456
1457pub const MIN_VECTOR_REQUEST_BYTES: f64 = 1024.0;
1471
1472pub fn vector_request_bytes(bytes: usize) -> f64 {
1474 (bytes as f64).max(MIN_VECTOR_REQUEST_BYTES)
1475}
1476
1477pub const TRANSACTIONAL_CAPACITY_FACTOR: f64 = 2.0;
1482
1483pub fn write_capacity_units(item_size_bytes: usize) -> f64 {
1485 ((item_size_bytes as f64) / 1024.0).ceil().max(1.0)
1486}
1487
1488pub fn table_write_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
1495 let old = old_size.map(write_capacity_units);
1496 let new = new_size.map(write_capacity_units);
1497 match (old, new) {
1498 (Some(old), Some(new)) => old.max(new),
1499 (Some(units), None) | (None, Some(units)) => units,
1500 (None, None) => write_capacity_units(0),
1503 }
1504}
1505
1506pub fn table_read_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
1514 let larger = old_size.unwrap_or(0).max(new_size.unwrap_or(0));
1515 read_capacity_units(larger)
1516}
1517
1518pub fn read_capacity_units(item_size_bytes: usize) -> f64 {
1522 ((item_size_bytes as f64) / 4096.0).ceil().max(1.0)
1523}
1524
1525pub fn read_capacity_units_with_consistency(item_size_bytes: usize, consistent: bool) -> f64 {
1530 let strongly = read_capacity_units(item_size_bytes);
1531 if consistent { strongly } else { strongly / 2.0 }
1532}
1533
1534pub fn capacity_wanted(mode: Option<&str>) -> bool {
1542 matches!(mode, Some("TOTAL") | Some("INDEXES"))
1543}
1544
1545pub fn vector_capacity_wanted(mode: Option<&str>) -> bool {
1555 matches!(mode, Some("INDEXES"))
1556}
1557
1558pub fn consumed_capacity(
1560 table_name: &str,
1561 capacity_units: f64,
1562 mode: &Option<String>,
1563) -> Option<ConsumedCapacity> {
1564 let mode = mode.as_deref().unwrap_or("NONE");
1565 match mode {
1566 "TOTAL" => Some(ConsumedCapacity {
1567 table_name: table_name.to_string(),
1568 capacity_units,
1569 table: None,
1570 global_secondary_indexes: None,
1571 local_secondary_indexes: None,
1572 ..Default::default()
1573 }),
1574 "INDEXES" => Some(ConsumedCapacity {
1575 table_name: table_name.to_string(),
1576 capacity_units,
1577 table: Some(CapacityDetail {
1578 capacity_units,
1579 ..Default::default()
1580 }),
1581 global_secondary_indexes: None,
1582 local_secondary_indexes: None,
1583 ..Default::default()
1584 }),
1585 _ => None,
1586 }
1587}
1588
1589pub fn consumed_capacity_with_indexes(
1591 table_name: &str,
1592 table_units: f64,
1593 gsi_units: &HashMap<String, f64>,
1594 mode: &Option<String>,
1595) -> Option<ConsumedCapacity> {
1596 consumed_capacity_with_secondary_indexes(
1597 table_name,
1598 table_units,
1599 gsi_units,
1600 &HashMap::new(),
1601 mode,
1602 )
1603}
1604
1605pub fn consumed_capacity_with_secondary_indexes(
1607 table_name: &str,
1608 table_units: f64,
1609 gsi_units: &HashMap<String, f64>,
1610 lsi_units: &HashMap<String, f64>,
1611 mode: &Option<String>,
1612) -> Option<ConsumedCapacity> {
1613 secondary_index_capacity(
1614 table_name,
1615 table_units,
1616 gsi_units,
1617 lsi_units,
1618 mode,
1619 CapacityAxis::None,
1620 )
1621}
1622
1623#[derive(Clone, Copy, PartialEq)]
1631enum CapacityAxis {
1632 None,
1633 Write,
1634}
1635
1636impl CapacityAxis {
1637 fn write_units(self, units: f64) -> Option<f64> {
1639 match self {
1640 Self::None => None,
1641 Self::Write => Some(units),
1642 }
1643 }
1644}
1645
1646fn secondary_index_capacity(
1649 table_name: &str,
1650 table_units: f64,
1651 gsi_units: &HashMap<String, f64>,
1652 lsi_units: &HashMap<String, f64>,
1653 mode: &Option<String>,
1654 axis: CapacityAxis,
1655) -> Option<ConsumedCapacity> {
1656 let total = table_units + gsi_units.values().sum::<f64>() + lsi_units.values().sum::<f64>();
1657
1658 match mode.as_deref().unwrap_or("NONE") {
1659 "INDEXES" => Some(ConsumedCapacity {
1660 table_name: table_name.to_string(),
1661 capacity_units: total,
1662 write_capacity_units: axis.write_units(total),
1663 table: Some(CapacityDetail {
1664 capacity_units: table_units,
1665 write_capacity_units: axis.write_units(table_units),
1666 ..Default::default()
1667 }),
1668 global_secondary_indexes: capacity_detail_map(gsi_units, axis),
1669 local_secondary_indexes: capacity_detail_map(lsi_units, axis),
1670 ..Default::default()
1671 }),
1672 "TOTAL" => Some(ConsumedCapacity {
1673 table_name: table_name.to_string(),
1674 capacity_units: total,
1675 write_capacity_units: axis.write_units(total),
1676 ..Default::default()
1677 }),
1678 _ => None,
1679 }
1680}
1681
1682pub fn consumed_capacity_with_vector_indexes(
1690 table_name: &str,
1691 table_units: f64,
1692 gsi_units: &HashMap<String, f64>,
1693 lsi_units: &HashMap<String, f64>,
1694 vector_bytes: &HashMap<String, f64>,
1695 mode: &Option<String>,
1696) -> Option<ConsumedCapacity> {
1697 attach_vector_index_capacity(
1698 consumed_capacity_with_secondary_indexes(
1699 table_name,
1700 table_units,
1701 gsi_units,
1702 lsi_units,
1703 mode,
1704 ),
1705 vector_bytes,
1706 mode,
1707 )
1708}
1709
1710pub(crate) fn attach_vector_index_capacity(
1719 capacity: Option<ConsumedCapacity>,
1720 vector_bytes: &HashMap<String, f64>,
1721 mode: &Option<String>,
1722) -> Option<ConsumedCapacity> {
1723 let mut capacity = capacity?;
1724 if vector_bytes.is_empty() || !vector_capacity_wanted(mode.as_deref()) {
1725 return Some(capacity);
1726 }
1727 capacity.vector_indexes = Some(
1728 vector_bytes
1729 .iter()
1730 .map(|(name, bytes)| {
1731 (
1732 name.clone(),
1733 VectorCapacityDetail {
1734 vector_write_request_bytes: *bytes,
1735 },
1736 )
1737 })
1738 .collect(),
1739 );
1740 Some(capacity)
1741}
1742
1743pub fn transactional_read_capacity(
1748 table_name: &str,
1749 units: f64,
1750 mode: &Option<String>,
1751) -> Option<ConsumedCapacity> {
1752 match mode.as_deref().unwrap_or("NONE") {
1753 "TOTAL" => Some(ConsumedCapacity {
1754 table_name: table_name.to_string(),
1755 capacity_units: units,
1756 read_capacity_units: Some(units),
1757 ..Default::default()
1758 }),
1759 "INDEXES" => Some(ConsumedCapacity {
1760 table_name: table_name.to_string(),
1761 capacity_units: units,
1762 read_capacity_units: Some(units),
1763 table: Some(CapacityDetail {
1764 capacity_units: units,
1765 read_capacity_units: Some(units),
1766 ..Default::default()
1767 }),
1768 ..Default::default()
1769 }),
1770 _ => None,
1771 }
1772}
1773
1774pub fn build_transactional_capacity(
1781 table_units: &HashMap<String, f64>,
1782 mode: &Option<String>,
1783 builder: fn(&str, f64, &Option<String>) -> Option<ConsumedCapacity>,
1784) -> Option<Vec<ConsumedCapacity>> {
1785 if !capacity_wanted(mode.as_deref()) {
1786 return None;
1787 }
1788
1789 let mut tables: Vec<&String> = table_units.keys().collect();
1794 tables.sort();
1795
1796 Some(
1797 tables
1798 .into_iter()
1799 .filter_map(|table| builder(table, *table_units.get(table)?, mode))
1800 .collect(),
1801 )
1802}
1803
1804pub fn transactional_write_capacity_with_indexes(
1816 table_name: &str,
1817 table_units: f64,
1818 gsi_units: &HashMap<String, f64>,
1819 lsi_units: &HashMap<String, f64>,
1820 mode: &Option<String>,
1821) -> Option<ConsumedCapacity> {
1822 secondary_index_capacity(
1823 table_name,
1824 table_units,
1825 gsi_units,
1826 lsi_units,
1827 mode,
1828 CapacityAxis::Write,
1829 )
1830}
1831
1832fn capacity_detail_map(
1835 units: &HashMap<String, f64>,
1836 axis: CapacityAxis,
1837) -> Option<HashMap<String, CapacityDetail>> {
1838 if units.is_empty() {
1839 return None;
1840 }
1841 Some(
1842 units
1843 .iter()
1844 .map(|(name, &u)| {
1845 (
1846 name.clone(),
1847 CapacityDetail {
1848 capacity_units: u,
1849 write_capacity_units: axis.write_units(u),
1850 ..Default::default()
1851 },
1852 )
1853 })
1854 .collect(),
1855 )
1856}
1857
1858pub fn transactional_write_capacity(
1863 table_name: &str,
1864 units: f64,
1865 mode: &Option<String>,
1866) -> Option<ConsumedCapacity> {
1867 match mode.as_deref().unwrap_or("NONE") {
1868 "TOTAL" => Some(ConsumedCapacity {
1869 table_name: table_name.to_string(),
1870 capacity_units: units,
1871 write_capacity_units: Some(units),
1872 ..Default::default()
1873 }),
1874 "INDEXES" => Some(ConsumedCapacity {
1875 table_name: table_name.to_string(),
1876 capacity_units: units,
1877 write_capacity_units: Some(units),
1878 table: Some(CapacityDetail {
1879 capacity_units: units,
1880 write_capacity_units: Some(units),
1881 ..Default::default()
1882 }),
1883 ..Default::default()
1884 }),
1885 _ => None,
1886 }
1887}
1888
1889#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1891pub struct KeySchemaElement {
1892 #[serde(rename = "AttributeName", alias = "attribute_name")]
1893 pub attribute_name: String,
1894 #[serde(rename = "KeyType", alias = "key_type")]
1895 pub key_type: KeyType,
1896}
1897
1898#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1900pub enum KeyType {
1901 #[default]
1902 HASH,
1903 RANGE,
1904}
1905
1906#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1908pub struct AttributeDefinition {
1909 #[serde(rename = "AttributeName", alias = "attribute_name")]
1910 pub attribute_name: String,
1911 #[serde(rename = "AttributeType", alias = "attribute_type")]
1912 pub attribute_type: ScalarAttributeType,
1913}
1914
1915#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1917pub enum ScalarAttributeType {
1918 #[default]
1919 S,
1920 N,
1921 B,
1922}
1923
1924#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1926pub struct Projection {
1927 #[serde(
1928 rename = "ProjectionType",
1929 alias = "projection_type",
1930 default,
1931 skip_serializing_if = "Option::is_none"
1932 )]
1933 pub projection_type: Option<ProjectionType>,
1934 #[serde(
1935 rename = "NonKeyAttributes",
1936 alias = "non_key_attributes",
1937 skip_serializing_if = "Option::is_none"
1938 )]
1939 pub non_key_attributes: Option<Vec<String>>,
1940}
1941
1942#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1944#[allow(non_camel_case_types)]
1945pub enum ProjectionType {
1946 #[default]
1947 ALL,
1948 KEYS_ONLY,
1949 INCLUDE,
1950}
1951
1952#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1954pub struct GlobalSecondaryIndex {
1955 #[serde(rename = "IndexName", alias = "index_name")]
1956 pub index_name: String,
1957 #[serde(rename = "KeySchema", alias = "key_schema")]
1958 pub key_schema: Vec<KeySchemaElement>,
1959 #[serde(rename = "Projection", alias = "projection")]
1960 pub projection: Projection,
1961 #[serde(
1962 rename = "ProvisionedThroughput",
1963 alias = "provisioned_throughput",
1964 skip_serializing_if = "Option::is_none"
1965 )]
1966 pub provisioned_throughput: Option<ProvisionedThroughput>,
1967}
1968
1969#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1971pub struct LocalSecondaryIndex {
1972 #[serde(rename = "IndexName", alias = "index_name")]
1973 pub index_name: String,
1974 #[serde(rename = "KeySchema", alias = "key_schema")]
1975 pub key_schema: Vec<KeySchemaElement>,
1976 #[serde(rename = "Projection", alias = "projection")]
1977 pub projection: Projection,
1978}
1979
1980#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1983pub struct VectorAttributeDefinition {
1984 #[serde(rename = "AttributeName", alias = "attribute_name")]
1985 pub attribute_name: String,
1986}
1987
1988#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1991pub struct SearchSchemaElement {
1992 #[serde(rename = "AttributeName", alias = "attribute_name")]
1993 pub attribute_name: String,
1994 #[serde(
1995 rename = "SearchSchemaElementType",
1996 alias = "search_schema_element_type"
1997 )]
1998 pub search_schema_element_type: String,
1999}
2000
2001#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2006pub struct VectorIndex {
2007 #[serde(rename = "IndexName", alias = "index_name")]
2008 pub index_name: String,
2009 #[serde(rename = "VectorAttribute", alias = "vector_attribute")]
2010 pub vector_attribute: VectorAttributeDefinition,
2011 #[serde(
2012 rename = "SearchSchema",
2013 alias = "search_schema",
2014 default,
2015 skip_serializing_if = "Option::is_none"
2016 )]
2017 pub search_schema: Option<Vec<SearchSchemaElement>>,
2018 #[serde(rename = "Projection", alias = "projection")]
2019 pub projection: Projection,
2020 #[serde(rename = "Dimensions", alias = "dimensions")]
2021 pub dimensions: u32,
2022 #[serde(rename = "DistanceFunction", alias = "distance_function")]
2023 pub distance_function: String,
2024}
2025
2026#[derive(Debug, Clone, PartialEq)]
2031pub enum VectorValueError {
2032 NotAList,
2034 WrongDimensions {
2036 actual: usize,
2038 },
2039 ElementNotANumber {
2041 position: usize,
2043 actual: &'static str,
2045 },
2046 ElementOutOfRange {
2050 position: usize,
2052 value: String,
2054 },
2055}
2056
2057pub fn check_vector_f32_values(
2067 value: &AttributeValue,
2068 dimensions: u32,
2069) -> std::result::Result<Vec<f32>, VectorValueError> {
2070 let AttributeValue::L(elems) = value else {
2071 return Err(VectorValueError::NotAList);
2072 };
2073 if elems.len() != dimensions as usize {
2074 return Err(VectorValueError::WrongDimensions {
2075 actual: elems.len(),
2076 });
2077 }
2078 let mut out = Vec::with_capacity(elems.len());
2079 for (position, elem) in elems.iter().enumerate() {
2080 let AttributeValue::N(n) = elem else {
2081 return Err(VectorValueError::ElementNotANumber {
2082 position,
2083 actual: elem.type_name(),
2084 });
2085 };
2086 match n.parse::<f32>() {
2089 Ok(v) if v.is_finite() => out.push(v),
2090 _ => {
2091 return Err(VectorValueError::ElementOutOfRange {
2092 position,
2093 value: n.clone(),
2094 });
2095 }
2096 }
2097 }
2098 Ok(out)
2099}
2100
2101pub fn vector_f32_values(value: &AttributeValue, dimensions: u32) -> Option<Vec<f32>> {
2106 check_vector_f32_values(value, dimensions).ok()
2107}
2108
2109pub fn f32_number_string(v: f32) -> String {
2114 serde_json::to_string(&v).unwrap_or_else(|_| "0.0".to_string())
2115}
2116
2117#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2119pub struct ProvisionedThroughput {
2120 #[serde(rename = "ReadCapacityUnits", alias = "read_capacity_units", default)]
2121 pub read_capacity_units: Option<i64>,
2122 #[serde(rename = "WriteCapacityUnits", alias = "write_capacity_units", default)]
2123 pub write_capacity_units: Option<i64>,
2124}
2125
2126#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2128pub struct OnDemandThroughput {
2129 #[serde(
2130 rename = "MaxReadRequestUnits",
2131 alias = "max_read_request_units",
2132 default,
2133 skip_serializing_if = "Option::is_none"
2134 )]
2135 pub max_read_request_units: Option<i64>,
2136 #[serde(
2137 rename = "MaxWriteRequestUnits",
2138 alias = "max_write_request_units",
2139 default,
2140 skip_serializing_if = "Option::is_none"
2141 )]
2142 pub max_write_request_units: Option<i64>,
2143}
2144
2145#[derive(Debug, Clone, PartialEq)]
2151pub struct ConversionError {
2152 pub expected: &'static str,
2154 pub actual: &'static str,
2156}
2157
2158impl fmt::Display for ConversionError {
2159 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2160 write!(f, "expected {}, got {}", self.expected, self.actual)
2161 }
2162}
2163
2164impl std::error::Error for ConversionError {}
2165
2166impl From<String> for AttributeValue {
2169 fn from(value: String) -> Self {
2170 AttributeValue::S(value)
2171 }
2172}
2173
2174impl From<&str> for AttributeValue {
2175 fn from(value: &str) -> Self {
2176 AttributeValue::S(value.to_string())
2177 }
2178}
2179
2180impl From<bool> for AttributeValue {
2181 fn from(value: bool) -> Self {
2182 AttributeValue::BOOL(value)
2183 }
2184}
2185
2186impl From<Vec<u8>> for AttributeValue {
2187 fn from(value: Vec<u8>) -> Self {
2188 AttributeValue::B(value)
2189 }
2190}
2191
2192impl From<&[u8]> for AttributeValue {
2193 fn from(value: &[u8]) -> Self {
2194 AttributeValue::B(value.to_vec())
2195 }
2196}
2197
2198macro_rules! impl_from_integer {
2200 ($($t:ty),+) => {
2201 $(
2202 impl From<$t> for AttributeValue {
2203 fn from(value: $t) -> Self {
2204 AttributeValue::N(value.to_string())
2205 }
2206 }
2207 )+
2208 };
2209}
2210
2211impl_from_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
2212
2213impl From<HashMap<String, AttributeValue>> for AttributeValue {
2215 fn from(value: HashMap<String, AttributeValue>) -> Self {
2216 AttributeValue::M(value)
2217 }
2218}
2219
2220impl From<Vec<AttributeValue>> for AttributeValue {
2221 fn from(value: Vec<AttributeValue>) -> Self {
2222 AttributeValue::L(value)
2223 }
2224}
2225
2226impl From<HashSet<String>> for AttributeValue {
2227 fn from(value: HashSet<String>) -> Self {
2228 AttributeValue::SS(value.into_iter().collect())
2229 }
2230}
2231
2232impl From<BTreeSet<String>> for AttributeValue {
2233 fn from(value: BTreeSet<String>) -> Self {
2234 AttributeValue::SS(value.into_iter().collect())
2235 }
2236}
2237
2238impl TryFrom<f64> for AttributeValue {
2241 type Error = ConversionError;
2242
2243 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
2244 if value.is_finite() {
2245 Ok(AttributeValue::N(value.to_string()))
2246 } else {
2247 Err(ConversionError {
2248 expected: "finite f64",
2249 actual: "NaN or Infinity",
2250 })
2251 }
2252 }
2253}
2254
2255impl TryFrom<f32> for AttributeValue {
2256 type Error = ConversionError;
2257
2258 fn try_from(value: f32) -> std::result::Result<Self, Self::Error> {
2259 if value.is_finite() {
2260 Ok(AttributeValue::N(value.to_string()))
2261 } else {
2262 Err(ConversionError {
2263 expected: "finite f32",
2264 actual: "NaN or Infinity",
2265 })
2266 }
2267 }
2268}
2269
2270impl TryFrom<AttributeValue> for String {
2273 type Error = ConversionError;
2274
2275 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2276 match value {
2277 AttributeValue::S(s) => Ok(s),
2278 other => Err(ConversionError {
2279 expected: "S",
2280 actual: other.type_name(),
2281 }),
2282 }
2283 }
2284}
2285
2286impl TryFrom<AttributeValue> for bool {
2287 type Error = ConversionError;
2288
2289 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2290 match value {
2291 AttributeValue::BOOL(b) => Ok(b),
2292 other => Err(ConversionError {
2293 expected: "BOOL",
2294 actual: other.type_name(),
2295 }),
2296 }
2297 }
2298}
2299
2300impl TryFrom<AttributeValue> for Vec<u8> {
2301 type Error = ConversionError;
2302
2303 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2304 match value {
2305 AttributeValue::B(b) => Ok(b),
2306 other => Err(ConversionError {
2307 expected: "B",
2308 actual: other.type_name(),
2309 }),
2310 }
2311 }
2312}
2313
2314macro_rules! impl_try_from_av_integer {
2315 ($($t:ty),+) => {
2316 $(
2317 impl TryFrom<AttributeValue> for $t {
2318 type Error = ConversionError;
2319
2320 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2321 match value {
2322 AttributeValue::N(n) => n.parse::<$t>().map_err(|_| ConversionError {
2323 expected: stringify!($t),
2324 actual: "N (parse failed)",
2325 }),
2326 other => Err(ConversionError {
2327 expected: "N",
2328 actual: other.type_name(),
2329 }),
2330 }
2331 }
2332 }
2333 )+
2334 };
2335}
2336
2337impl_try_from_av_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
2338
2339impl TryFrom<AttributeValue> for f64 {
2340 type Error = ConversionError;
2341
2342 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2343 match value {
2344 AttributeValue::N(n) => n.parse::<f64>().map_err(|_| ConversionError {
2345 expected: "f64",
2346 actual: "N (parse failed)",
2347 }),
2348 other => Err(ConversionError {
2349 expected: "N",
2350 actual: other.type_name(),
2351 }),
2352 }
2353 }
2354}
2355
2356impl TryFrom<AttributeValue> for f32 {
2357 type Error = ConversionError;
2358
2359 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2360 match value {
2361 AttributeValue::N(n) => n.parse::<f32>().map_err(|_| ConversionError {
2362 expected: "f32",
2363 actual: "N (parse failed)",
2364 }),
2365 other => Err(ConversionError {
2366 expected: "N",
2367 actual: other.type_name(),
2368 }),
2369 }
2370 }
2371}
2372
2373impl TryFrom<AttributeValue> for HashMap<String, AttributeValue> {
2374 type Error = ConversionError;
2375
2376 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2377 match value {
2378 AttributeValue::M(m) => Ok(m),
2379 other => Err(ConversionError {
2380 expected: "M",
2381 actual: other.type_name(),
2382 }),
2383 }
2384 }
2385}
2386
2387impl TryFrom<AttributeValue> for Vec<AttributeValue> {
2388 type Error = ConversionError;
2389
2390 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2391 match value {
2392 AttributeValue::L(l) => Ok(l),
2393 other => Err(ConversionError {
2394 expected: "L",
2395 actual: other.type_name(),
2396 }),
2397 }
2398 }
2399}
2400
2401impl TryFrom<AttributeValue> for Vec<String> {
2402 type Error = ConversionError;
2403
2404 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2405 match value {
2406 AttributeValue::SS(ss) => Ok(ss),
2407 AttributeValue::L(l) => {
2408 l.into_iter()
2410 .map(|av| match av {
2411 AttributeValue::S(s) => Ok(s),
2412 other => Err(ConversionError {
2413 expected: "S (within L)",
2414 actual: other.type_name(),
2415 }),
2416 })
2417 .collect()
2418 }
2419 other => Err(ConversionError {
2420 expected: "SS or L",
2421 actual: other.type_name(),
2422 }),
2423 }
2424 }
2425}
2426
2427#[cfg(test)]
2428mod tests {
2429 use super::*;
2430
2431 #[test]
2432 fn test_serialize_string() {
2433 let val = AttributeValue::S("hello".to_string());
2434 let json = serde_json::to_string(&val).unwrap();
2435 assert_eq!(json, r#"{"S":"hello"}"#);
2436 }
2437
2438 #[test]
2439 fn test_serialize_number() {
2440 let val = AttributeValue::N("42".to_string());
2441 let json = serde_json::to_string(&val).unwrap();
2442 assert_eq!(json, r#"{"N":"42"}"#);
2443 }
2444
2445 #[test]
2446 fn test_serialize_binary() {
2447 let val = AttributeValue::B(vec![1, 2, 3]);
2448 let json = serde_json::to_string(&val).unwrap();
2449 assert_eq!(json, r#"{"B":"AQID"}"#);
2450 }
2451
2452 #[test]
2453 fn test_serialize_bool() {
2454 let val = AttributeValue::BOOL(true);
2455 let json = serde_json::to_string(&val).unwrap();
2456 assert_eq!(json, r#"{"BOOL":true}"#);
2457 }
2458
2459 #[test]
2460 fn test_serialize_null() {
2461 let val = AttributeValue::NULL(true);
2462 let json = serde_json::to_string(&val).unwrap();
2463 assert_eq!(json, r#"{"NULL":true}"#);
2464 }
2465
2466 #[test]
2467 fn test_deserialize_null_true() {
2468 let val: AttributeValue = serde_json::from_str(r#"{"NULL":true}"#).unwrap();
2469 assert_eq!(val, AttributeValue::NULL(true));
2470 }
2471
2472 #[test]
2473 fn test_deserialize_null_false_rejected() {
2474 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":false}"#).unwrap_err();
2477 assert!(
2478 err.to_string().contains("must have the value of true"),
2479 "unexpected error: {err}"
2480 );
2481 }
2482
2483 #[test]
2484 fn test_deserialize_null_non_boolean_rejected() {
2485 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":"no"}"#).unwrap_err();
2487 assert!(
2488 err.to_string().contains("must have the value of true"),
2489 "unexpected error: {err}"
2490 );
2491 }
2492
2493 #[test]
2494 fn test_serialize_string_set() {
2495 let val = AttributeValue::SS(vec!["a".to_string(), "b".to_string()]);
2496 let json = serde_json::to_string(&val).unwrap();
2497 assert_eq!(json, r#"{"SS":["a","b"]}"#);
2498 }
2499
2500 #[test]
2501 fn test_serialize_list() {
2502 let val = AttributeValue::L(vec![
2503 AttributeValue::S("hello".to_string()),
2504 AttributeValue::N("42".to_string()),
2505 ]);
2506 let json = serde_json::to_string(&val).unwrap();
2507 assert_eq!(json, r#"{"L":[{"S":"hello"},{"N":"42"}]}"#);
2508 }
2509
2510 #[test]
2511 fn test_serialize_map() {
2512 let mut m = HashMap::new();
2513 m.insert("key".to_string(), AttributeValue::S("value".to_string()));
2514 let val = AttributeValue::M(m);
2515 let json = serde_json::to_string(&val).unwrap();
2516 assert_eq!(json, r#"{"M":{"key":{"S":"value"}}}"#);
2517 }
2518
2519 #[test]
2520 fn test_round_trip_all_types() {
2521 let values = vec![
2522 AttributeValue::S("hello".to_string()),
2523 AttributeValue::N("42.5".to_string()),
2524 AttributeValue::B(vec![0, 255, 128]),
2525 AttributeValue::BOOL(false),
2526 AttributeValue::NULL(true),
2527 AttributeValue::SS(vec!["x".to_string(), "y".to_string()]),
2528 AttributeValue::NS(vec!["1".to_string(), "2.5".to_string()]),
2529 AttributeValue::BS(vec![vec![1], vec![2, 3]]),
2530 AttributeValue::L(vec![
2531 AttributeValue::S("nested".to_string()),
2532 AttributeValue::N("99".to_string()),
2533 ]),
2534 ];
2535
2536 for val in values {
2537 let json = serde_json::to_string(&val).unwrap();
2538 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
2539 assert_eq!(val, deserialized, "Round-trip failed for {json}");
2540 }
2541 }
2542
2543 #[test]
2544 fn test_size_string() {
2545 let val = AttributeValue::S("hello".to_string());
2546 assert_eq!(val.size(), 5);
2547 }
2548
2549 #[test]
2550 fn test_size_number() {
2551 let val = AttributeValue::N("42".to_string());
2553 assert_eq!(val.size(), 2);
2554 }
2555
2556 #[test]
2557 fn test_size_number_matches_dynamodb() {
2558 let cases: &[(&str, usize)] = &[
2566 ("1", 2),
2568 ("12", 2),
2569 ("123", 3),
2570 ("1234", 3),
2571 ("12345", 4),
2572 ("123456", 4),
2573 ("1234567", 5),
2574 ("12345678", 5),
2575 ("123456789", 6),
2576 ("1234567891", 6),
2577 ("12345678912", 7),
2578 ("123456789123", 7),
2579 ("1234567890123456789", 11),
2580 ("12345678901234567890123456789012345678", 20),
2581 ("0042", 2),
2583 ("100", 2),
2584 ("1010", 3),
2585 ("0.0000001", 2),
2586 ("0", 1),
2587 ("1E125", 2),
2589 ("1E-100", 2),
2590 ("1.5", 3),
2593 ("1.2", 3),
2594 ("1.200", 3),
2595 ("1.234", 4),
2596 ("3.14159", 5),
2597 ("100.5", 4),
2598 ("0.15", 2),
2599 ("15", 2),
2600 ("-42", 3),
2602 ("-12345", 5),
2603 ("-1E125", 3),
2604 ("-0.15", 3),
2605 ("-0", 1),
2606 ];
2607 for (literal, expected) in cases {
2608 assert_eq!(
2609 AttributeValue::N((*literal).to_string()).size(),
2610 *expected,
2611 "size of N {literal:?}"
2612 );
2613 }
2614 }
2615
2616 #[test]
2617 fn test_size_number_survives_normalisation() {
2618 for literal in [
2622 "1E125",
2623 "1E-100",
2624 "0042",
2625 "1.200",
2626 "0.0000001",
2627 "100",
2628 "3.14159",
2629 "42",
2630 ] {
2631 let raw = AttributeValue::N(literal.to_string()).size();
2632 let normalised = AttributeValue::N(normalize_dynamo_number(literal)).size();
2633 assert_eq!(
2634 raw, normalised,
2635 "N {literal:?} changed size when normalised"
2636 );
2637 }
2638 }
2639
2640 #[test]
2641 fn test_size_bool() {
2642 assert_eq!(AttributeValue::BOOL(true).size(), 1);
2643 }
2644
2645 #[test]
2646 fn test_size_null() {
2647 assert_eq!(AttributeValue::NULL(true).size(), 1);
2648 }
2649
2650 #[test]
2651 fn test_key_string_s() {
2652 let val = AttributeValue::S("hello".to_string());
2653 assert_eq!(val.to_key_string(), Some("S:hello".to_string()));
2654 }
2655
2656 #[test]
2657 fn test_key_string_n() {
2658 let val = AttributeValue::N("42".to_string());
2659 let key = val.to_key_string().unwrap();
2660 assert!(key.starts_with("N:"));
2661 }
2662
2663 #[test]
2664 fn test_key_string_b() {
2665 let val = AttributeValue::B(vec![0xff, 0x00, 0xab]);
2666 assert_eq!(val.to_key_string(), Some("B:ff00ab".to_string()));
2667 }
2668
2669 #[test]
2670 fn test_key_string_non_key_type_returns_none() {
2671 assert_eq!(AttributeValue::BOOL(true).to_key_string(), None);
2672 assert_eq!(AttributeValue::L(vec![]).to_key_string(), None);
2673 }
2674
2675 #[test]
2677 fn test_number_sort_ordering() {
2678 let numbers = vec![
2679 "-1000", "-100", "-10", "-1", "-0.5", "-0.001", "0", "0.001", "0.5", "1", "10", "100",
2680 "1000",
2681 ];
2682 let encoded: Vec<String> = numbers
2683 .iter()
2684 .map(|n| normalize_number_for_sort(n))
2685 .collect();
2686
2687 for i in 0..encoded.len() - 1 {
2688 assert!(
2689 encoded[i] < encoded[i + 1],
2690 "Sort order broken: {} ({}) should be < {} ({})",
2691 numbers[i],
2692 encoded[i],
2693 numbers[i + 1],
2694 encoded[i + 1]
2695 );
2696 }
2697 }
2698
2699 #[test]
2700 fn test_number_sort_zero_variants() {
2701 let z1 = normalize_number_for_sort("0");
2702 let z2 = normalize_number_for_sort("-0");
2703 let z3 = normalize_number_for_sort("0.0");
2704 assert_eq!(z1, z2);
2705 assert_eq!(z2, z3);
2706 }
2707
2708 #[test]
2709 fn test_number_sort_decimals() {
2710 let a = normalize_number_for_sort("1.5");
2711 let b = normalize_number_for_sort("2.5");
2712 assert!(a < b);
2713
2714 let c = normalize_number_for_sort("0.001");
2715 let d = normalize_number_for_sort("0.01");
2716 assert!(c < d);
2717 }
2718
2719 #[test]
2720 fn test_number_sort_scientific() {
2721 let a = normalize_number_for_sort("1e10");
2722 let b = normalize_number_for_sort("1e11");
2723 assert!(a < b);
2724
2725 let c = normalize_number_for_sort("-1e11");
2726 let d = normalize_number_for_sort("-1e10");
2727 assert!(c < d);
2728 }
2729
2730 #[test]
2734 fn test_number_sort_out_of_range_sorts_at_the_extremes() {
2735 let ordered = [
2736 "-1e99999999999",
2737 "-9.9999999999999999999999999999999999999E125",
2738 "-1E-130",
2739 "-1e-99999999999",
2740 "0",
2741 "1e-99999999999",
2742 "1E-130",
2743 "9.9999999999999999999999999999999999999E125",
2744 "1e99999999999",
2745 ];
2746 for pair in ordered.windows(2) {
2747 let (a, b) = (pair[0], pair[1]);
2748 assert!(
2749 normalize_number_for_sort(a) < normalize_number_for_sort(b),
2750 "{a} should sort before {b}"
2751 );
2752 }
2753 }
2754
2755 #[test]
2758 fn test_split_exponent_reads_an_unparseable_exponent_as_zero() {
2759 assert_eq!(split_exponent("1e"), ("1", 0));
2760 assert_eq!(split_exponent("1e+"), ("1", 0));
2761 assert_eq!(split_exponent("1e99999999999999999999"), ("1", i64::MAX));
2762 assert_eq!(split_exponent("1e-99999999999999999999"), ("1", i64::MIN));
2763 }
2764
2765 #[test]
2770 fn test_validate_number_accepts_dynamodb_grammar() {
2771 for input in [
2772 "+5", "+1.5", "+0", "-0", "+0.0", "+1e2", "1e+2", "1.5E+3", "-7", "+.5", ".5", "5.",
2773 "00042", "1.23E10", "+1e-2", "1E-130", "+1E-130",
2774 ] {
2775 assert!(
2776 validate_dynamo_number(input).is_ok(),
2777 "expected {input} to validate, got {:?}",
2778 validate_dynamo_number(input)
2779 );
2780 }
2781 }
2782
2783 #[test]
2784 fn test_validate_number_rejects_malformed() {
2785 for input in [
2788 "+e2", "e2", "+1+2", "1+2", "+1.2.3", "1.2.3", "++5", "+-5", "-+5", "+", "-", "1e",
2789 "1e+", ".", "1.2e3.4", "0x5", "NaN", "Infinity", "1_000", " 5", "5 ", "1 5", "",
2790 ] {
2791 assert!(
2792 matches!(
2793 validate_dynamo_number(input),
2794 Err(crate::errors::DynoxideError::ValidationException(_))
2795 ),
2796 "expected {input:?} to be rejected with ValidationException, got {:?}",
2797 validate_dynamo_number(input)
2798 );
2799 }
2800 }
2801
2802 fn validation_message(input: &str) -> Option<String> {
2803 match validate_dynamo_number(input) {
2804 Ok(()) => None,
2805 Err(crate::errors::DynoxideError::ValidationException(m)) => Some(m),
2806 Err(e) => panic!("expected a ValidationException for {input:?}, got {e:?}"),
2807 }
2808 }
2809
2810 const OVERFLOW: &str =
2811 "Number overflow. Attempting to store a number with magnitude larger than supported range";
2812 const UNDERFLOW: &str = "Number underflow. Attempting to store a number with magnitude smaller than supported range";
2813
2814 #[test]
2818 fn test_validate_number_range_checks_exponents_beyond_i32() {
2819 for (input, expected) in [
2820 ("1e99999999999", OVERFLOW),
2821 ("-1e99999999999", OVERFLOW),
2822 ("1E+99999999999", OVERFLOW),
2823 ("1e-99999999999", UNDERFLOW),
2824 ("-1e-99999999999", UNDERFLOW),
2825 ("1e2147483648", OVERFLOW),
2826 ("1e-2147483649", UNDERFLOW),
2827 ("10e2147483647", OVERFLOW),
2829 ("0.01e-2147483648", UNDERFLOW),
2830 ("1e9223372036854775807", OVERFLOW),
2831 ("1e-9223372036854775808", UNDERFLOW),
2832 ("10e9223372036854775807", OVERFLOW),
2833 ("0.01e-9223372036854775808", UNDERFLOW),
2834 ] {
2835 assert_eq!(
2836 validation_message(input).as_deref(),
2837 Some(expected),
2838 "{input}"
2839 );
2840 }
2841 }
2842
2843 #[test]
2847 fn test_validate_number_rejects_exponent_beyond_i64_as_unconvertible() {
2848 for input in [
2849 "1e9223372036854775808",
2850 "1e+9223372036854775808",
2851 "1e-9223372036854775809",
2852 "0e9223372036854775808",
2853 ] {
2854 assert_eq!(
2855 validation_message(input),
2856 Some(format!(
2857 "The parameter cannot be converted to a numeric value: {input}"
2858 )),
2859 "{input}"
2860 );
2861 }
2862 }
2863
2864 #[test]
2868 fn test_validate_number_range_checks_zero_by_its_last_written_digit() {
2869 let zeros_130 = format!("0.{}", "0".repeat(130));
2870 let zeros_131 = format!("0.{}", "0".repeat(131));
2871 for (input, expected) in [
2872 ("0e125", None),
2873 ("0e126", Some(OVERFLOW)),
2874 ("0.0e126", None),
2875 ("0.0e127", Some(OVERFLOW)),
2876 ("0.00e127", None),
2877 ("00e126", Some(OVERFLOW)),
2878 ("00.00e126", None),
2879 ("-0e127", Some(OVERFLOW)),
2880 ("0e99999999999", Some(OVERFLOW)),
2881 ("0e-130", None),
2882 ("0e-131", Some(UNDERFLOW)),
2883 ("0.0e-129", None),
2884 ("0.0e-130", Some(UNDERFLOW)),
2885 ("0e-99999999999", Some(UNDERFLOW)),
2886 (zeros_130.as_str(), None),
2887 (zeros_131.as_str(), Some(UNDERFLOW)),
2888 ] {
2889 assert_eq!(validation_message(input).as_deref(), expected, "{input}");
2890 }
2891 }
2892
2893 #[test]
2894 fn test_normalize_number_matches_dynamodb() {
2895 for (input, stored) in [
2896 ("+5", "5"),
2897 ("+1.5", "1.5"),
2898 ("+0", "0"),
2899 ("-0", "0"),
2900 ("+0.0", "0"),
2901 ("+1e2", "100"),
2902 ("1e+2", "100"),
2903 ("1.5E+3", "1500"),
2904 ("-7", "-7"),
2905 ("+.5", "0.5"),
2906 (".5", "0.5"),
2907 ("5.", "5"),
2908 ("00042", "42"),
2909 ("1.23E10", "12300000000"),
2910 ("+1e-2", "0.01"),
2911 ] {
2912 assert_eq!(
2913 normalize_dynamo_number(input),
2914 stored,
2915 "{input} should normalise to {stored}"
2916 );
2917 }
2918 }
2919
2920 #[test]
2921 fn test_type_name() {
2922 assert_eq!(AttributeValue::S("".to_string()).type_name(), "S");
2923 assert_eq!(AttributeValue::N("0".to_string()).type_name(), "N");
2924 assert_eq!(AttributeValue::B(vec![]).type_name(), "B");
2925 assert_eq!(AttributeValue::BOOL(true).type_name(), "BOOL");
2926 assert_eq!(AttributeValue::NULL(true).type_name(), "NULL");
2927 assert_eq!(AttributeValue::SS(vec![]).type_name(), "SS");
2928 assert_eq!(AttributeValue::NS(vec![]).type_name(), "NS");
2929 assert_eq!(AttributeValue::BS(vec![]).type_name(), "BS");
2930 assert_eq!(AttributeValue::L(vec![]).type_name(), "L");
2931 assert_eq!(AttributeValue::M(HashMap::new()).type_name(), "M");
2932 }
2933}