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
629pub(crate) fn empty_set_refusal(
631 type_key: &str,
632 member: &serde_json::Value,
633) -> Option<&'static str> {
634 if !member.as_array().is_some_and(Vec::is_empty) {
635 return None;
636 }
637 match type_key {
638 "SS" => Some("One or more parameter values were invalid: An string set may not be empty"),
639 "NS" => Some("One or more parameter values were invalid: An number set may not be empty"),
640 "BS" => Some("One or more parameter values were invalid: Binary sets should not be empty"),
641 _ => None,
642 }
643}
644
645const TYPE_MEMBERS: [&str; 10] = ["S", "N", "B", "BOOL", "NULL", "SS", "NS", "BS", "L", "M"];
647
648pub(crate) fn reads_on_older_layer(value: &serde_json::Value) -> bool {
659 match value {
660 serde_json::Value::Object(map) => {
661 let string_flag = ["BOOL", "NULL", "Exists"]
662 .iter()
663 .any(|member| map.get(*member).is_some_and(serde_json::Value::is_string));
664 let members: Vec<&serde_json::Value> = TYPE_MEMBERS
665 .iter()
666 .filter_map(|member| map.get(*member))
667 .filter(|member| !member.is_null())
668 .collect();
669 string_flag
670 || (members.len() > 1 && members.iter().any(|member| !member.is_object()))
671 || map.values().any(reads_on_older_layer)
672 }
673 serde_json::Value::Array(items) => items.iter().any(reads_on_older_layer),
674 _ => false,
675 }
676}
677
678pub(crate) fn current_value_layer() -> crate::validation::ValueLayer {
681 VALUE_LAYER.with(std::cell::Cell::get)
682}
683
684thread_local! {
685 static VALUE_LAYER: std::cell::Cell<crate::validation::ValueLayer> =
689 const { std::cell::Cell::new(crate::validation::ValueLayer::Current) };
690}
691
692pub(crate) fn with_value_layer<T>(
696 layer: crate::validation::ValueLayer,
697 read: impl FnOnce() -> T,
698) -> T {
699 struct Restore(crate::validation::ValueLayer);
700 impl Drop for Restore {
701 fn drop(&mut self) {
702 VALUE_LAYER.with(|cell| cell.set(self.0));
703 }
704 }
705 let _restore = Restore(VALUE_LAYER.with(|cell| cell.replace(layer)));
706 read()
707}
708
709#[cfg(any(feature = "wasm-sqlite", test))]
714pub(crate) async fn with_value_layer_async<F: std::future::Future>(
715 layer: crate::validation::ValueLayer,
716 future: F,
717) -> F::Output {
718 struct Layered<F> {
719 layer: crate::validation::ValueLayer,
720 future: std::pin::Pin<Box<F>>,
721 }
722 impl<F: std::future::Future> std::future::Future for Layered<F> {
723 type Output = F::Output;
724 fn poll(
725 mut self: std::pin::Pin<&mut Self>,
726 cx: &mut std::task::Context<'_>,
727 ) -> std::task::Poll<F::Output> {
728 let layer = self.layer;
729 with_value_layer(layer, || self.future.as_mut().poll(cx))
730 }
731 }
732 Layered {
733 layer,
734 future: Box::pin(future),
735 }
736 .await
737}
738
739pub(crate) fn reading_parameters<T>(read: impl FnOnce() -> T) -> T {
742 with_value_layer(crate::validation::ValueLayer::Older, read)
743}
744
745pub(crate) fn deserialize_older_expression_values<'de, D: serde::Deserializer<'de>>(
751 deserializer: D,
752) -> std::result::Result<Option<HashMap<String, AttributeValue>>, D::Error> {
753 let Some(raw) =
754 Option::<serde_json::Map<String, serde_json::Value>>::deserialize(deserializer)?
755 else {
756 return Ok(None);
757 };
758 let mut values = HashMap::with_capacity(raw.len());
759 for (key, value) in raw {
760 let read = with_value_layer(crate::validation::ValueLayer::Older, || {
761 serde_json::from_value::<AttributeValue>(value)
762 });
763 match read {
764 Ok(av) => {
765 values.insert(key, av);
766 }
767 Err(e) => {
768 use crate::serde_errors::{ValueRefusal, named_value_refusal, value_refusal};
769 let msg = e.to_string();
770 return Err(de::Error::custom(match value_refusal(&msg) {
771 ValueRefusal::Number(inner) => format!(
772 "{}{}",
773 crate::serde_errors::VALIDATION_MARKER,
774 named_value_refusal(inner, &key)
775 ),
776 _ => msg,
777 }));
778 }
779 }
780 }
781 Ok(Some(values))
782}
783
784pub(crate) fn deserialize_named_null_refusals<'de, D: serde::Deserializer<'de>>(
790 deserializer: D,
791) -> std::result::Result<Option<HashMap<String, AttributeValue>>, D::Error> {
792 deserialize_named_refusals(deserializer, false, true)
793}
794
795pub(crate) fn deserialize_named_empty_and_null_refusals<'de, D: serde::Deserializer<'de>>(
799 deserializer: D,
800) -> std::result::Result<Option<HashMap<String, AttributeValue>>, D::Error> {
801 deserialize_named_refusals(deserializer, true, false)
802}
803
804fn deserialize_named_refusals<'de, D: serde::Deserializer<'de>>(
808 deserializer: D,
809 name_empty: bool,
810 name_older: bool,
811) -> std::result::Result<Option<HashMap<String, AttributeValue>>, D::Error> {
812 let Some(raw) =
813 Option::<serde_json::Map<String, serde_json::Value>>::deserialize(deserializer)?
814 else {
815 return Ok(None);
816 };
817 let mut values = HashMap::with_capacity(raw.len());
818 for (key, value) in raw {
819 match serde_json::from_value::<AttributeValue>(value) {
820 Ok(av) => {
821 values.insert(key, av);
822 }
823 Err(e) => {
824 use crate::serde_errors::{
825 EMPTY_VALUE, MORE_THAN_ONE_TYPE, VALIDATION_MARKER, ValueRefusal,
826 named_value_refusal, value_refusal,
827 };
828 let msg = e.to_string();
829 let older =
835 name_older && current_value_layer() == crate::validation::ValueLayer::Older;
836 return Err(de::Error::custom(match value_refusal(&msg) {
837 ValueRefusal::ParsedNull(inner) => {
838 format!("{VALIDATION_MARKER}{}", named_value_refusal(inner, &key))
839 }
840 ValueRefusal::Empty if name_empty => {
841 format!(
842 "{VALIDATION_MARKER}{}",
843 named_value_refusal(EMPTY_VALUE, &key)
844 )
845 }
846 ValueRefusal::MoreThanOneType if name_older => format!(
847 "{VALIDATION_MARKER}{}",
848 named_value_refusal(MORE_THAN_ONE_TYPE, &key)
849 ),
850 ValueRefusal::EmptySet(inner) if name_older => {
851 format!("{VALIDATION_MARKER}{}", named_value_refusal(inner, &key))
852 }
853 ValueRefusal::Number(inner) if older => {
854 format!("{VALIDATION_MARKER}{}", named_value_refusal(inner, &key))
855 }
856 _ => msg,
857 }));
858 }
859 }
860 }
861 Ok(Some(values))
862}
863
864pub(crate) fn deserialize_parameters<'de, D: serde::Deserializer<'de>>(
867 deserializer: D,
868) -> std::result::Result<Option<Vec<AttributeValue>>, D::Error> {
869 reading_parameters(|| Option::<Vec<AttributeValue>>::deserialize(deserializer))
870}
871
872pub(crate) fn validate_parameter_number(
880 num_str: &str,
881) -> std::result::Result<(), crate::errors::DynoxideError> {
882 if num_str.is_empty() || !is_well_formed_dynamo_number(num_str) {
883 return validate_dynamo_number(num_str);
884 }
885 let unconvertible = || {
886 crate::errors::DynoxideError::ValidationException(format!(
887 "The parameter cannot be converted to a numeric value: {num_str}"
888 ))
889 };
890 let unsigned = num_str.trim_start_matches(['+', '-']);
891 let (coefficient, exponent) = match unsigned.find(['e', 'E']) {
892 Some(at) => (
893 &unsigned[..at],
894 unsigned[at + 1..]
895 .parse::<i32>()
896 .map_err(|_| unconvertible())?,
897 ),
898 None => (unsigned, 0),
899 };
900 let fraction_digits = coefficient
901 .split_once('.')
902 .map_or(0, |(_, fraction)| fraction.len());
903 let scale = i64::try_from(fraction_digits).unwrap_or(i64::MAX) - i64::from(exponent);
904 if i32::try_from(scale).is_err() {
905 return Err(unconvertible());
906 }
907 let significant = coefficient
908 .chars()
909 .filter(char::is_ascii_digit)
910 .skip_while(|&d| d == '0')
911 .count()
912 .max(1);
913 if i64::try_from(significant).unwrap_or(i64::MAX) - scale >= i64::from(i32::MAX) {
914 return Err(crate::errors::DynoxideError::ValidationException(
915 "Number underflow. Attempting to store a number with magnitude smaller than \
916 supported range"
917 .to_string(),
918 ));
919 }
920 validate_dynamo_number(num_str)
921}
922
923pub fn validate_dynamo_number(
930 num_str: &str,
931) -> std::result::Result<(), crate::errors::DynoxideError> {
932 if num_str.is_empty() {
933 return Err(crate::errors::DynoxideError::ValidationException(
934 "The parameter cannot be converted to a numeric value".to_string(),
935 ));
936 }
937
938 if !is_well_formed_dynamo_number(num_str) {
947 return Err(crate::errors::DynoxideError::ValidationException(format!(
948 "The parameter cannot be converted to a numeric value: {num_str}"
949 )));
950 }
951
952 if num_str
957 .find(['e', 'E'])
958 .is_some_and(|pos| num_str[pos + 1..].parse::<i64>().is_err())
959 {
960 return Err(crate::errors::DynoxideError::ValidationException(format!(
961 "The parameter cannot be converted to a numeric value: {num_str}"
962 )));
963 }
964
965 let (mantissa_digits, exponent) = parse_number_parts(num_str);
968
969 let exponent = if mantissa_digits.is_empty() || mantissa_digits.iter().all(|&d| d == 0) {
970 let (coeff, written_exponent) = split_exponent(num_str);
975 let frac_digits = coeff.find('.').map_or(0, |dot| coeff.len() - dot - 1);
976 written_exponent
977 .saturating_sub(frac_digits as i64)
978 .saturating_add(1)
979 } else {
980 if mantissa_digits.len() > 38 {
982 return Err(crate::errors::DynoxideError::ValidationException(
983 "Attempting to store more than 38 significant digits in a Number".to_string(),
984 ));
985 }
986 i64::from(exponent)
987 };
988
989 if exponent > 126 {
992 return Err(crate::errors::DynoxideError::ValidationException(
993 "Number overflow. Attempting to store a number with magnitude larger than supported range"
994 .to_string(),
995 ));
996 }
997
998 if exponent < -129 {
1005 return Err(crate::errors::DynoxideError::ValidationException(
1006 "Number underflow. Attempting to store a number with magnitude smaller than supported range"
1007 .to_string(),
1008 ));
1009 }
1010
1011 Ok(())
1012}
1013
1014fn is_well_formed_dynamo_number(s: &str) -> bool {
1020 let bytes = s.as_bytes();
1021 let n = bytes.len();
1022 let mut i = 0;
1023
1024 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
1026 i += 1;
1027 }
1028
1029 let mut coeff_digits = 0usize;
1031 let mut dots = 0usize;
1032 while i < n && (bytes[i].is_ascii_digit() || bytes[i] == b'.') {
1033 if bytes[i] == b'.' {
1034 dots += 1;
1035 if dots > 1 {
1036 return false;
1037 }
1038 } else {
1039 coeff_digits += 1;
1040 }
1041 i += 1;
1042 }
1043 if coeff_digits == 0 {
1044 return false;
1045 }
1046
1047 if i < n && (bytes[i] == b'e' || bytes[i] == b'E') {
1049 i += 1;
1050 if i < n && (bytes[i] == b'+' || bytes[i] == b'-') {
1051 i += 1;
1052 }
1053 let mut exp_digits = 0usize;
1054 while i < n && bytes[i].is_ascii_digit() {
1055 exp_digits += 1;
1056 i += 1;
1057 }
1058 if exp_digits == 0 {
1059 return false;
1060 }
1061 }
1062
1063 i == n
1065}
1066
1067pub fn number_size(num_str: &str) -> usize {
1081 let trimmed = num_str.trim();
1082 let negative = trimmed.starts_with('-');
1083 let abs = trimmed.strip_prefix(['-', '+']).unwrap_or(trimmed);
1084 let (mantissa, exponent) = parse_number_parts(abs);
1085
1086 let int_digits = exponent.clamp(0, mantissa.len() as i32) as usize;
1090 let frac_digits = mantissa.len() - int_digits;
1091
1092 let sign = usize::from(negative && !mantissa.is_empty());
1096
1097 int_digits.div_ceil(2) + frac_digits.div_ceil(2) + 1 + sign
1098}
1099
1100pub fn normalize_dynamo_number(num_str: &str) -> String {
1108 let trimmed = num_str.trim();
1109 if trimmed.is_empty() {
1110 return "0".to_string();
1111 }
1112 if validate_dynamo_number(trimmed).is_err() {
1118 return trimmed.to_string();
1119 }
1120
1121 let negative = trimmed.starts_with('-');
1122 let abs_str = if negative {
1123 &trimmed[1..]
1124 } else {
1125 trimmed.trim_start_matches('+')
1126 };
1127
1128 let (mantissa_digits, exponent) = parse_number_parts(abs_str);
1129
1130 if mantissa_digits.is_empty() {
1132 return "0".to_string();
1133 }
1134
1135 let num_digits = mantissa_digits.len() as i32;
1140 let int_digits = exponent; let mut result = String::new();
1143 if negative {
1144 result.push('-');
1145 }
1146
1147 if int_digits <= 0 {
1148 result.push_str("0.");
1150 for _ in 0..(-int_digits) {
1151 result.push('0');
1152 }
1153 for &d in &mantissa_digits {
1154 result.push((b'0' + d) as char);
1155 }
1156 } else if int_digits >= num_digits {
1157 for &d in &mantissa_digits {
1159 result.push((b'0' + d) as char);
1160 }
1161 for _ in 0..(int_digits - num_digits) {
1162 result.push('0');
1163 }
1164 } else {
1165 let int_part = int_digits as usize;
1167 for &d in &mantissa_digits[..int_part] {
1168 result.push((b'0' + d) as char);
1169 }
1170 result.push('.');
1171 for &d in &mantissa_digits[int_part..] {
1172 result.push((b'0' + d) as char);
1173 }
1174 }
1175
1176 result
1177}
1178
1179fn zero_encoding() -> String {
1180 format!("1{}{}", "0".repeat(4), "0".repeat(40))
1182}
1183
1184fn encoded_exponent(exponent: i32) -> u16 {
1190 (exponent.clamp(-5000, 4999) + 5000) as u16
1191}
1192
1193fn encode_positive(mantissa: &[u8], exponent: i32) -> String {
1194 let exp_encoded = encoded_exponent(exponent);
1195 let mantissa_str = mantissa_to_string(mantissa, 40);
1196 format!("2{exp_encoded:04}{mantissa_str}")
1197}
1198
1199fn encode_negative(mantissa: &[u8], exponent: i32) -> String {
1200 let exp_encoded = 9999 - encoded_exponent(exponent);
1202 let mantissa_str = complement_mantissa(mantissa, 40);
1203 format!("0{exp_encoded:04}{mantissa_str}")
1204}
1205
1206pub(crate) fn parse_number_parts(s: &str) -> (Vec<u8>, i32) {
1213 let (coeff, exp_part) = split_exponent(s);
1215
1216 let (int_part, frac_part) = if let Some(dot) = coeff.find('.') {
1218 (&coeff[..dot], &coeff[dot + 1..])
1219 } else {
1220 (coeff, "")
1221 };
1222
1223 let mut digits: Vec<u8> = Vec::new();
1225 for ch in int_part.chars().chain(frac_part.chars()) {
1226 if ch.is_ascii_digit() {
1227 digits.push(ch as u8 - b'0');
1228 }
1229 }
1230
1231 if digits.is_empty() {
1232 return (vec![], 0);
1233 }
1234
1235 let int_len = int_part.chars().filter(|c| c.is_ascii_digit()).count() as i64;
1237
1238 let leading_zeros = digits.iter().take_while(|&&d| d == 0).count();
1240 digits.drain(..leading_zeros);
1241
1242 while digits.last() == Some(&0) {
1244 digits.pop();
1245 }
1246
1247 if digits.is_empty() {
1248 return (vec![], 0);
1249 }
1250
1251 let exponent = (int_len - leading_zeros as i64).saturating_add(exp_part);
1254 let exponent = exponent.clamp(i32::MIN.into(), i32::MAX.into()) as i32;
1255
1256 (digits, exponent)
1257}
1258
1259fn split_exponent(s: &str) -> (&str, i64) {
1267 let Some(pos) = s.find(['e', 'E']) else {
1268 return (s, 0);
1269 };
1270 let exponent = match s[pos + 1..].parse::<i64>() {
1271 Ok(exponent) => exponent,
1272 Err(e) => match e.kind() {
1273 std::num::IntErrorKind::PosOverflow => i64::MAX,
1274 std::num::IntErrorKind::NegOverflow => i64::MIN,
1275 _ => 0,
1276 },
1277 };
1278 (&s[..pos], exponent)
1279}
1280
1281fn mantissa_to_string(digits: &[u8], width: usize) -> String {
1282 let mut s = String::with_capacity(width);
1283 for &d in digits.iter().take(width) {
1284 s.push((b'0' + d) as char);
1285 }
1286 while s.len() < width {
1287 s.push('0');
1288 }
1289 s
1290}
1291
1292fn complement_mantissa(digits: &[u8], width: usize) -> String {
1293 let mut s = String::with_capacity(width);
1294 for i in 0..width {
1295 let d = if i < digits.len() { digits[i] } else { 0 };
1296 s.push((b'0' + (9 - d)) as char);
1297 }
1298 s
1299}
1300
1301pub(crate) fn key_value_from_key_string(key: &str) -> Option<AttributeValue> {
1306 let (kind, body) = key.split_once(':')?;
1307 match kind {
1308 "S" => Some(AttributeValue::S(body.to_string())),
1309 "B" => hex_decode(body).map(AttributeValue::B),
1310 "N" => number_from_sortable(body).map(AttributeValue::N),
1311 _ => None,
1312 }
1313}
1314
1315fn number_from_sortable(body: &str) -> Option<String> {
1318 if body.len() != 45 || !body.bytes().all(|b| b.is_ascii_digit()) {
1319 return None;
1320 }
1321 let (sign, rest) = body.split_at(1);
1322 let (exponent, mantissa) = rest.split_at(4);
1323 let exponent: i32 = exponent.parse().ok()?;
1324 let (negative, exponent, mantissa): (bool, i32, String) = match sign {
1325 "1" => return Some("0".to_string()),
1326 "2" => (false, exponent - 5000, mantissa.to_string()),
1327 "0" => (
1328 true,
1329 (9999 - exponent) - 5000,
1330 mantissa
1331 .bytes()
1332 .map(|b| (b'0' + (9 - (b - b'0'))) as char)
1333 .collect(),
1334 ),
1335 _ => return None,
1336 };
1337 let digits = mantissa.trim_end_matches('0');
1338 if digits.is_empty() {
1339 return Some("0".to_string());
1340 }
1341 Some(format!(
1342 "{}0.{digits}E{exponent}",
1343 if negative { "-" } else { "" }
1344 ))
1345}
1346
1347fn hex_decode(hex: &str) -> Option<Vec<u8>> {
1348 if !hex.len().is_multiple_of(2) {
1349 return None;
1350 }
1351 (0..hex.len())
1352 .step_by(2)
1353 .map(|i| u8::from_str_radix(hex.get(i..i + 2)?, 16).ok())
1354 .collect()
1355}
1356
1357fn hex_encode(bytes: &[u8]) -> String {
1359 let mut s = String::with_capacity(bytes.len() * 2);
1360 for &b in bytes {
1361 s.push_str(&format!("{b:02x}"));
1362 }
1363 s
1364}
1365
1366pub type Item = HashMap<String, AttributeValue>;
1372
1373#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1375pub struct SseSpecification {
1376 #[serde(rename = "Enabled", default)]
1377 pub enabled: Option<bool>,
1378 #[serde(rename = "SSEType", default)]
1379 pub sse_type: Option<String>,
1380 #[serde(rename = "KMSMasterKeyId", default)]
1381 pub kms_master_key_id: Option<String>,
1382}
1383
1384#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1386pub struct Tag {
1387 #[serde(rename = "Key")]
1388 pub key: String,
1389 #[serde(rename = "Value")]
1390 pub value: String,
1391}
1392
1393pub fn item_size(item: &Item) -> usize {
1395 item.iter()
1396 .map(|(name, value)| name.len() + value.size())
1397 .sum()
1398}
1399
1400pub(crate) fn vector_billed_bytes(dimensions: u32) -> usize {
1404 4 * dimensions as usize
1405}
1406
1407pub(crate) fn billed_item_size(item: &Item, vector_indexes: &[VectorIndex]) -> usize {
1424 if vector_indexes.is_empty() {
1425 return item_size(item);
1426 }
1427 item.iter()
1428 .map(|(name, value)| {
1429 let billed = vector_indexes
1430 .iter()
1431 .filter(|vix| vix.vector_attribute.attribute_name == *name)
1432 .find(|vix| vector_f32_values(value, vix.dimensions).is_some())
1433 .map(|vix| vector_billed_bytes(vix.dimensions));
1434 name.len() + billed.unwrap_or_else(|| value.size())
1435 })
1436 .sum()
1437}
1438
1439pub const MAX_ITEM_SIZE: usize = 400 * 1024;
1441
1442#[derive(Debug, Clone, Serialize, Deserialize)]
1445pub struct ItemCollectionMetrics {
1446 #[serde(rename = "ItemCollectionKey")]
1447 pub item_collection_key: HashMap<String, AttributeValue>,
1448 #[serde(rename = "SizeEstimateRangeGB")]
1449 pub size_estimate_range_gb: Vec<f64>,
1450}
1451
1452#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1454pub struct ConsumedCapacity {
1455 #[serde(rename = "TableName")]
1456 pub table_name: String,
1457 #[serde(rename = "CapacityUnits")]
1458 pub capacity_units: f64,
1459 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
1460 pub read_capacity_units: Option<f64>,
1461 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
1462 pub write_capacity_units: Option<f64>,
1463 #[serde(rename = "Table", skip_serializing_if = "Option::is_none")]
1464 pub table: Option<CapacityDetail>,
1465 #[serde(
1466 rename = "GlobalSecondaryIndexes",
1467 skip_serializing_if = "Option::is_none"
1468 )]
1469 pub global_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
1470 #[serde(
1471 rename = "LocalSecondaryIndexes",
1472 skip_serializing_if = "Option::is_none"
1473 )]
1474 pub local_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
1475 #[serde(rename = "VectorIndexes", skip_serializing_if = "Option::is_none")]
1479 pub vector_indexes: Option<HashMap<String, VectorCapacityDetail>>,
1480}
1481
1482#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1484pub struct CapacityDetail {
1485 #[serde(rename = "CapacityUnits")]
1486 pub capacity_units: f64,
1487 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
1488 pub read_capacity_units: Option<f64>,
1489 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
1490 pub write_capacity_units: Option<f64>,
1491}
1492
1493#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1500pub struct VectorCapacityDetail {
1501 #[serde(rename = "VectorWriteRequestBytes")]
1502 pub vector_write_request_bytes: f64,
1503}
1504
1505#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1513pub struct VectorSearchCapacity {
1514 #[serde(rename = "VectorSearchRequestBytes")]
1515 pub vector_search_request_bytes: f64,
1516}
1517
1518pub const MIN_VECTOR_REQUEST_BYTES: f64 = 1024.0;
1532
1533pub fn vector_request_bytes(bytes: usize) -> f64 {
1535 (bytes as f64).max(MIN_VECTOR_REQUEST_BYTES)
1536}
1537
1538pub const TRANSACTIONAL_CAPACITY_FACTOR: f64 = 2.0;
1543
1544pub fn write_capacity_units(item_size_bytes: usize) -> f64 {
1546 ((item_size_bytes as f64) / 1024.0).ceil().max(1.0)
1547}
1548
1549pub fn table_write_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
1556 let old = old_size.map(write_capacity_units);
1557 let new = new_size.map(write_capacity_units);
1558 match (old, new) {
1559 (Some(old), Some(new)) => old.max(new),
1560 (Some(units), None) | (None, Some(units)) => units,
1561 (None, None) => write_capacity_units(0),
1564 }
1565}
1566
1567pub fn table_read_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
1575 let larger = old_size.unwrap_or(0).max(new_size.unwrap_or(0));
1576 read_capacity_units(larger)
1577}
1578
1579pub fn read_capacity_units(item_size_bytes: usize) -> f64 {
1583 ((item_size_bytes as f64) / 4096.0).ceil().max(1.0)
1584}
1585
1586pub fn read_capacity_units_with_consistency(item_size_bytes: usize, consistent: bool) -> f64 {
1591 let strongly = read_capacity_units(item_size_bytes);
1592 if consistent { strongly } else { strongly / 2.0 }
1593}
1594
1595pub fn capacity_wanted(mode: Option<&str>) -> bool {
1603 matches!(mode, Some("TOTAL") | Some("INDEXES"))
1604}
1605
1606pub fn vector_capacity_wanted(mode: Option<&str>) -> bool {
1616 matches!(mode, Some("INDEXES"))
1617}
1618
1619pub fn consumed_capacity(
1621 table_name: &str,
1622 capacity_units: f64,
1623 mode: &Option<String>,
1624) -> Option<ConsumedCapacity> {
1625 let mode = mode.as_deref().unwrap_or("NONE");
1626 match mode {
1627 "TOTAL" => Some(ConsumedCapacity {
1628 table_name: table_name.to_string(),
1629 capacity_units,
1630 table: None,
1631 global_secondary_indexes: None,
1632 local_secondary_indexes: None,
1633 ..Default::default()
1634 }),
1635 "INDEXES" => Some(ConsumedCapacity {
1636 table_name: table_name.to_string(),
1637 capacity_units,
1638 table: Some(CapacityDetail {
1639 capacity_units,
1640 ..Default::default()
1641 }),
1642 global_secondary_indexes: None,
1643 local_secondary_indexes: None,
1644 ..Default::default()
1645 }),
1646 _ => None,
1647 }
1648}
1649
1650pub fn consumed_capacity_with_indexes(
1652 table_name: &str,
1653 table_units: f64,
1654 gsi_units: &HashMap<String, f64>,
1655 mode: &Option<String>,
1656) -> Option<ConsumedCapacity> {
1657 consumed_capacity_with_secondary_indexes(
1658 table_name,
1659 table_units,
1660 gsi_units,
1661 &HashMap::new(),
1662 mode,
1663 )
1664}
1665
1666pub fn consumed_capacity_with_secondary_indexes(
1668 table_name: &str,
1669 table_units: f64,
1670 gsi_units: &HashMap<String, f64>,
1671 lsi_units: &HashMap<String, f64>,
1672 mode: &Option<String>,
1673) -> Option<ConsumedCapacity> {
1674 secondary_index_capacity(
1675 table_name,
1676 table_units,
1677 gsi_units,
1678 lsi_units,
1679 mode,
1680 CapacityAxis::None,
1681 )
1682}
1683
1684#[derive(Clone, Copy, PartialEq)]
1692enum CapacityAxis {
1693 None,
1694 Write,
1695}
1696
1697impl CapacityAxis {
1698 fn write_units(self, units: f64) -> Option<f64> {
1700 match self {
1701 Self::None => None,
1702 Self::Write => Some(units),
1703 }
1704 }
1705}
1706
1707fn secondary_index_capacity(
1710 table_name: &str,
1711 table_units: f64,
1712 gsi_units: &HashMap<String, f64>,
1713 lsi_units: &HashMap<String, f64>,
1714 mode: &Option<String>,
1715 axis: CapacityAxis,
1716) -> Option<ConsumedCapacity> {
1717 let total = table_units + gsi_units.values().sum::<f64>() + lsi_units.values().sum::<f64>();
1718
1719 match mode.as_deref().unwrap_or("NONE") {
1720 "INDEXES" => Some(ConsumedCapacity {
1721 table_name: table_name.to_string(),
1722 capacity_units: total,
1723 write_capacity_units: axis.write_units(total),
1724 table: Some(CapacityDetail {
1725 capacity_units: table_units,
1726 write_capacity_units: axis.write_units(table_units),
1727 ..Default::default()
1728 }),
1729 global_secondary_indexes: capacity_detail_map(gsi_units, axis),
1730 local_secondary_indexes: capacity_detail_map(lsi_units, axis),
1731 ..Default::default()
1732 }),
1733 "TOTAL" => Some(ConsumedCapacity {
1734 table_name: table_name.to_string(),
1735 capacity_units: total,
1736 write_capacity_units: axis.write_units(total),
1737 ..Default::default()
1738 }),
1739 _ => None,
1740 }
1741}
1742
1743pub fn consumed_capacity_with_vector_indexes(
1751 table_name: &str,
1752 table_units: f64,
1753 gsi_units: &HashMap<String, f64>,
1754 lsi_units: &HashMap<String, f64>,
1755 vector_bytes: &HashMap<String, f64>,
1756 mode: &Option<String>,
1757) -> Option<ConsumedCapacity> {
1758 attach_vector_index_capacity(
1759 consumed_capacity_with_secondary_indexes(
1760 table_name,
1761 table_units,
1762 gsi_units,
1763 lsi_units,
1764 mode,
1765 ),
1766 vector_bytes,
1767 mode,
1768 )
1769}
1770
1771pub(crate) fn attach_vector_index_capacity(
1780 capacity: Option<ConsumedCapacity>,
1781 vector_bytes: &HashMap<String, f64>,
1782 mode: &Option<String>,
1783) -> Option<ConsumedCapacity> {
1784 let mut capacity = capacity?;
1785 if vector_bytes.is_empty() || !vector_capacity_wanted(mode.as_deref()) {
1786 return Some(capacity);
1787 }
1788 capacity.vector_indexes = Some(
1789 vector_bytes
1790 .iter()
1791 .map(|(name, bytes)| {
1792 (
1793 name.clone(),
1794 VectorCapacityDetail {
1795 vector_write_request_bytes: *bytes,
1796 },
1797 )
1798 })
1799 .collect(),
1800 );
1801 Some(capacity)
1802}
1803
1804pub fn transactional_read_capacity(
1809 table_name: &str,
1810 units: f64,
1811 mode: &Option<String>,
1812) -> Option<ConsumedCapacity> {
1813 match mode.as_deref().unwrap_or("NONE") {
1814 "TOTAL" => Some(ConsumedCapacity {
1815 table_name: table_name.to_string(),
1816 capacity_units: units,
1817 read_capacity_units: Some(units),
1818 ..Default::default()
1819 }),
1820 "INDEXES" => Some(ConsumedCapacity {
1821 table_name: table_name.to_string(),
1822 capacity_units: units,
1823 read_capacity_units: Some(units),
1824 table: Some(CapacityDetail {
1825 capacity_units: units,
1826 read_capacity_units: Some(units),
1827 ..Default::default()
1828 }),
1829 ..Default::default()
1830 }),
1831 _ => None,
1832 }
1833}
1834
1835pub fn build_transactional_capacity(
1842 table_units: &HashMap<String, f64>,
1843 mode: &Option<String>,
1844 builder: fn(&str, f64, &Option<String>) -> Option<ConsumedCapacity>,
1845) -> Option<Vec<ConsumedCapacity>> {
1846 if !capacity_wanted(mode.as_deref()) {
1847 return None;
1848 }
1849
1850 let mut tables: Vec<&String> = table_units.keys().collect();
1855 tables.sort();
1856
1857 Some(
1858 tables
1859 .into_iter()
1860 .filter_map(|table| builder(table, *table_units.get(table)?, mode))
1861 .collect(),
1862 )
1863}
1864
1865pub fn transactional_write_capacity_with_indexes(
1877 table_name: &str,
1878 table_units: f64,
1879 gsi_units: &HashMap<String, f64>,
1880 lsi_units: &HashMap<String, f64>,
1881 mode: &Option<String>,
1882) -> Option<ConsumedCapacity> {
1883 secondary_index_capacity(
1884 table_name,
1885 table_units,
1886 gsi_units,
1887 lsi_units,
1888 mode,
1889 CapacityAxis::Write,
1890 )
1891}
1892
1893fn capacity_detail_map(
1896 units: &HashMap<String, f64>,
1897 axis: CapacityAxis,
1898) -> Option<HashMap<String, CapacityDetail>> {
1899 if units.is_empty() {
1900 return None;
1901 }
1902 Some(
1903 units
1904 .iter()
1905 .map(|(name, &u)| {
1906 (
1907 name.clone(),
1908 CapacityDetail {
1909 capacity_units: u,
1910 write_capacity_units: axis.write_units(u),
1911 ..Default::default()
1912 },
1913 )
1914 })
1915 .collect(),
1916 )
1917}
1918
1919pub fn transactional_write_capacity(
1924 table_name: &str,
1925 units: f64,
1926 mode: &Option<String>,
1927) -> Option<ConsumedCapacity> {
1928 match mode.as_deref().unwrap_or("NONE") {
1929 "TOTAL" => Some(ConsumedCapacity {
1930 table_name: table_name.to_string(),
1931 capacity_units: units,
1932 write_capacity_units: Some(units),
1933 ..Default::default()
1934 }),
1935 "INDEXES" => Some(ConsumedCapacity {
1936 table_name: table_name.to_string(),
1937 capacity_units: units,
1938 write_capacity_units: Some(units),
1939 table: Some(CapacityDetail {
1940 capacity_units: units,
1941 write_capacity_units: Some(units),
1942 ..Default::default()
1943 }),
1944 ..Default::default()
1945 }),
1946 _ => None,
1947 }
1948}
1949
1950#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1952pub struct KeySchemaElement {
1953 #[serde(rename = "AttributeName", alias = "attribute_name")]
1954 pub attribute_name: String,
1955 #[serde(rename = "KeyType", alias = "key_type")]
1956 pub key_type: KeyType,
1957}
1958
1959#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1961pub enum KeyType {
1962 #[default]
1963 HASH,
1964 RANGE,
1965}
1966
1967#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1969pub struct AttributeDefinition {
1970 #[serde(rename = "AttributeName", alias = "attribute_name")]
1971 pub attribute_name: String,
1972 #[serde(rename = "AttributeType", alias = "attribute_type")]
1973 pub attribute_type: ScalarAttributeType,
1974}
1975
1976#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1978pub enum ScalarAttributeType {
1979 #[default]
1980 S,
1981 N,
1982 B,
1983}
1984
1985#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1987pub struct Projection {
1988 #[serde(
1989 rename = "ProjectionType",
1990 alias = "projection_type",
1991 default,
1992 skip_serializing_if = "Option::is_none"
1993 )]
1994 pub projection_type: Option<ProjectionType>,
1995 #[serde(
1996 rename = "NonKeyAttributes",
1997 alias = "non_key_attributes",
1998 skip_serializing_if = "Option::is_none"
1999 )]
2000 pub non_key_attributes: Option<Vec<String>>,
2001}
2002
2003#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2005#[allow(non_camel_case_types)]
2006pub enum ProjectionType {
2007 #[default]
2008 ALL,
2009 KEYS_ONLY,
2010 INCLUDE,
2011}
2012
2013#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2015pub struct GlobalSecondaryIndex {
2016 #[serde(rename = "IndexName", alias = "index_name")]
2017 pub index_name: String,
2018 #[serde(rename = "KeySchema", alias = "key_schema")]
2019 pub key_schema: Vec<KeySchemaElement>,
2020 #[serde(rename = "Projection", alias = "projection")]
2021 pub projection: Projection,
2022 #[serde(
2023 rename = "ProvisionedThroughput",
2024 alias = "provisioned_throughput",
2025 skip_serializing_if = "Option::is_none"
2026 )]
2027 pub provisioned_throughput: Option<ProvisionedThroughput>,
2028}
2029
2030#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2032pub struct LocalSecondaryIndex {
2033 #[serde(rename = "IndexName", alias = "index_name")]
2034 pub index_name: String,
2035 #[serde(rename = "KeySchema", alias = "key_schema")]
2036 pub key_schema: Vec<KeySchemaElement>,
2037 #[serde(rename = "Projection", alias = "projection")]
2038 pub projection: Projection,
2039}
2040
2041#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2044pub struct VectorAttributeDefinition {
2045 #[serde(rename = "AttributeName", alias = "attribute_name")]
2046 pub attribute_name: String,
2047}
2048
2049#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2052pub struct SearchSchemaElement {
2053 #[serde(rename = "AttributeName", alias = "attribute_name")]
2054 pub attribute_name: String,
2055 #[serde(
2056 rename = "SearchSchemaElementType",
2057 alias = "search_schema_element_type"
2058 )]
2059 pub search_schema_element_type: String,
2060}
2061
2062#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2067pub struct VectorIndex {
2068 #[serde(rename = "IndexName", alias = "index_name")]
2069 pub index_name: String,
2070 #[serde(rename = "VectorAttribute", alias = "vector_attribute")]
2071 pub vector_attribute: VectorAttributeDefinition,
2072 #[serde(
2073 rename = "SearchSchema",
2074 alias = "search_schema",
2075 default,
2076 skip_serializing_if = "Option::is_none"
2077 )]
2078 pub search_schema: Option<Vec<SearchSchemaElement>>,
2079 #[serde(rename = "Projection", alias = "projection")]
2080 pub projection: Projection,
2081 #[serde(rename = "Dimensions", alias = "dimensions")]
2082 pub dimensions: u32,
2083 #[serde(rename = "DistanceFunction", alias = "distance_function")]
2084 pub distance_function: String,
2085}
2086
2087#[derive(Debug, Clone, PartialEq)]
2092pub enum VectorValueError {
2093 NotAList,
2095 WrongDimensions {
2097 actual: usize,
2099 },
2100 ElementNotANumber {
2102 position: usize,
2104 actual: &'static str,
2106 },
2107 ElementOutOfRange {
2111 position: usize,
2113 value: String,
2115 },
2116}
2117
2118pub fn check_vector_f32_values(
2128 value: &AttributeValue,
2129 dimensions: u32,
2130) -> std::result::Result<Vec<f32>, VectorValueError> {
2131 let AttributeValue::L(elems) = value else {
2132 return Err(VectorValueError::NotAList);
2133 };
2134 if elems.len() != dimensions as usize {
2135 return Err(VectorValueError::WrongDimensions {
2136 actual: elems.len(),
2137 });
2138 }
2139 let mut out = Vec::with_capacity(elems.len());
2140 for (position, elem) in elems.iter().enumerate() {
2141 let AttributeValue::N(n) = elem else {
2142 return Err(VectorValueError::ElementNotANumber {
2143 position,
2144 actual: elem.type_name(),
2145 });
2146 };
2147 match n.parse::<f32>() {
2150 Ok(v) if v.is_finite() => out.push(v),
2151 _ => {
2152 return Err(VectorValueError::ElementOutOfRange {
2153 position,
2154 value: n.clone(),
2155 });
2156 }
2157 }
2158 }
2159 Ok(out)
2160}
2161
2162pub fn vector_f32_values(value: &AttributeValue, dimensions: u32) -> Option<Vec<f32>> {
2167 check_vector_f32_values(value, dimensions).ok()
2168}
2169
2170pub fn f32_number_string(v: f32) -> String {
2175 serde_json::to_string(&v).unwrap_or_else(|_| "0.0".to_string())
2176}
2177
2178#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2180pub struct ProvisionedThroughput {
2181 #[serde(rename = "ReadCapacityUnits", alias = "read_capacity_units", default)]
2182 pub read_capacity_units: Option<i64>,
2183 #[serde(rename = "WriteCapacityUnits", alias = "write_capacity_units", default)]
2184 pub write_capacity_units: Option<i64>,
2185}
2186
2187#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2189pub struct OnDemandThroughput {
2190 #[serde(
2191 rename = "MaxReadRequestUnits",
2192 alias = "max_read_request_units",
2193 default,
2194 skip_serializing_if = "Option::is_none"
2195 )]
2196 pub max_read_request_units: Option<i64>,
2197 #[serde(
2198 rename = "MaxWriteRequestUnits",
2199 alias = "max_write_request_units",
2200 default,
2201 skip_serializing_if = "Option::is_none"
2202 )]
2203 pub max_write_request_units: Option<i64>,
2204}
2205
2206#[derive(Debug, Clone, PartialEq)]
2212pub struct ConversionError {
2213 pub expected: &'static str,
2215 pub actual: &'static str,
2217}
2218
2219impl fmt::Display for ConversionError {
2220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2221 write!(f, "expected {}, got {}", self.expected, self.actual)
2222 }
2223}
2224
2225impl std::error::Error for ConversionError {}
2226
2227impl From<String> for AttributeValue {
2230 fn from(value: String) -> Self {
2231 AttributeValue::S(value)
2232 }
2233}
2234
2235impl From<&str> for AttributeValue {
2236 fn from(value: &str) -> Self {
2237 AttributeValue::S(value.to_string())
2238 }
2239}
2240
2241impl From<bool> for AttributeValue {
2242 fn from(value: bool) -> Self {
2243 AttributeValue::BOOL(value)
2244 }
2245}
2246
2247impl From<Vec<u8>> for AttributeValue {
2248 fn from(value: Vec<u8>) -> Self {
2249 AttributeValue::B(value)
2250 }
2251}
2252
2253impl From<&[u8]> for AttributeValue {
2254 fn from(value: &[u8]) -> Self {
2255 AttributeValue::B(value.to_vec())
2256 }
2257}
2258
2259macro_rules! impl_from_integer {
2261 ($($t:ty),+) => {
2262 $(
2263 impl From<$t> for AttributeValue {
2264 fn from(value: $t) -> Self {
2265 AttributeValue::N(value.to_string())
2266 }
2267 }
2268 )+
2269 };
2270}
2271
2272impl_from_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
2273
2274impl From<HashMap<String, AttributeValue>> for AttributeValue {
2276 fn from(value: HashMap<String, AttributeValue>) -> Self {
2277 AttributeValue::M(value)
2278 }
2279}
2280
2281impl From<Vec<AttributeValue>> for AttributeValue {
2282 fn from(value: Vec<AttributeValue>) -> Self {
2283 AttributeValue::L(value)
2284 }
2285}
2286
2287impl From<HashSet<String>> for AttributeValue {
2288 fn from(value: HashSet<String>) -> Self {
2289 AttributeValue::SS(value.into_iter().collect())
2290 }
2291}
2292
2293impl From<BTreeSet<String>> for AttributeValue {
2294 fn from(value: BTreeSet<String>) -> Self {
2295 AttributeValue::SS(value.into_iter().collect())
2296 }
2297}
2298
2299impl TryFrom<f64> for AttributeValue {
2302 type Error = ConversionError;
2303
2304 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
2305 if value.is_finite() {
2306 Ok(AttributeValue::N(value.to_string()))
2307 } else {
2308 Err(ConversionError {
2309 expected: "finite f64",
2310 actual: "NaN or Infinity",
2311 })
2312 }
2313 }
2314}
2315
2316impl TryFrom<f32> for AttributeValue {
2317 type Error = ConversionError;
2318
2319 fn try_from(value: f32) -> std::result::Result<Self, Self::Error> {
2320 if value.is_finite() {
2321 Ok(AttributeValue::N(value.to_string()))
2322 } else {
2323 Err(ConversionError {
2324 expected: "finite f32",
2325 actual: "NaN or Infinity",
2326 })
2327 }
2328 }
2329}
2330
2331impl TryFrom<AttributeValue> for String {
2334 type Error = ConversionError;
2335
2336 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2337 match value {
2338 AttributeValue::S(s) => Ok(s),
2339 other => Err(ConversionError {
2340 expected: "S",
2341 actual: other.type_name(),
2342 }),
2343 }
2344 }
2345}
2346
2347impl TryFrom<AttributeValue> for bool {
2348 type Error = ConversionError;
2349
2350 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2351 match value {
2352 AttributeValue::BOOL(b) => Ok(b),
2353 other => Err(ConversionError {
2354 expected: "BOOL",
2355 actual: other.type_name(),
2356 }),
2357 }
2358 }
2359}
2360
2361impl TryFrom<AttributeValue> for Vec<u8> {
2362 type Error = ConversionError;
2363
2364 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2365 match value {
2366 AttributeValue::B(b) => Ok(b),
2367 other => Err(ConversionError {
2368 expected: "B",
2369 actual: other.type_name(),
2370 }),
2371 }
2372 }
2373}
2374
2375macro_rules! impl_try_from_av_integer {
2376 ($($t:ty),+) => {
2377 $(
2378 impl TryFrom<AttributeValue> for $t {
2379 type Error = ConversionError;
2380
2381 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2382 match value {
2383 AttributeValue::N(n) => n.parse::<$t>().map_err(|_| ConversionError {
2384 expected: stringify!($t),
2385 actual: "N (parse failed)",
2386 }),
2387 other => Err(ConversionError {
2388 expected: "N",
2389 actual: other.type_name(),
2390 }),
2391 }
2392 }
2393 }
2394 )+
2395 };
2396}
2397
2398impl_try_from_av_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
2399
2400impl TryFrom<AttributeValue> for f64 {
2401 type Error = ConversionError;
2402
2403 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2404 match value {
2405 AttributeValue::N(n) => n.parse::<f64>().map_err(|_| ConversionError {
2406 expected: "f64",
2407 actual: "N (parse failed)",
2408 }),
2409 other => Err(ConversionError {
2410 expected: "N",
2411 actual: other.type_name(),
2412 }),
2413 }
2414 }
2415}
2416
2417impl TryFrom<AttributeValue> for f32 {
2418 type Error = ConversionError;
2419
2420 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2421 match value {
2422 AttributeValue::N(n) => n.parse::<f32>().map_err(|_| ConversionError {
2423 expected: "f32",
2424 actual: "N (parse failed)",
2425 }),
2426 other => Err(ConversionError {
2427 expected: "N",
2428 actual: other.type_name(),
2429 }),
2430 }
2431 }
2432}
2433
2434impl TryFrom<AttributeValue> for HashMap<String, AttributeValue> {
2435 type Error = ConversionError;
2436
2437 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2438 match value {
2439 AttributeValue::M(m) => Ok(m),
2440 other => Err(ConversionError {
2441 expected: "M",
2442 actual: other.type_name(),
2443 }),
2444 }
2445 }
2446}
2447
2448impl TryFrom<AttributeValue> for Vec<AttributeValue> {
2449 type Error = ConversionError;
2450
2451 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2452 match value {
2453 AttributeValue::L(l) => Ok(l),
2454 other => Err(ConversionError {
2455 expected: "L",
2456 actual: other.type_name(),
2457 }),
2458 }
2459 }
2460}
2461
2462impl TryFrom<AttributeValue> for Vec<String> {
2463 type Error = ConversionError;
2464
2465 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2466 match value {
2467 AttributeValue::SS(ss) => Ok(ss),
2468 AttributeValue::L(l) => {
2469 l.into_iter()
2471 .map(|av| match av {
2472 AttributeValue::S(s) => Ok(s),
2473 other => Err(ConversionError {
2474 expected: "S (within L)",
2475 actual: other.type_name(),
2476 }),
2477 })
2478 .collect()
2479 }
2480 other => Err(ConversionError {
2481 expected: "SS or L",
2482 actual: other.type_name(),
2483 }),
2484 }
2485 }
2486}
2487
2488#[cfg(test)]
2489mod tests {
2490 use super::*;
2491
2492 struct SeesLayer {
2495 seen: Vec<crate::validation::ValueLayer>,
2496 waited: bool,
2497 }
2498
2499 impl std::future::Future for SeesLayer {
2500 type Output = Vec<crate::validation::ValueLayer>;
2501 fn poll(
2502 mut self: std::pin::Pin<&mut Self>,
2503 cx: &mut std::task::Context<'_>,
2504 ) -> std::task::Poll<Self::Output> {
2505 self.seen.push(current_value_layer());
2506 if self.waited {
2507 std::task::Poll::Ready(std::mem::take(&mut self.seen))
2508 } else {
2509 self.waited = true;
2510 cx.waker().wake_by_ref();
2511 std::task::Poll::Pending
2512 }
2513 }
2514 }
2515
2516 fn sees_layer() -> SeesLayer {
2517 SeesLayer {
2518 seen: Vec::new(),
2519 waited: false,
2520 }
2521 }
2522
2523 #[test]
2527 fn the_value_layer_follows_a_future_across_its_polls_and_no_further() {
2528 use crate::validation::ValueLayer::{Current, Older};
2529 use std::future::Future;
2530 let mut cx = std::task::Context::from_waker(std::task::Waker::noop());
2531 let mut older = Box::pin(with_value_layer_async(Older, sees_layer()));
2532 let mut current = Box::pin(with_value_layer_async(Current, sees_layer()));
2533
2534 assert_eq!(current_value_layer(), Current);
2535 assert!(older.as_mut().poll(&mut cx).is_pending());
2536 assert_eq!(current_value_layer(), Current);
2538 assert!(current.as_mut().poll(&mut cx).is_pending());
2541 assert_eq!(current_value_layer(), Current);
2542 assert_eq!(
2543 with_value_layer(Older, || {
2544 let mut neighbour = Box::pin(sees_layer());
2545 let _ = neighbour.as_mut().poll(&mut cx);
2546 current_value_layer()
2547 }),
2548 Older
2549 );
2550 match older.as_mut().poll(&mut cx) {
2551 std::task::Poll::Ready(seen) => assert_eq!(seen, vec![Older, Older]),
2552 std::task::Poll::Pending => panic!("the future was ready on its second poll"),
2553 }
2554 match current.as_mut().poll(&mut cx) {
2555 std::task::Poll::Ready(seen) => assert_eq!(seen, vec![Current, Current]),
2556 std::task::Poll::Pending => panic!("the future was ready on its second poll"),
2557 }
2558 assert_eq!(current_value_layer(), Current);
2559 }
2560
2561 #[test]
2562 fn test_serialize_string() {
2563 let val = AttributeValue::S("hello".to_string());
2564 let json = serde_json::to_string(&val).unwrap();
2565 assert_eq!(json, r#"{"S":"hello"}"#);
2566 }
2567
2568 #[test]
2569 fn test_serialize_number() {
2570 let val = AttributeValue::N("42".to_string());
2571 let json = serde_json::to_string(&val).unwrap();
2572 assert_eq!(json, r#"{"N":"42"}"#);
2573 }
2574
2575 #[test]
2576 fn test_serialize_binary() {
2577 let val = AttributeValue::B(vec![1, 2, 3]);
2578 let json = serde_json::to_string(&val).unwrap();
2579 assert_eq!(json, r#"{"B":"AQID"}"#);
2580 }
2581
2582 #[test]
2583 fn test_serialize_bool() {
2584 let val = AttributeValue::BOOL(true);
2585 let json = serde_json::to_string(&val).unwrap();
2586 assert_eq!(json, r#"{"BOOL":true}"#);
2587 }
2588
2589 #[test]
2590 fn test_serialize_null() {
2591 let val = AttributeValue::NULL(true);
2592 let json = serde_json::to_string(&val).unwrap();
2593 assert_eq!(json, r#"{"NULL":true}"#);
2594 }
2595
2596 #[test]
2597 fn test_deserialize_null_true() {
2598 let val: AttributeValue = serde_json::from_str(r#"{"NULL":true}"#).unwrap();
2599 assert_eq!(val, AttributeValue::NULL(true));
2600 }
2601
2602 #[test]
2603 fn test_deserialize_null_false_rejected() {
2604 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":false}"#).unwrap_err();
2607 assert!(
2608 err.to_string().contains("must have the value of true"),
2609 "unexpected error: {err}"
2610 );
2611 }
2612
2613 #[test]
2614 fn test_deserialize_null_non_boolean_rejected() {
2615 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":"no"}"#).unwrap_err();
2617 assert!(
2618 err.to_string().contains("must have the value of true"),
2619 "unexpected error: {err}"
2620 );
2621 }
2622
2623 #[test]
2624 fn test_serialize_string_set() {
2625 let val = AttributeValue::SS(vec!["a".to_string(), "b".to_string()]);
2626 let json = serde_json::to_string(&val).unwrap();
2627 assert_eq!(json, r#"{"SS":["a","b"]}"#);
2628 }
2629
2630 #[test]
2631 fn test_serialize_list() {
2632 let val = AttributeValue::L(vec![
2633 AttributeValue::S("hello".to_string()),
2634 AttributeValue::N("42".to_string()),
2635 ]);
2636 let json = serde_json::to_string(&val).unwrap();
2637 assert_eq!(json, r#"{"L":[{"S":"hello"},{"N":"42"}]}"#);
2638 }
2639
2640 #[test]
2641 fn test_serialize_map() {
2642 let mut m = HashMap::new();
2643 m.insert("key".to_string(), AttributeValue::S("value".to_string()));
2644 let val = AttributeValue::M(m);
2645 let json = serde_json::to_string(&val).unwrap();
2646 assert_eq!(json, r#"{"M":{"key":{"S":"value"}}}"#);
2647 }
2648
2649 #[test]
2650 fn test_round_trip_all_types() {
2651 let values = vec![
2652 AttributeValue::S("hello".to_string()),
2653 AttributeValue::N("42.5".to_string()),
2654 AttributeValue::B(vec![0, 255, 128]),
2655 AttributeValue::BOOL(false),
2656 AttributeValue::NULL(true),
2657 AttributeValue::SS(vec!["x".to_string(), "y".to_string()]),
2658 AttributeValue::NS(vec!["1".to_string(), "2.5".to_string()]),
2659 AttributeValue::BS(vec![vec![1], vec![2, 3]]),
2660 AttributeValue::L(vec![
2661 AttributeValue::S("nested".to_string()),
2662 AttributeValue::N("99".to_string()),
2663 ]),
2664 ];
2665
2666 for val in values {
2667 let json = serde_json::to_string(&val).unwrap();
2668 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
2669 assert_eq!(val, deserialized, "Round-trip failed for {json}");
2670 }
2671 }
2672
2673 #[test]
2674 fn test_size_string() {
2675 let val = AttributeValue::S("hello".to_string());
2676 assert_eq!(val.size(), 5);
2677 }
2678
2679 #[test]
2680 fn test_size_number() {
2681 let val = AttributeValue::N("42".to_string());
2683 assert_eq!(val.size(), 2);
2684 }
2685
2686 #[test]
2687 fn test_size_number_matches_dynamodb() {
2688 let cases: &[(&str, usize)] = &[
2696 ("1", 2),
2698 ("12", 2),
2699 ("123", 3),
2700 ("1234", 3),
2701 ("12345", 4),
2702 ("123456", 4),
2703 ("1234567", 5),
2704 ("12345678", 5),
2705 ("123456789", 6),
2706 ("1234567891", 6),
2707 ("12345678912", 7),
2708 ("123456789123", 7),
2709 ("1234567890123456789", 11),
2710 ("12345678901234567890123456789012345678", 20),
2711 ("0042", 2),
2713 ("100", 2),
2714 ("1010", 3),
2715 ("0.0000001", 2),
2716 ("0", 1),
2717 ("1E125", 2),
2719 ("1E-100", 2),
2720 ("1.5", 3),
2723 ("1.2", 3),
2724 ("1.200", 3),
2725 ("1.234", 4),
2726 ("3.14159", 5),
2727 ("100.5", 4),
2728 ("0.15", 2),
2729 ("15", 2),
2730 ("-42", 3),
2732 ("-12345", 5),
2733 ("-1E125", 3),
2734 ("-0.15", 3),
2735 ("-0", 1),
2736 ];
2737 for (literal, expected) in cases {
2738 assert_eq!(
2739 AttributeValue::N((*literal).to_string()).size(),
2740 *expected,
2741 "size of N {literal:?}"
2742 );
2743 }
2744 }
2745
2746 #[test]
2747 fn test_size_number_survives_normalisation() {
2748 for literal in [
2752 "1E125",
2753 "1E-100",
2754 "0042",
2755 "1.200",
2756 "0.0000001",
2757 "100",
2758 "3.14159",
2759 "42",
2760 ] {
2761 let raw = AttributeValue::N(literal.to_string()).size();
2762 let normalised = AttributeValue::N(normalize_dynamo_number(literal)).size();
2763 assert_eq!(
2764 raw, normalised,
2765 "N {literal:?} changed size when normalised"
2766 );
2767 }
2768 }
2769
2770 #[test]
2771 fn test_size_bool() {
2772 assert_eq!(AttributeValue::BOOL(true).size(), 1);
2773 }
2774
2775 #[test]
2776 fn test_size_null() {
2777 assert_eq!(AttributeValue::NULL(true).size(), 1);
2778 }
2779
2780 #[test]
2781 fn test_key_string_s() {
2782 let val = AttributeValue::S("hello".to_string());
2783 assert_eq!(val.to_key_string(), Some("S:hello".to_string()));
2784 }
2785
2786 #[test]
2787 fn test_key_string_n() {
2788 let val = AttributeValue::N("42".to_string());
2789 let key = val.to_key_string().unwrap();
2790 assert!(key.starts_with("N:"));
2791 }
2792
2793 #[test]
2794 fn test_key_string_b() {
2795 let val = AttributeValue::B(vec![0xff, 0x00, 0xab]);
2796 assert_eq!(val.to_key_string(), Some("B:ff00ab".to_string()));
2797 }
2798
2799 #[test]
2800 fn test_key_string_non_key_type_returns_none() {
2801 assert_eq!(AttributeValue::BOOL(true).to_key_string(), None);
2802 assert_eq!(AttributeValue::L(vec![]).to_key_string(), None);
2803 }
2804
2805 #[test]
2807 fn test_number_sort_ordering() {
2808 let numbers = vec![
2809 "-1000", "-100", "-10", "-1", "-0.5", "-0.001", "0", "0.001", "0.5", "1", "10", "100",
2810 "1000",
2811 ];
2812 let encoded: Vec<String> = numbers
2813 .iter()
2814 .map(|n| normalize_number_for_sort(n))
2815 .collect();
2816
2817 for i in 0..encoded.len() - 1 {
2818 assert!(
2819 encoded[i] < encoded[i + 1],
2820 "Sort order broken: {} ({}) should be < {} ({})",
2821 numbers[i],
2822 encoded[i],
2823 numbers[i + 1],
2824 encoded[i + 1]
2825 );
2826 }
2827 }
2828
2829 #[test]
2830 fn test_number_sort_zero_variants() {
2831 let z1 = normalize_number_for_sort("0");
2832 let z2 = normalize_number_for_sort("-0");
2833 let z3 = normalize_number_for_sort("0.0");
2834 assert_eq!(z1, z2);
2835 assert_eq!(z2, z3);
2836 }
2837
2838 #[test]
2839 fn test_number_sort_decimals() {
2840 let a = normalize_number_for_sort("1.5");
2841 let b = normalize_number_for_sort("2.5");
2842 assert!(a < b);
2843
2844 let c = normalize_number_for_sort("0.001");
2845 let d = normalize_number_for_sort("0.01");
2846 assert!(c < d);
2847 }
2848
2849 #[test]
2850 fn test_number_sort_scientific() {
2851 let a = normalize_number_for_sort("1e10");
2852 let b = normalize_number_for_sort("1e11");
2853 assert!(a < b);
2854
2855 let c = normalize_number_for_sort("-1e11");
2856 let d = normalize_number_for_sort("-1e10");
2857 assert!(c < d);
2858 }
2859
2860 #[test]
2864 fn test_number_sort_out_of_range_sorts_at_the_extremes() {
2865 let ordered = [
2866 "-1e99999999999",
2867 "-9.9999999999999999999999999999999999999E125",
2868 "-1E-130",
2869 "-1e-99999999999",
2870 "0",
2871 "1e-99999999999",
2872 "1E-130",
2873 "9.9999999999999999999999999999999999999E125",
2874 "1e99999999999",
2875 ];
2876 for pair in ordered.windows(2) {
2877 let (a, b) = (pair[0], pair[1]);
2878 assert!(
2879 normalize_number_for_sort(a) < normalize_number_for_sort(b),
2880 "{a} should sort before {b}"
2881 );
2882 }
2883 }
2884
2885 #[test]
2888 fn test_split_exponent_reads_an_unparseable_exponent_as_zero() {
2889 assert_eq!(split_exponent("1e"), ("1", 0));
2890 assert_eq!(split_exponent("1e+"), ("1", 0));
2891 assert_eq!(split_exponent("1e99999999999999999999"), ("1", i64::MAX));
2892 assert_eq!(split_exponent("1e-99999999999999999999"), ("1", i64::MIN));
2893 }
2894
2895 #[test]
2900 fn test_validate_number_accepts_dynamodb_grammar() {
2901 for input in [
2902 "+5", "+1.5", "+0", "-0", "+0.0", "+1e2", "1e+2", "1.5E+3", "-7", "+.5", ".5", "5.",
2903 "00042", "1.23E10", "+1e-2", "1E-130", "+1E-130",
2904 ] {
2905 assert!(
2906 validate_dynamo_number(input).is_ok(),
2907 "expected {input} to validate, got {:?}",
2908 validate_dynamo_number(input)
2909 );
2910 }
2911 }
2912
2913 #[test]
2914 fn test_validate_number_rejects_malformed() {
2915 for input in [
2918 "+e2", "e2", "+1+2", "1+2", "+1.2.3", "1.2.3", "++5", "+-5", "-+5", "+", "-", "1e",
2919 "1e+", ".", "1.2e3.4", "0x5", "NaN", "Infinity", "1_000", " 5", "5 ", "1 5", "",
2920 ] {
2921 assert!(
2922 matches!(
2923 validate_dynamo_number(input),
2924 Err(crate::errors::DynoxideError::ValidationException(_))
2925 ),
2926 "expected {input:?} to be rejected with ValidationException, got {:?}",
2927 validate_dynamo_number(input)
2928 );
2929 }
2930 }
2931
2932 fn validation_message(input: &str) -> Option<String> {
2933 match validate_dynamo_number(input) {
2934 Ok(()) => None,
2935 Err(crate::errors::DynoxideError::ValidationException(m)) => Some(m),
2936 Err(e) => panic!("expected a ValidationException for {input:?}, got {e:?}"),
2937 }
2938 }
2939
2940 const OVERFLOW: &str =
2941 "Number overflow. Attempting to store a number with magnitude larger than supported range";
2942 const UNDERFLOW: &str = "Number underflow. Attempting to store a number with magnitude smaller than supported range";
2943
2944 #[test]
2948 fn test_validate_number_range_checks_exponents_beyond_i32() {
2949 for (input, expected) in [
2950 ("1e99999999999", OVERFLOW),
2951 ("-1e99999999999", OVERFLOW),
2952 ("1E+99999999999", OVERFLOW),
2953 ("1e-99999999999", UNDERFLOW),
2954 ("-1e-99999999999", UNDERFLOW),
2955 ("1e2147483648", OVERFLOW),
2956 ("1e-2147483649", UNDERFLOW),
2957 ("10e2147483647", OVERFLOW),
2959 ("0.01e-2147483648", UNDERFLOW),
2960 ("1e9223372036854775807", OVERFLOW),
2961 ("1e-9223372036854775808", UNDERFLOW),
2962 ("10e9223372036854775807", OVERFLOW),
2963 ("0.01e-9223372036854775808", UNDERFLOW),
2964 ] {
2965 assert_eq!(
2966 validation_message(input).as_deref(),
2967 Some(expected),
2968 "{input}"
2969 );
2970 }
2971 }
2972
2973 #[test]
2977 fn test_validate_number_rejects_exponent_beyond_i64_as_unconvertible() {
2978 for input in [
2979 "1e9223372036854775808",
2980 "1e+9223372036854775808",
2981 "1e-9223372036854775809",
2982 "0e9223372036854775808",
2983 ] {
2984 assert_eq!(
2985 validation_message(input),
2986 Some(format!(
2987 "The parameter cannot be converted to a numeric value: {input}"
2988 )),
2989 "{input}"
2990 );
2991 }
2992 }
2993
2994 #[test]
2998 fn test_validate_number_range_checks_zero_by_its_last_written_digit() {
2999 let zeros_130 = format!("0.{}", "0".repeat(130));
3000 let zeros_131 = format!("0.{}", "0".repeat(131));
3001 for (input, expected) in [
3002 ("0e125", None),
3003 ("0e126", Some(OVERFLOW)),
3004 ("0.0e126", None),
3005 ("0.0e127", Some(OVERFLOW)),
3006 ("0.00e127", None),
3007 ("00e126", Some(OVERFLOW)),
3008 ("00.00e126", None),
3009 ("-0e127", Some(OVERFLOW)),
3010 ("0e99999999999", Some(OVERFLOW)),
3011 ("0e-130", None),
3012 ("0e-131", Some(UNDERFLOW)),
3013 ("0.0e-129", None),
3014 ("0.0e-130", Some(UNDERFLOW)),
3015 ("0e-99999999999", Some(UNDERFLOW)),
3016 (zeros_130.as_str(), None),
3017 (zeros_131.as_str(), Some(UNDERFLOW)),
3018 ] {
3019 assert_eq!(validation_message(input).as_deref(), expected, "{input}");
3020 }
3021 }
3022
3023 #[test]
3024 fn test_normalize_number_matches_dynamodb() {
3025 for (input, stored) in [
3026 ("+5", "5"),
3027 ("+1.5", "1.5"),
3028 ("+0", "0"),
3029 ("-0", "0"),
3030 ("+0.0", "0"),
3031 ("+1e2", "100"),
3032 ("1e+2", "100"),
3033 ("1.5E+3", "1500"),
3034 ("-7", "-7"),
3035 ("+.5", "0.5"),
3036 (".5", "0.5"),
3037 ("5.", "5"),
3038 ("00042", "42"),
3039 ("1.23E10", "12300000000"),
3040 ("+1e-2", "0.01"),
3041 ] {
3042 assert_eq!(
3043 normalize_dynamo_number(input),
3044 stored,
3045 "{input} should normalise to {stored}"
3046 );
3047 }
3048 }
3049
3050 #[test]
3051 fn test_type_name() {
3052 assert_eq!(AttributeValue::S("".to_string()).type_name(), "S");
3053 assert_eq!(AttributeValue::N("0".to_string()).type_name(), "N");
3054 assert_eq!(AttributeValue::B(vec![]).type_name(), "B");
3055 assert_eq!(AttributeValue::BOOL(true).type_name(), "BOOL");
3056 assert_eq!(AttributeValue::NULL(true).type_name(), "NULL");
3057 assert_eq!(AttributeValue::SS(vec![]).type_name(), "SS");
3058 assert_eq!(AttributeValue::NS(vec![]).type_name(), "NS");
3059 assert_eq!(AttributeValue::BS(vec![]).type_name(), "BS");
3060 assert_eq!(AttributeValue::L(vec![]).type_name(), "L");
3061 assert_eq!(AttributeValue::M(HashMap::new()).type_name(), "M");
3062 }
3063}