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
1298pub(crate) fn key_value_from_key_string(key: &str) -> Option<AttributeValue> {
1303 let (kind, body) = key.split_once(':')?;
1304 match kind {
1305 "S" => Some(AttributeValue::S(body.to_string())),
1306 "B" => hex_decode(body).map(AttributeValue::B),
1307 "N" => number_from_sortable(body).map(AttributeValue::N),
1308 _ => None,
1309 }
1310}
1311
1312fn number_from_sortable(body: &str) -> Option<String> {
1315 if body.len() != 45 || !body.bytes().all(|b| b.is_ascii_digit()) {
1316 return None;
1317 }
1318 let (sign, rest) = body.split_at(1);
1319 let (exponent, mantissa) = rest.split_at(4);
1320 let exponent: i32 = exponent.parse().ok()?;
1321 let (negative, exponent, mantissa): (bool, i32, String) = match sign {
1322 "1" => return Some("0".to_string()),
1323 "2" => (false, exponent - 5000, mantissa.to_string()),
1324 "0" => (
1325 true,
1326 (9999 - exponent) - 5000,
1327 mantissa
1328 .bytes()
1329 .map(|b| (b'0' + (9 - (b - b'0'))) as char)
1330 .collect(),
1331 ),
1332 _ => return None,
1333 };
1334 let digits = mantissa.trim_end_matches('0');
1335 if digits.is_empty() {
1336 return Some("0".to_string());
1337 }
1338 Some(format!(
1339 "{}0.{digits}E{exponent}",
1340 if negative { "-" } else { "" }
1341 ))
1342}
1343
1344fn hex_decode(hex: &str) -> Option<Vec<u8>> {
1345 if !hex.len().is_multiple_of(2) {
1346 return None;
1347 }
1348 (0..hex.len())
1349 .step_by(2)
1350 .map(|i| u8::from_str_radix(hex.get(i..i + 2)?, 16).ok())
1351 .collect()
1352}
1353
1354fn hex_encode(bytes: &[u8]) -> String {
1356 let mut s = String::with_capacity(bytes.len() * 2);
1357 for &b in bytes {
1358 s.push_str(&format!("{b:02x}"));
1359 }
1360 s
1361}
1362
1363pub type Item = HashMap<String, AttributeValue>;
1369
1370#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1372pub struct SseSpecification {
1373 #[serde(rename = "Enabled", default)]
1374 pub enabled: Option<bool>,
1375 #[serde(rename = "SSEType", default)]
1376 pub sse_type: Option<String>,
1377 #[serde(rename = "KMSMasterKeyId", default)]
1378 pub kms_master_key_id: Option<String>,
1379}
1380
1381#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1383pub struct Tag {
1384 #[serde(rename = "Key")]
1385 pub key: String,
1386 #[serde(rename = "Value")]
1387 pub value: String,
1388}
1389
1390pub fn item_size(item: &Item) -> usize {
1392 item.iter()
1393 .map(|(name, value)| name.len() + value.size())
1394 .sum()
1395}
1396
1397pub(crate) fn vector_billed_bytes(dimensions: u32) -> usize {
1401 4 * dimensions as usize
1402}
1403
1404pub(crate) fn billed_item_size(item: &Item, vector_indexes: &[VectorIndex]) -> usize {
1419 if vector_indexes.is_empty() {
1420 return item_size(item);
1421 }
1422 item.iter()
1423 .map(|(name, value)| {
1424 let billed = vector_indexes
1425 .iter()
1426 .filter(|vix| vix.vector_attribute.attribute_name == *name)
1427 .find(|vix| vector_f32_values(value, vix.dimensions).is_some())
1428 .map(|vix| vector_billed_bytes(vix.dimensions));
1429 name.len() + billed.unwrap_or_else(|| value.size())
1430 })
1431 .sum()
1432}
1433
1434pub const MAX_ITEM_SIZE: usize = 400 * 1024;
1436
1437#[derive(Debug, Clone, Serialize, Deserialize)]
1440pub struct ItemCollectionMetrics {
1441 #[serde(rename = "ItemCollectionKey")]
1442 pub item_collection_key: HashMap<String, AttributeValue>,
1443 #[serde(rename = "SizeEstimateRangeGB")]
1444 pub size_estimate_range_gb: Vec<f64>,
1445}
1446
1447#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1449pub struct ConsumedCapacity {
1450 #[serde(rename = "TableName")]
1451 pub table_name: String,
1452 #[serde(rename = "CapacityUnits")]
1453 pub capacity_units: f64,
1454 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
1455 pub read_capacity_units: Option<f64>,
1456 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
1457 pub write_capacity_units: Option<f64>,
1458 #[serde(rename = "Table", skip_serializing_if = "Option::is_none")]
1459 pub table: Option<CapacityDetail>,
1460 #[serde(
1461 rename = "GlobalSecondaryIndexes",
1462 skip_serializing_if = "Option::is_none"
1463 )]
1464 pub global_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
1465 #[serde(
1466 rename = "LocalSecondaryIndexes",
1467 skip_serializing_if = "Option::is_none"
1468 )]
1469 pub local_secondary_indexes: Option<HashMap<String, CapacityDetail>>,
1470 #[serde(rename = "VectorIndexes", skip_serializing_if = "Option::is_none")]
1474 pub vector_indexes: Option<HashMap<String, VectorCapacityDetail>>,
1475}
1476
1477#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1479pub struct CapacityDetail {
1480 #[serde(rename = "CapacityUnits")]
1481 pub capacity_units: f64,
1482 #[serde(rename = "ReadCapacityUnits", skip_serializing_if = "Option::is_none")]
1483 pub read_capacity_units: Option<f64>,
1484 #[serde(rename = "WriteCapacityUnits", skip_serializing_if = "Option::is_none")]
1485 pub write_capacity_units: Option<f64>,
1486}
1487
1488#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1495pub struct VectorCapacityDetail {
1496 #[serde(rename = "VectorWriteRequestBytes")]
1497 pub vector_write_request_bytes: f64,
1498}
1499
1500#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1508pub struct VectorSearchCapacity {
1509 #[serde(rename = "VectorSearchRequestBytes")]
1510 pub vector_search_request_bytes: f64,
1511}
1512
1513pub const MIN_VECTOR_REQUEST_BYTES: f64 = 1024.0;
1527
1528pub fn vector_request_bytes(bytes: usize) -> f64 {
1530 (bytes as f64).max(MIN_VECTOR_REQUEST_BYTES)
1531}
1532
1533pub const TRANSACTIONAL_CAPACITY_FACTOR: f64 = 2.0;
1538
1539pub fn write_capacity_units(item_size_bytes: usize) -> f64 {
1541 ((item_size_bytes as f64) / 1024.0).ceil().max(1.0)
1542}
1543
1544pub fn table_write_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
1551 let old = old_size.map(write_capacity_units);
1552 let new = new_size.map(write_capacity_units);
1553 match (old, new) {
1554 (Some(old), Some(new)) => old.max(new),
1555 (Some(units), None) | (None, Some(units)) => units,
1556 (None, None) => write_capacity_units(0),
1559 }
1560}
1561
1562pub fn table_read_capacity_units(old_size: Option<usize>, new_size: Option<usize>) -> f64 {
1570 let larger = old_size.unwrap_or(0).max(new_size.unwrap_or(0));
1571 read_capacity_units(larger)
1572}
1573
1574pub fn read_capacity_units(item_size_bytes: usize) -> f64 {
1578 ((item_size_bytes as f64) / 4096.0).ceil().max(1.0)
1579}
1580
1581pub fn read_capacity_units_with_consistency(item_size_bytes: usize, consistent: bool) -> f64 {
1586 let strongly = read_capacity_units(item_size_bytes);
1587 if consistent { strongly } else { strongly / 2.0 }
1588}
1589
1590pub fn capacity_wanted(mode: Option<&str>) -> bool {
1598 matches!(mode, Some("TOTAL") | Some("INDEXES"))
1599}
1600
1601pub fn vector_capacity_wanted(mode: Option<&str>) -> bool {
1611 matches!(mode, Some("INDEXES"))
1612}
1613
1614pub fn consumed_capacity(
1616 table_name: &str,
1617 capacity_units: f64,
1618 mode: &Option<String>,
1619) -> Option<ConsumedCapacity> {
1620 let mode = mode.as_deref().unwrap_or("NONE");
1621 match mode {
1622 "TOTAL" => Some(ConsumedCapacity {
1623 table_name: table_name.to_string(),
1624 capacity_units,
1625 table: None,
1626 global_secondary_indexes: None,
1627 local_secondary_indexes: None,
1628 ..Default::default()
1629 }),
1630 "INDEXES" => Some(ConsumedCapacity {
1631 table_name: table_name.to_string(),
1632 capacity_units,
1633 table: Some(CapacityDetail {
1634 capacity_units,
1635 ..Default::default()
1636 }),
1637 global_secondary_indexes: None,
1638 local_secondary_indexes: None,
1639 ..Default::default()
1640 }),
1641 _ => None,
1642 }
1643}
1644
1645pub fn consumed_capacity_with_indexes(
1647 table_name: &str,
1648 table_units: f64,
1649 gsi_units: &HashMap<String, f64>,
1650 mode: &Option<String>,
1651) -> Option<ConsumedCapacity> {
1652 consumed_capacity_with_secondary_indexes(
1653 table_name,
1654 table_units,
1655 gsi_units,
1656 &HashMap::new(),
1657 mode,
1658 )
1659}
1660
1661pub fn consumed_capacity_with_secondary_indexes(
1663 table_name: &str,
1664 table_units: f64,
1665 gsi_units: &HashMap<String, f64>,
1666 lsi_units: &HashMap<String, f64>,
1667 mode: &Option<String>,
1668) -> Option<ConsumedCapacity> {
1669 secondary_index_capacity(
1670 table_name,
1671 table_units,
1672 gsi_units,
1673 lsi_units,
1674 mode,
1675 CapacityAxis::None,
1676 )
1677}
1678
1679#[derive(Clone, Copy, PartialEq)]
1687enum CapacityAxis {
1688 None,
1689 Write,
1690}
1691
1692impl CapacityAxis {
1693 fn write_units(self, units: f64) -> Option<f64> {
1695 match self {
1696 Self::None => None,
1697 Self::Write => Some(units),
1698 }
1699 }
1700}
1701
1702fn secondary_index_capacity(
1705 table_name: &str,
1706 table_units: f64,
1707 gsi_units: &HashMap<String, f64>,
1708 lsi_units: &HashMap<String, f64>,
1709 mode: &Option<String>,
1710 axis: CapacityAxis,
1711) -> Option<ConsumedCapacity> {
1712 let total = table_units + gsi_units.values().sum::<f64>() + lsi_units.values().sum::<f64>();
1713
1714 match mode.as_deref().unwrap_or("NONE") {
1715 "INDEXES" => Some(ConsumedCapacity {
1716 table_name: table_name.to_string(),
1717 capacity_units: total,
1718 write_capacity_units: axis.write_units(total),
1719 table: Some(CapacityDetail {
1720 capacity_units: table_units,
1721 write_capacity_units: axis.write_units(table_units),
1722 ..Default::default()
1723 }),
1724 global_secondary_indexes: capacity_detail_map(gsi_units, axis),
1725 local_secondary_indexes: capacity_detail_map(lsi_units, axis),
1726 ..Default::default()
1727 }),
1728 "TOTAL" => Some(ConsumedCapacity {
1729 table_name: table_name.to_string(),
1730 capacity_units: total,
1731 write_capacity_units: axis.write_units(total),
1732 ..Default::default()
1733 }),
1734 _ => None,
1735 }
1736}
1737
1738pub fn consumed_capacity_with_vector_indexes(
1746 table_name: &str,
1747 table_units: f64,
1748 gsi_units: &HashMap<String, f64>,
1749 lsi_units: &HashMap<String, f64>,
1750 vector_bytes: &HashMap<String, f64>,
1751 mode: &Option<String>,
1752) -> Option<ConsumedCapacity> {
1753 attach_vector_index_capacity(
1754 consumed_capacity_with_secondary_indexes(
1755 table_name,
1756 table_units,
1757 gsi_units,
1758 lsi_units,
1759 mode,
1760 ),
1761 vector_bytes,
1762 mode,
1763 )
1764}
1765
1766pub(crate) fn attach_vector_index_capacity(
1775 capacity: Option<ConsumedCapacity>,
1776 vector_bytes: &HashMap<String, f64>,
1777 mode: &Option<String>,
1778) -> Option<ConsumedCapacity> {
1779 let mut capacity = capacity?;
1780 if vector_bytes.is_empty() || !vector_capacity_wanted(mode.as_deref()) {
1781 return Some(capacity);
1782 }
1783 capacity.vector_indexes = Some(
1784 vector_bytes
1785 .iter()
1786 .map(|(name, bytes)| {
1787 (
1788 name.clone(),
1789 VectorCapacityDetail {
1790 vector_write_request_bytes: *bytes,
1791 },
1792 )
1793 })
1794 .collect(),
1795 );
1796 Some(capacity)
1797}
1798
1799pub fn transactional_read_capacity(
1804 table_name: &str,
1805 units: f64,
1806 mode: &Option<String>,
1807) -> Option<ConsumedCapacity> {
1808 match mode.as_deref().unwrap_or("NONE") {
1809 "TOTAL" => Some(ConsumedCapacity {
1810 table_name: table_name.to_string(),
1811 capacity_units: units,
1812 read_capacity_units: Some(units),
1813 ..Default::default()
1814 }),
1815 "INDEXES" => Some(ConsumedCapacity {
1816 table_name: table_name.to_string(),
1817 capacity_units: units,
1818 read_capacity_units: Some(units),
1819 table: Some(CapacityDetail {
1820 capacity_units: units,
1821 read_capacity_units: Some(units),
1822 ..Default::default()
1823 }),
1824 ..Default::default()
1825 }),
1826 _ => None,
1827 }
1828}
1829
1830pub fn build_transactional_capacity(
1837 table_units: &HashMap<String, f64>,
1838 mode: &Option<String>,
1839 builder: fn(&str, f64, &Option<String>) -> Option<ConsumedCapacity>,
1840) -> Option<Vec<ConsumedCapacity>> {
1841 if !capacity_wanted(mode.as_deref()) {
1842 return None;
1843 }
1844
1845 let mut tables: Vec<&String> = table_units.keys().collect();
1850 tables.sort();
1851
1852 Some(
1853 tables
1854 .into_iter()
1855 .filter_map(|table| builder(table, *table_units.get(table)?, mode))
1856 .collect(),
1857 )
1858}
1859
1860pub fn transactional_write_capacity_with_indexes(
1872 table_name: &str,
1873 table_units: f64,
1874 gsi_units: &HashMap<String, f64>,
1875 lsi_units: &HashMap<String, f64>,
1876 mode: &Option<String>,
1877) -> Option<ConsumedCapacity> {
1878 secondary_index_capacity(
1879 table_name,
1880 table_units,
1881 gsi_units,
1882 lsi_units,
1883 mode,
1884 CapacityAxis::Write,
1885 )
1886}
1887
1888fn capacity_detail_map(
1891 units: &HashMap<String, f64>,
1892 axis: CapacityAxis,
1893) -> Option<HashMap<String, CapacityDetail>> {
1894 if units.is_empty() {
1895 return None;
1896 }
1897 Some(
1898 units
1899 .iter()
1900 .map(|(name, &u)| {
1901 (
1902 name.clone(),
1903 CapacityDetail {
1904 capacity_units: u,
1905 write_capacity_units: axis.write_units(u),
1906 ..Default::default()
1907 },
1908 )
1909 })
1910 .collect(),
1911 )
1912}
1913
1914pub fn transactional_write_capacity(
1919 table_name: &str,
1920 units: f64,
1921 mode: &Option<String>,
1922) -> Option<ConsumedCapacity> {
1923 match mode.as_deref().unwrap_or("NONE") {
1924 "TOTAL" => Some(ConsumedCapacity {
1925 table_name: table_name.to_string(),
1926 capacity_units: units,
1927 write_capacity_units: Some(units),
1928 ..Default::default()
1929 }),
1930 "INDEXES" => Some(ConsumedCapacity {
1931 table_name: table_name.to_string(),
1932 capacity_units: units,
1933 write_capacity_units: Some(units),
1934 table: Some(CapacityDetail {
1935 capacity_units: units,
1936 write_capacity_units: Some(units),
1937 ..Default::default()
1938 }),
1939 ..Default::default()
1940 }),
1941 _ => None,
1942 }
1943}
1944
1945#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1947pub struct KeySchemaElement {
1948 #[serde(rename = "AttributeName", alias = "attribute_name")]
1949 pub attribute_name: String,
1950 #[serde(rename = "KeyType", alias = "key_type")]
1951 pub key_type: KeyType,
1952}
1953
1954#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1956pub enum KeyType {
1957 #[default]
1958 HASH,
1959 RANGE,
1960}
1961
1962#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1964pub struct AttributeDefinition {
1965 #[serde(rename = "AttributeName", alias = "attribute_name")]
1966 pub attribute_name: String,
1967 #[serde(rename = "AttributeType", alias = "attribute_type")]
1968 pub attribute_type: ScalarAttributeType,
1969}
1970
1971#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1973pub enum ScalarAttributeType {
1974 #[default]
1975 S,
1976 N,
1977 B,
1978}
1979
1980#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
1982pub struct Projection {
1983 #[serde(
1984 rename = "ProjectionType",
1985 alias = "projection_type",
1986 default,
1987 skip_serializing_if = "Option::is_none"
1988 )]
1989 pub projection_type: Option<ProjectionType>,
1990 #[serde(
1991 rename = "NonKeyAttributes",
1992 alias = "non_key_attributes",
1993 skip_serializing_if = "Option::is_none"
1994 )]
1995 pub non_key_attributes: Option<Vec<String>>,
1996}
1997
1998#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2000#[allow(non_camel_case_types)]
2001pub enum ProjectionType {
2002 #[default]
2003 ALL,
2004 KEYS_ONLY,
2005 INCLUDE,
2006}
2007
2008#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2010pub struct GlobalSecondaryIndex {
2011 #[serde(rename = "IndexName", alias = "index_name")]
2012 pub index_name: String,
2013 #[serde(rename = "KeySchema", alias = "key_schema")]
2014 pub key_schema: Vec<KeySchemaElement>,
2015 #[serde(rename = "Projection", alias = "projection")]
2016 pub projection: Projection,
2017 #[serde(
2018 rename = "ProvisionedThroughput",
2019 alias = "provisioned_throughput",
2020 skip_serializing_if = "Option::is_none"
2021 )]
2022 pub provisioned_throughput: Option<ProvisionedThroughput>,
2023}
2024
2025#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2027pub struct LocalSecondaryIndex {
2028 #[serde(rename = "IndexName", alias = "index_name")]
2029 pub index_name: String,
2030 #[serde(rename = "KeySchema", alias = "key_schema")]
2031 pub key_schema: Vec<KeySchemaElement>,
2032 #[serde(rename = "Projection", alias = "projection")]
2033 pub projection: Projection,
2034}
2035
2036#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2039pub struct VectorAttributeDefinition {
2040 #[serde(rename = "AttributeName", alias = "attribute_name")]
2041 pub attribute_name: String,
2042}
2043
2044#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2047pub struct SearchSchemaElement {
2048 #[serde(rename = "AttributeName", alias = "attribute_name")]
2049 pub attribute_name: String,
2050 #[serde(
2051 rename = "SearchSchemaElementType",
2052 alias = "search_schema_element_type"
2053 )]
2054 pub search_schema_element_type: String,
2055}
2056
2057#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2062pub struct VectorIndex {
2063 #[serde(rename = "IndexName", alias = "index_name")]
2064 pub index_name: String,
2065 #[serde(rename = "VectorAttribute", alias = "vector_attribute")]
2066 pub vector_attribute: VectorAttributeDefinition,
2067 #[serde(
2068 rename = "SearchSchema",
2069 alias = "search_schema",
2070 default,
2071 skip_serializing_if = "Option::is_none"
2072 )]
2073 pub search_schema: Option<Vec<SearchSchemaElement>>,
2074 #[serde(rename = "Projection", alias = "projection")]
2075 pub projection: Projection,
2076 #[serde(rename = "Dimensions", alias = "dimensions")]
2077 pub dimensions: u32,
2078 #[serde(rename = "DistanceFunction", alias = "distance_function")]
2079 pub distance_function: String,
2080}
2081
2082#[derive(Debug, Clone, PartialEq)]
2087pub enum VectorValueError {
2088 NotAList,
2090 WrongDimensions {
2092 actual: usize,
2094 },
2095 ElementNotANumber {
2097 position: usize,
2099 actual: &'static str,
2101 },
2102 ElementOutOfRange {
2106 position: usize,
2108 value: String,
2110 },
2111}
2112
2113pub fn check_vector_f32_values(
2123 value: &AttributeValue,
2124 dimensions: u32,
2125) -> std::result::Result<Vec<f32>, VectorValueError> {
2126 let AttributeValue::L(elems) = value else {
2127 return Err(VectorValueError::NotAList);
2128 };
2129 if elems.len() != dimensions as usize {
2130 return Err(VectorValueError::WrongDimensions {
2131 actual: elems.len(),
2132 });
2133 }
2134 let mut out = Vec::with_capacity(elems.len());
2135 for (position, elem) in elems.iter().enumerate() {
2136 let AttributeValue::N(n) = elem else {
2137 return Err(VectorValueError::ElementNotANumber {
2138 position,
2139 actual: elem.type_name(),
2140 });
2141 };
2142 match n.parse::<f32>() {
2145 Ok(v) if v.is_finite() => out.push(v),
2146 _ => {
2147 return Err(VectorValueError::ElementOutOfRange {
2148 position,
2149 value: n.clone(),
2150 });
2151 }
2152 }
2153 }
2154 Ok(out)
2155}
2156
2157pub fn vector_f32_values(value: &AttributeValue, dimensions: u32) -> Option<Vec<f32>> {
2162 check_vector_f32_values(value, dimensions).ok()
2163}
2164
2165pub fn f32_number_string(v: f32) -> String {
2170 serde_json::to_string(&v).unwrap_or_else(|_| "0.0".to_string())
2171}
2172
2173#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2175pub struct ProvisionedThroughput {
2176 #[serde(rename = "ReadCapacityUnits", alias = "read_capacity_units", default)]
2177 pub read_capacity_units: Option<i64>,
2178 #[serde(rename = "WriteCapacityUnits", alias = "write_capacity_units", default)]
2179 pub write_capacity_units: Option<i64>,
2180}
2181
2182#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
2184pub struct OnDemandThroughput {
2185 #[serde(
2186 rename = "MaxReadRequestUnits",
2187 alias = "max_read_request_units",
2188 default,
2189 skip_serializing_if = "Option::is_none"
2190 )]
2191 pub max_read_request_units: Option<i64>,
2192 #[serde(
2193 rename = "MaxWriteRequestUnits",
2194 alias = "max_write_request_units",
2195 default,
2196 skip_serializing_if = "Option::is_none"
2197 )]
2198 pub max_write_request_units: Option<i64>,
2199}
2200
2201#[derive(Debug, Clone, PartialEq)]
2207pub struct ConversionError {
2208 pub expected: &'static str,
2210 pub actual: &'static str,
2212}
2213
2214impl fmt::Display for ConversionError {
2215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2216 write!(f, "expected {}, got {}", self.expected, self.actual)
2217 }
2218}
2219
2220impl std::error::Error for ConversionError {}
2221
2222impl From<String> for AttributeValue {
2225 fn from(value: String) -> Self {
2226 AttributeValue::S(value)
2227 }
2228}
2229
2230impl From<&str> for AttributeValue {
2231 fn from(value: &str) -> Self {
2232 AttributeValue::S(value.to_string())
2233 }
2234}
2235
2236impl From<bool> for AttributeValue {
2237 fn from(value: bool) -> Self {
2238 AttributeValue::BOOL(value)
2239 }
2240}
2241
2242impl From<Vec<u8>> for AttributeValue {
2243 fn from(value: Vec<u8>) -> Self {
2244 AttributeValue::B(value)
2245 }
2246}
2247
2248impl From<&[u8]> for AttributeValue {
2249 fn from(value: &[u8]) -> Self {
2250 AttributeValue::B(value.to_vec())
2251 }
2252}
2253
2254macro_rules! impl_from_integer {
2256 ($($t:ty),+) => {
2257 $(
2258 impl From<$t> for AttributeValue {
2259 fn from(value: $t) -> Self {
2260 AttributeValue::N(value.to_string())
2261 }
2262 }
2263 )+
2264 };
2265}
2266
2267impl_from_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
2268
2269impl From<HashMap<String, AttributeValue>> for AttributeValue {
2271 fn from(value: HashMap<String, AttributeValue>) -> Self {
2272 AttributeValue::M(value)
2273 }
2274}
2275
2276impl From<Vec<AttributeValue>> for AttributeValue {
2277 fn from(value: Vec<AttributeValue>) -> Self {
2278 AttributeValue::L(value)
2279 }
2280}
2281
2282impl From<HashSet<String>> for AttributeValue {
2283 fn from(value: HashSet<String>) -> Self {
2284 AttributeValue::SS(value.into_iter().collect())
2285 }
2286}
2287
2288impl From<BTreeSet<String>> for AttributeValue {
2289 fn from(value: BTreeSet<String>) -> Self {
2290 AttributeValue::SS(value.into_iter().collect())
2291 }
2292}
2293
2294impl TryFrom<f64> for AttributeValue {
2297 type Error = ConversionError;
2298
2299 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
2300 if value.is_finite() {
2301 Ok(AttributeValue::N(value.to_string()))
2302 } else {
2303 Err(ConversionError {
2304 expected: "finite f64",
2305 actual: "NaN or Infinity",
2306 })
2307 }
2308 }
2309}
2310
2311impl TryFrom<f32> for AttributeValue {
2312 type Error = ConversionError;
2313
2314 fn try_from(value: f32) -> std::result::Result<Self, Self::Error> {
2315 if value.is_finite() {
2316 Ok(AttributeValue::N(value.to_string()))
2317 } else {
2318 Err(ConversionError {
2319 expected: "finite f32",
2320 actual: "NaN or Infinity",
2321 })
2322 }
2323 }
2324}
2325
2326impl TryFrom<AttributeValue> for String {
2329 type Error = ConversionError;
2330
2331 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2332 match value {
2333 AttributeValue::S(s) => Ok(s),
2334 other => Err(ConversionError {
2335 expected: "S",
2336 actual: other.type_name(),
2337 }),
2338 }
2339 }
2340}
2341
2342impl TryFrom<AttributeValue> for bool {
2343 type Error = ConversionError;
2344
2345 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2346 match value {
2347 AttributeValue::BOOL(b) => Ok(b),
2348 other => Err(ConversionError {
2349 expected: "BOOL",
2350 actual: other.type_name(),
2351 }),
2352 }
2353 }
2354}
2355
2356impl TryFrom<AttributeValue> for Vec<u8> {
2357 type Error = ConversionError;
2358
2359 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2360 match value {
2361 AttributeValue::B(b) => Ok(b),
2362 other => Err(ConversionError {
2363 expected: "B",
2364 actual: other.type_name(),
2365 }),
2366 }
2367 }
2368}
2369
2370macro_rules! impl_try_from_av_integer {
2371 ($($t:ty),+) => {
2372 $(
2373 impl TryFrom<AttributeValue> for $t {
2374 type Error = ConversionError;
2375
2376 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2377 match value {
2378 AttributeValue::N(n) => n.parse::<$t>().map_err(|_| ConversionError {
2379 expected: stringify!($t),
2380 actual: "N (parse failed)",
2381 }),
2382 other => Err(ConversionError {
2383 expected: "N",
2384 actual: other.type_name(),
2385 }),
2386 }
2387 }
2388 }
2389 )+
2390 };
2391}
2392
2393impl_try_from_av_integer!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
2394
2395impl TryFrom<AttributeValue> for f64 {
2396 type Error = ConversionError;
2397
2398 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2399 match value {
2400 AttributeValue::N(n) => n.parse::<f64>().map_err(|_| ConversionError {
2401 expected: "f64",
2402 actual: "N (parse failed)",
2403 }),
2404 other => Err(ConversionError {
2405 expected: "N",
2406 actual: other.type_name(),
2407 }),
2408 }
2409 }
2410}
2411
2412impl TryFrom<AttributeValue> for f32 {
2413 type Error = ConversionError;
2414
2415 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2416 match value {
2417 AttributeValue::N(n) => n.parse::<f32>().map_err(|_| ConversionError {
2418 expected: "f32",
2419 actual: "N (parse failed)",
2420 }),
2421 other => Err(ConversionError {
2422 expected: "N",
2423 actual: other.type_name(),
2424 }),
2425 }
2426 }
2427}
2428
2429impl TryFrom<AttributeValue> for HashMap<String, AttributeValue> {
2430 type Error = ConversionError;
2431
2432 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2433 match value {
2434 AttributeValue::M(m) => Ok(m),
2435 other => Err(ConversionError {
2436 expected: "M",
2437 actual: other.type_name(),
2438 }),
2439 }
2440 }
2441}
2442
2443impl TryFrom<AttributeValue> for Vec<AttributeValue> {
2444 type Error = ConversionError;
2445
2446 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2447 match value {
2448 AttributeValue::L(l) => Ok(l),
2449 other => Err(ConversionError {
2450 expected: "L",
2451 actual: other.type_name(),
2452 }),
2453 }
2454 }
2455}
2456
2457impl TryFrom<AttributeValue> for Vec<String> {
2458 type Error = ConversionError;
2459
2460 fn try_from(value: AttributeValue) -> std::result::Result<Self, ConversionError> {
2461 match value {
2462 AttributeValue::SS(ss) => Ok(ss),
2463 AttributeValue::L(l) => {
2464 l.into_iter()
2466 .map(|av| match av {
2467 AttributeValue::S(s) => Ok(s),
2468 other => Err(ConversionError {
2469 expected: "S (within L)",
2470 actual: other.type_name(),
2471 }),
2472 })
2473 .collect()
2474 }
2475 other => Err(ConversionError {
2476 expected: "SS or L",
2477 actual: other.type_name(),
2478 }),
2479 }
2480 }
2481}
2482
2483#[cfg(test)]
2484mod tests {
2485 use super::*;
2486
2487 struct SeesLayer {
2490 seen: Vec<crate::validation::ValueLayer>,
2491 waited: bool,
2492 }
2493
2494 impl std::future::Future for SeesLayer {
2495 type Output = Vec<crate::validation::ValueLayer>;
2496 fn poll(
2497 mut self: std::pin::Pin<&mut Self>,
2498 cx: &mut std::task::Context<'_>,
2499 ) -> std::task::Poll<Self::Output> {
2500 self.seen.push(current_value_layer());
2501 if self.waited {
2502 std::task::Poll::Ready(std::mem::take(&mut self.seen))
2503 } else {
2504 self.waited = true;
2505 cx.waker().wake_by_ref();
2506 std::task::Poll::Pending
2507 }
2508 }
2509 }
2510
2511 fn sees_layer() -> SeesLayer {
2512 SeesLayer {
2513 seen: Vec::new(),
2514 waited: false,
2515 }
2516 }
2517
2518 #[test]
2522 fn the_value_layer_follows_a_future_across_its_polls_and_no_further() {
2523 use crate::validation::ValueLayer::{Current, Older};
2524 use std::future::Future;
2525 let mut cx = std::task::Context::from_waker(std::task::Waker::noop());
2526 let mut older = Box::pin(with_value_layer_async(Older, sees_layer()));
2527 let mut current = Box::pin(with_value_layer_async(Current, sees_layer()));
2528
2529 assert_eq!(current_value_layer(), Current);
2530 assert!(older.as_mut().poll(&mut cx).is_pending());
2531 assert_eq!(current_value_layer(), Current);
2533 assert!(current.as_mut().poll(&mut cx).is_pending());
2536 assert_eq!(current_value_layer(), Current);
2537 assert_eq!(
2538 with_value_layer(Older, || {
2539 let mut neighbour = Box::pin(sees_layer());
2540 let _ = neighbour.as_mut().poll(&mut cx);
2541 current_value_layer()
2542 }),
2543 Older
2544 );
2545 match older.as_mut().poll(&mut cx) {
2546 std::task::Poll::Ready(seen) => assert_eq!(seen, vec![Older, Older]),
2547 std::task::Poll::Pending => panic!("the future was ready on its second poll"),
2548 }
2549 match current.as_mut().poll(&mut cx) {
2550 std::task::Poll::Ready(seen) => assert_eq!(seen, vec![Current, Current]),
2551 std::task::Poll::Pending => panic!("the future was ready on its second poll"),
2552 }
2553 assert_eq!(current_value_layer(), Current);
2554 }
2555
2556 #[test]
2557 fn test_serialize_string() {
2558 let val = AttributeValue::S("hello".to_string());
2559 let json = serde_json::to_string(&val).unwrap();
2560 assert_eq!(json, r#"{"S":"hello"}"#);
2561 }
2562
2563 #[test]
2564 fn test_serialize_number() {
2565 let val = AttributeValue::N("42".to_string());
2566 let json = serde_json::to_string(&val).unwrap();
2567 assert_eq!(json, r#"{"N":"42"}"#);
2568 }
2569
2570 #[test]
2571 fn test_serialize_binary() {
2572 let val = AttributeValue::B(vec![1, 2, 3]);
2573 let json = serde_json::to_string(&val).unwrap();
2574 assert_eq!(json, r#"{"B":"AQID"}"#);
2575 }
2576
2577 #[test]
2578 fn test_serialize_bool() {
2579 let val = AttributeValue::BOOL(true);
2580 let json = serde_json::to_string(&val).unwrap();
2581 assert_eq!(json, r#"{"BOOL":true}"#);
2582 }
2583
2584 #[test]
2585 fn test_serialize_null() {
2586 let val = AttributeValue::NULL(true);
2587 let json = serde_json::to_string(&val).unwrap();
2588 assert_eq!(json, r#"{"NULL":true}"#);
2589 }
2590
2591 #[test]
2592 fn test_deserialize_null_true() {
2593 let val: AttributeValue = serde_json::from_str(r#"{"NULL":true}"#).unwrap();
2594 assert_eq!(val, AttributeValue::NULL(true));
2595 }
2596
2597 #[test]
2598 fn test_deserialize_null_false_rejected() {
2599 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":false}"#).unwrap_err();
2602 assert!(
2603 err.to_string().contains("must have the value of true"),
2604 "unexpected error: {err}"
2605 );
2606 }
2607
2608 #[test]
2609 fn test_deserialize_null_non_boolean_rejected() {
2610 let err = serde_json::from_str::<AttributeValue>(r#"{"NULL":"no"}"#).unwrap_err();
2612 assert!(
2613 err.to_string().contains("must have the value of true"),
2614 "unexpected error: {err}"
2615 );
2616 }
2617
2618 #[test]
2619 fn test_serialize_string_set() {
2620 let val = AttributeValue::SS(vec!["a".to_string(), "b".to_string()]);
2621 let json = serde_json::to_string(&val).unwrap();
2622 assert_eq!(json, r#"{"SS":["a","b"]}"#);
2623 }
2624
2625 #[test]
2626 fn test_serialize_list() {
2627 let val = AttributeValue::L(vec![
2628 AttributeValue::S("hello".to_string()),
2629 AttributeValue::N("42".to_string()),
2630 ]);
2631 let json = serde_json::to_string(&val).unwrap();
2632 assert_eq!(json, r#"{"L":[{"S":"hello"},{"N":"42"}]}"#);
2633 }
2634
2635 #[test]
2636 fn test_serialize_map() {
2637 let mut m = HashMap::new();
2638 m.insert("key".to_string(), AttributeValue::S("value".to_string()));
2639 let val = AttributeValue::M(m);
2640 let json = serde_json::to_string(&val).unwrap();
2641 assert_eq!(json, r#"{"M":{"key":{"S":"value"}}}"#);
2642 }
2643
2644 #[test]
2645 fn test_round_trip_all_types() {
2646 let values = vec![
2647 AttributeValue::S("hello".to_string()),
2648 AttributeValue::N("42.5".to_string()),
2649 AttributeValue::B(vec![0, 255, 128]),
2650 AttributeValue::BOOL(false),
2651 AttributeValue::NULL(true),
2652 AttributeValue::SS(vec!["x".to_string(), "y".to_string()]),
2653 AttributeValue::NS(vec!["1".to_string(), "2.5".to_string()]),
2654 AttributeValue::BS(vec![vec![1], vec![2, 3]]),
2655 AttributeValue::L(vec![
2656 AttributeValue::S("nested".to_string()),
2657 AttributeValue::N("99".to_string()),
2658 ]),
2659 ];
2660
2661 for val in values {
2662 let json = serde_json::to_string(&val).unwrap();
2663 let deserialized: AttributeValue = serde_json::from_str(&json).unwrap();
2664 assert_eq!(val, deserialized, "Round-trip failed for {json}");
2665 }
2666 }
2667
2668 #[test]
2669 fn test_size_string() {
2670 let val = AttributeValue::S("hello".to_string());
2671 assert_eq!(val.size(), 5);
2672 }
2673
2674 #[test]
2675 fn test_size_number() {
2676 let val = AttributeValue::N("42".to_string());
2678 assert_eq!(val.size(), 2);
2679 }
2680
2681 #[test]
2682 fn test_size_number_matches_dynamodb() {
2683 let cases: &[(&str, usize)] = &[
2691 ("1", 2),
2693 ("12", 2),
2694 ("123", 3),
2695 ("1234", 3),
2696 ("12345", 4),
2697 ("123456", 4),
2698 ("1234567", 5),
2699 ("12345678", 5),
2700 ("123456789", 6),
2701 ("1234567891", 6),
2702 ("12345678912", 7),
2703 ("123456789123", 7),
2704 ("1234567890123456789", 11),
2705 ("12345678901234567890123456789012345678", 20),
2706 ("0042", 2),
2708 ("100", 2),
2709 ("1010", 3),
2710 ("0.0000001", 2),
2711 ("0", 1),
2712 ("1E125", 2),
2714 ("1E-100", 2),
2715 ("1.5", 3),
2718 ("1.2", 3),
2719 ("1.200", 3),
2720 ("1.234", 4),
2721 ("3.14159", 5),
2722 ("100.5", 4),
2723 ("0.15", 2),
2724 ("15", 2),
2725 ("-42", 3),
2727 ("-12345", 5),
2728 ("-1E125", 3),
2729 ("-0.15", 3),
2730 ("-0", 1),
2731 ];
2732 for (literal, expected) in cases {
2733 assert_eq!(
2734 AttributeValue::N((*literal).to_string()).size(),
2735 *expected,
2736 "size of N {literal:?}"
2737 );
2738 }
2739 }
2740
2741 #[test]
2742 fn test_size_number_survives_normalisation() {
2743 for literal in [
2747 "1E125",
2748 "1E-100",
2749 "0042",
2750 "1.200",
2751 "0.0000001",
2752 "100",
2753 "3.14159",
2754 "42",
2755 ] {
2756 let raw = AttributeValue::N(literal.to_string()).size();
2757 let normalised = AttributeValue::N(normalize_dynamo_number(literal)).size();
2758 assert_eq!(
2759 raw, normalised,
2760 "N {literal:?} changed size when normalised"
2761 );
2762 }
2763 }
2764
2765 #[test]
2766 fn test_size_bool() {
2767 assert_eq!(AttributeValue::BOOL(true).size(), 1);
2768 }
2769
2770 #[test]
2771 fn test_size_null() {
2772 assert_eq!(AttributeValue::NULL(true).size(), 1);
2773 }
2774
2775 #[test]
2776 fn test_key_string_s() {
2777 let val = AttributeValue::S("hello".to_string());
2778 assert_eq!(val.to_key_string(), Some("S:hello".to_string()));
2779 }
2780
2781 #[test]
2782 fn test_key_string_n() {
2783 let val = AttributeValue::N("42".to_string());
2784 let key = val.to_key_string().unwrap();
2785 assert!(key.starts_with("N:"));
2786 }
2787
2788 #[test]
2789 fn test_key_string_b() {
2790 let val = AttributeValue::B(vec![0xff, 0x00, 0xab]);
2791 assert_eq!(val.to_key_string(), Some("B:ff00ab".to_string()));
2792 }
2793
2794 #[test]
2795 fn test_key_string_non_key_type_returns_none() {
2796 assert_eq!(AttributeValue::BOOL(true).to_key_string(), None);
2797 assert_eq!(AttributeValue::L(vec![]).to_key_string(), None);
2798 }
2799
2800 #[test]
2802 fn test_number_sort_ordering() {
2803 let numbers = vec![
2804 "-1000", "-100", "-10", "-1", "-0.5", "-0.001", "0", "0.001", "0.5", "1", "10", "100",
2805 "1000",
2806 ];
2807 let encoded: Vec<String> = numbers
2808 .iter()
2809 .map(|n| normalize_number_for_sort(n))
2810 .collect();
2811
2812 for i in 0..encoded.len() - 1 {
2813 assert!(
2814 encoded[i] < encoded[i + 1],
2815 "Sort order broken: {} ({}) should be < {} ({})",
2816 numbers[i],
2817 encoded[i],
2818 numbers[i + 1],
2819 encoded[i + 1]
2820 );
2821 }
2822 }
2823
2824 #[test]
2825 fn test_number_sort_zero_variants() {
2826 let z1 = normalize_number_for_sort("0");
2827 let z2 = normalize_number_for_sort("-0");
2828 let z3 = normalize_number_for_sort("0.0");
2829 assert_eq!(z1, z2);
2830 assert_eq!(z2, z3);
2831 }
2832
2833 #[test]
2834 fn test_number_sort_decimals() {
2835 let a = normalize_number_for_sort("1.5");
2836 let b = normalize_number_for_sort("2.5");
2837 assert!(a < b);
2838
2839 let c = normalize_number_for_sort("0.001");
2840 let d = normalize_number_for_sort("0.01");
2841 assert!(c < d);
2842 }
2843
2844 #[test]
2845 fn test_number_sort_scientific() {
2846 let a = normalize_number_for_sort("1e10");
2847 let b = normalize_number_for_sort("1e11");
2848 assert!(a < b);
2849
2850 let c = normalize_number_for_sort("-1e11");
2851 let d = normalize_number_for_sort("-1e10");
2852 assert!(c < d);
2853 }
2854
2855 #[test]
2859 fn test_number_sort_out_of_range_sorts_at_the_extremes() {
2860 let ordered = [
2861 "-1e99999999999",
2862 "-9.9999999999999999999999999999999999999E125",
2863 "-1E-130",
2864 "-1e-99999999999",
2865 "0",
2866 "1e-99999999999",
2867 "1E-130",
2868 "9.9999999999999999999999999999999999999E125",
2869 "1e99999999999",
2870 ];
2871 for pair in ordered.windows(2) {
2872 let (a, b) = (pair[0], pair[1]);
2873 assert!(
2874 normalize_number_for_sort(a) < normalize_number_for_sort(b),
2875 "{a} should sort before {b}"
2876 );
2877 }
2878 }
2879
2880 #[test]
2883 fn test_split_exponent_reads_an_unparseable_exponent_as_zero() {
2884 assert_eq!(split_exponent("1e"), ("1", 0));
2885 assert_eq!(split_exponent("1e+"), ("1", 0));
2886 assert_eq!(split_exponent("1e99999999999999999999"), ("1", i64::MAX));
2887 assert_eq!(split_exponent("1e-99999999999999999999"), ("1", i64::MIN));
2888 }
2889
2890 #[test]
2895 fn test_validate_number_accepts_dynamodb_grammar() {
2896 for input in [
2897 "+5", "+1.5", "+0", "-0", "+0.0", "+1e2", "1e+2", "1.5E+3", "-7", "+.5", ".5", "5.",
2898 "00042", "1.23E10", "+1e-2", "1E-130", "+1E-130",
2899 ] {
2900 assert!(
2901 validate_dynamo_number(input).is_ok(),
2902 "expected {input} to validate, got {:?}",
2903 validate_dynamo_number(input)
2904 );
2905 }
2906 }
2907
2908 #[test]
2909 fn test_validate_number_rejects_malformed() {
2910 for input in [
2913 "+e2", "e2", "+1+2", "1+2", "+1.2.3", "1.2.3", "++5", "+-5", "-+5", "+", "-", "1e",
2914 "1e+", ".", "1.2e3.4", "0x5", "NaN", "Infinity", "1_000", " 5", "5 ", "1 5", "",
2915 ] {
2916 assert!(
2917 matches!(
2918 validate_dynamo_number(input),
2919 Err(crate::errors::DynoxideError::ValidationException(_))
2920 ),
2921 "expected {input:?} to be rejected with ValidationException, got {:?}",
2922 validate_dynamo_number(input)
2923 );
2924 }
2925 }
2926
2927 fn validation_message(input: &str) -> Option<String> {
2928 match validate_dynamo_number(input) {
2929 Ok(()) => None,
2930 Err(crate::errors::DynoxideError::ValidationException(m)) => Some(m),
2931 Err(e) => panic!("expected a ValidationException for {input:?}, got {e:?}"),
2932 }
2933 }
2934
2935 const OVERFLOW: &str =
2936 "Number overflow. Attempting to store a number with magnitude larger than supported range";
2937 const UNDERFLOW: &str = "Number underflow. Attempting to store a number with magnitude smaller than supported range";
2938
2939 #[test]
2943 fn test_validate_number_range_checks_exponents_beyond_i32() {
2944 for (input, expected) in [
2945 ("1e99999999999", OVERFLOW),
2946 ("-1e99999999999", OVERFLOW),
2947 ("1E+99999999999", OVERFLOW),
2948 ("1e-99999999999", UNDERFLOW),
2949 ("-1e-99999999999", UNDERFLOW),
2950 ("1e2147483648", OVERFLOW),
2951 ("1e-2147483649", UNDERFLOW),
2952 ("10e2147483647", OVERFLOW),
2954 ("0.01e-2147483648", UNDERFLOW),
2955 ("1e9223372036854775807", OVERFLOW),
2956 ("1e-9223372036854775808", UNDERFLOW),
2957 ("10e9223372036854775807", OVERFLOW),
2958 ("0.01e-9223372036854775808", UNDERFLOW),
2959 ] {
2960 assert_eq!(
2961 validation_message(input).as_deref(),
2962 Some(expected),
2963 "{input}"
2964 );
2965 }
2966 }
2967
2968 #[test]
2972 fn test_validate_number_rejects_exponent_beyond_i64_as_unconvertible() {
2973 for input in [
2974 "1e9223372036854775808",
2975 "1e+9223372036854775808",
2976 "1e-9223372036854775809",
2977 "0e9223372036854775808",
2978 ] {
2979 assert_eq!(
2980 validation_message(input),
2981 Some(format!(
2982 "The parameter cannot be converted to a numeric value: {input}"
2983 )),
2984 "{input}"
2985 );
2986 }
2987 }
2988
2989 #[test]
2993 fn test_validate_number_range_checks_zero_by_its_last_written_digit() {
2994 let zeros_130 = format!("0.{}", "0".repeat(130));
2995 let zeros_131 = format!("0.{}", "0".repeat(131));
2996 for (input, expected) in [
2997 ("0e125", None),
2998 ("0e126", Some(OVERFLOW)),
2999 ("0.0e126", None),
3000 ("0.0e127", Some(OVERFLOW)),
3001 ("0.00e127", None),
3002 ("00e126", Some(OVERFLOW)),
3003 ("00.00e126", None),
3004 ("-0e127", Some(OVERFLOW)),
3005 ("0e99999999999", Some(OVERFLOW)),
3006 ("0e-130", None),
3007 ("0e-131", Some(UNDERFLOW)),
3008 ("0.0e-129", None),
3009 ("0.0e-130", Some(UNDERFLOW)),
3010 ("0e-99999999999", Some(UNDERFLOW)),
3011 (zeros_130.as_str(), None),
3012 (zeros_131.as_str(), Some(UNDERFLOW)),
3013 ] {
3014 assert_eq!(validation_message(input).as_deref(), expected, "{input}");
3015 }
3016 }
3017
3018 #[test]
3019 fn test_normalize_number_matches_dynamodb() {
3020 for (input, stored) in [
3021 ("+5", "5"),
3022 ("+1.5", "1.5"),
3023 ("+0", "0"),
3024 ("-0", "0"),
3025 ("+0.0", "0"),
3026 ("+1e2", "100"),
3027 ("1e+2", "100"),
3028 ("1.5E+3", "1500"),
3029 ("-7", "-7"),
3030 ("+.5", "0.5"),
3031 (".5", "0.5"),
3032 ("5.", "5"),
3033 ("00042", "42"),
3034 ("1.23E10", "12300000000"),
3035 ("+1e-2", "0.01"),
3036 ] {
3037 assert_eq!(
3038 normalize_dynamo_number(input),
3039 stored,
3040 "{input} should normalise to {stored}"
3041 );
3042 }
3043 }
3044
3045 #[test]
3046 fn test_type_name() {
3047 assert_eq!(AttributeValue::S("".to_string()).type_name(), "S");
3048 assert_eq!(AttributeValue::N("0".to_string()).type_name(), "N");
3049 assert_eq!(AttributeValue::B(vec![]).type_name(), "B");
3050 assert_eq!(AttributeValue::BOOL(true).type_name(), "BOOL");
3051 assert_eq!(AttributeValue::NULL(true).type_name(), "NULL");
3052 assert_eq!(AttributeValue::SS(vec![]).type_name(), "SS");
3053 assert_eq!(AttributeValue::NS(vec![]).type_name(), "NS");
3054 assert_eq!(AttributeValue::BS(vec![]).type_name(), "BS");
3055 assert_eq!(AttributeValue::L(vec![]).type_name(), "L");
3056 assert_eq!(AttributeValue::M(HashMap::new()).type_name(), "M");
3057 }
3058}