1mod coercion;
8mod compare;
9mod hash;
10mod input;
11mod output;
12mod rank;
13mod storage_key;
14mod storage_key_runtime;
15mod tag;
16mod wire;
17
18#[cfg(test)]
19mod tests;
20
21use crate::{
22 model::field::{FieldKind, FieldStorageDecode},
23 prelude::*,
24 traits::{
25 EnumValue, FieldTypeMeta, NumericValue, Repr, RuntimeValueDecode, RuntimeValueEncode,
26 RuntimeValueMeta,
27 },
28 types::*,
29};
30use candid::CandidType;
31use serde::Deserialize;
32use std::{cmp::Ordering, fmt};
33
34pub use coercion::{CoercionFamily, CoercionFamilyExt};
36#[cfg(test)]
37pub(crate) use hash::with_test_hash_override;
38pub(crate) use hash::{ValueHashWriter, hash_single_list_identity_canonical_value, hash_value};
39pub use input::{InputValue, InputValueEnum};
40pub use output::{OutputValue, OutputValueEnum};
41pub use storage_key::{StorageKey, StorageKeyDecodeError, StorageKeyEncodeError};
42pub(crate) use storage_key_runtime::{
43 storage_key_as_runtime_value, storage_key_from_runtime_value,
44};
45pub use tag::ValueTag;
46
47const F64_SAFE_I64: i64 = 1i64 << 53;
52const F64_SAFE_U64: u64 = 1u64 << 53;
53const F64_SAFE_I128: i128 = 1i128 << 53;
54const F64_SAFE_U128: u128 = 1u128 << 53;
55pub(crate) const VALUE_WIRE_TYPE_NAME: &str = "Value";
56pub(crate) const VALUE_WIRE_VARIANT_LABELS: &[&str] = &[
57 "Account",
58 "Blob",
59 "Bool",
60 "Date",
61 "Decimal",
62 "Duration",
63 "Enum",
64 "Float32",
65 "Float64",
66 "Int",
67 "Int128",
68 "IntBig",
69 "List",
70 "Map",
71 "Null",
72 "Principal",
73 "Subaccount",
74 "Text",
75 "Timestamp",
76 "Uint",
77 "Uint128",
78 "UintBig",
79 "Ulid",
80 "Unit",
81];
82
83enum NumericRepr {
88 Decimal(Decimal),
89 F64(f64),
90 None,
91}
92
93#[derive(Clone, Copy)]
95pub(crate) enum ValueWireVariant {
96 Account,
97 Blob,
98 Bool,
99 Date,
100 Decimal,
101 Duration,
102 Enum,
103 Float32,
104 Float64,
105 Int,
106 Int128,
107 IntBig,
108 List,
109 Map,
110 Null,
111 Principal,
112 Subaccount,
113 Text,
114 Timestamp,
115 Uint,
116 Uint128,
117 UintBig,
118 Ulid,
119 Unit,
120}
121
122impl ValueWireVariant {
123 pub(crate) fn from_label(label: &str) -> Option<Self> {
125 match label {
126 "Account" => Some(Self::Account),
127 "Blob" => Some(Self::Blob),
128 "Bool" => Some(Self::Bool),
129 "Date" => Some(Self::Date),
130 "Decimal" => Some(Self::Decimal),
131 "Duration" => Some(Self::Duration),
132 "Enum" => Some(Self::Enum),
133 "Float32" => Some(Self::Float32),
134 "Float64" => Some(Self::Float64),
135 "Int" => Some(Self::Int),
136 "Int128" => Some(Self::Int128),
137 "IntBig" => Some(Self::IntBig),
138 "List" => Some(Self::List),
139 "Map" => Some(Self::Map),
140 "Null" => Some(Self::Null),
141 "Principal" => Some(Self::Principal),
142 "Subaccount" => Some(Self::Subaccount),
143 "Text" => Some(Self::Text),
144 "Timestamp" => Some(Self::Timestamp),
145 "Uint" => Some(Self::Uint),
146 "Uint128" => Some(Self::Uint128),
147 "UintBig" => Some(Self::UintBig),
148 "Ulid" => Some(Self::Ulid),
149 "Unit" => Some(Self::Unit),
150 _ => None,
151 }
152 }
153}
154
155#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub enum TextMode {
161 Cs, Ci, }
164
165#[derive(Clone, Debug, Eq, PartialEq)]
172pub enum MapValueError {
173 EmptyKey {
174 index: usize,
175 },
176 NonScalarKey {
177 index: usize,
178 key: Value,
179 },
180 NonScalarValue {
181 index: usize,
182 value: Value,
183 },
184 DuplicateKey {
185 left_index: usize,
186 right_index: usize,
187 },
188}
189
190impl std::fmt::Display for MapValueError {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 match self {
193 Self::EmptyKey { index } => write!(f, "map key at index {index} must be non-null"),
194 Self::NonScalarKey { index, key } => {
195 write!(f, "map key at index {index} is not scalar: {key:?}")
196 }
197 Self::NonScalarValue { index, value } => {
198 write!(
199 f,
200 "map value at index {index} is not scalar/ref-like: {value:?}"
201 )
202 }
203 Self::DuplicateKey {
204 left_index,
205 right_index,
206 } => write!(
207 f,
208 "map contains duplicate keys at normalized positions {left_index} and {right_index}"
209 ),
210 }
211 }
212}
213
214impl std::error::Error for MapValueError {}
215
216#[derive(Clone, Debug, Eq, PartialEq)]
223pub enum SchemaInvariantError {
224 InvalidMapValue(MapValueError),
225}
226
227impl std::fmt::Display for SchemaInvariantError {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 match self {
230 Self::InvalidMapValue(err) => write!(f, "{err}"),
231 }
232 }
233}
234
235impl std::error::Error for SchemaInvariantError {}
236
237impl From<MapValueError> for SchemaInvariantError {
238 fn from(value: MapValueError) -> Self {
239 Self::InvalidMapValue(value)
240 }
241}
242
243#[derive(CandidType, Clone, Eq, PartialEq)]
252pub enum Value {
253 Account(Account),
254 Blob(Vec<u8>),
255 Bool(bool),
256 Date(Date),
257 Decimal(Decimal),
258 Duration(Duration),
259 Enum(ValueEnum),
260 Float32(Float32),
261 Float64(Float64),
262 Int(i64),
263 Int128(Int128),
264 IntBig(Int),
265 List(Vec<Self>),
269 Map(Vec<(Self, Self)>),
276 Null,
277 Principal(Principal),
278 Subaccount(Subaccount),
279 Text(String),
280 Timestamp(Timestamp),
281 Uint(u64),
282 Uint128(Nat128),
283 UintBig(Nat),
284 Ulid(Ulid),
285 Unit,
286}
287
288impl fmt::Debug for Value {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 match self {
291 Self::Account(value) => f.debug_tuple("Account").field(value).finish(),
292 Self::Blob(value) => write!(f, "Blob({} bytes)", value.len()),
293 Self::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
294 Self::Date(value) => f.debug_tuple("Date").field(value).finish(),
295 Self::Decimal(value) => f.debug_tuple("Decimal").field(value).finish(),
296 Self::Duration(value) => f.debug_tuple("Duration").field(value).finish(),
297 Self::Enum(value) => f.debug_tuple("Enum").field(value).finish(),
298 Self::Float32(value) => f.debug_tuple("Float32").field(value).finish(),
299 Self::Float64(value) => f.debug_tuple("Float64").field(value).finish(),
300 Self::Int(value) => f.debug_tuple("Int").field(value).finish(),
301 Self::Int128(value) => f.debug_tuple("Int128").field(value).finish(),
302 Self::IntBig(value) => f.debug_tuple("IntBig").field(value).finish(),
303 Self::List(value) => f.debug_tuple("List").field(value).finish(),
304 Self::Map(value) => f.debug_tuple("Map").field(value).finish(),
305 Self::Null => f.write_str("Null"),
306 Self::Principal(value) => f.debug_tuple("Principal").field(value).finish(),
307 Self::Subaccount(value) => f.debug_tuple("Subaccount").field(value).finish(),
308 Self::Text(value) => f.debug_tuple("Text").field(value).finish(),
309 Self::Timestamp(value) => f.debug_tuple("Timestamp").field(value).finish(),
310 Self::Uint(value) => f.debug_tuple("Uint").field(value).finish(),
311 Self::Uint128(value) => f.debug_tuple("Uint128").field(value).finish(),
312 Self::UintBig(value) => f.debug_tuple("UintBig").field(value).finish(),
313 Self::Ulid(value) => f.debug_tuple("Ulid").field(value).finish(),
314 Self::Unit => f.write_str("Unit"),
315 }
316 }
317}
318
319impl FieldTypeMeta for Value {
320 const KIND: FieldKind = FieldKind::Structured { queryable: false };
321 const STORAGE_DECODE: FieldStorageDecode = FieldStorageDecode::Value;
322}
323
324impl Value {
325 pub const __KIND: FieldKind = FieldKind::Structured { queryable: false };
326 pub const __STORAGE_DECODE: FieldStorageDecode = FieldStorageDecode::Value;
327}
328
329macro_rules! value_is_numeric_from_registry {
331 ( @args $value:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
332 match $value {
333 $( $value_pat => $is_numeric, )*
334 _ => false,
335 }
336 };
337}
338
339macro_rules! value_supports_numeric_coercion_from_registry {
340 ( @args $value:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
341 match $value {
342 $( $value_pat => $supports_numeric_coercion, )*
343 _ => false,
344 }
345 };
346}
347
348macro_rules! value_storage_key_case {
349 ( $value:expr, Unit, true ) => {
350 if let Value::Unit = $value {
351 Some(StorageKey::Unit)
352 } else {
353 None
354 }
355 };
356 ( $value:expr, $scalar:ident, true ) => {
357 if let Value::$scalar(v) = $value {
358 Some(StorageKey::$scalar(*v))
359 } else {
360 None
361 }
362 };
363 ( $value:expr, $scalar:ident, false ) => {
364 None
365 };
366}
367
368macro_rules! value_storage_key_from_registry {
369 ( @args $value:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:tt, is_storage_key_encodable = $is_storage_key_encodable:tt) ),* $(,)? ) => {
370 {
371 let mut key = None;
372 $(
373 match key {
374 Some(_) => {}
375 None => {
376 key = value_storage_key_case!($value, $scalar, $is_storage_key_encodable);
377 }
378 }
379 )*
380 key
381 }
382 };
383}
384
385macro_rules! value_coercion_family_from_registry {
386 ( @args $value:expr; @entries $( ($scalar:ident, $coercion_family:expr, $value_pat:pat, is_numeric_value = $is_numeric:expr, supports_numeric_coercion = $supports_numeric_coercion:expr, supports_arithmetic = $supports_arithmetic:expr, supports_equality = $supports_equality:expr, supports_ordering = $supports_ordering:expr, is_keyable = $is_keyable:expr, is_storage_key_encodable = $is_storage_key_encodable:expr) ),* $(,)? ) => {
387 match $value {
388 $( $value_pat => $coercion_family, )*
389 Value::List(_) => CoercionFamily::Collection,
390 Value::Map(_) => CoercionFamily::Collection,
391 Value::Null => CoercionFamily::Null,
392 }
393 };
394}
395
396impl Value {
397 pub fn from_slice<T>(items: &[T]) -> Self
406 where
407 T: Into<Self> + Clone,
408 {
409 Self::List(items.iter().cloned().map(Into::into).collect())
410 }
411
412 pub fn from_list<T>(items: Vec<T>) -> Self
416 where
417 T: Into<Self>,
418 {
419 Self::List(items.into_iter().map(Into::into).collect())
420 }
421
422 pub fn from_map(entries: Vec<(Self, Self)>) -> Result<Self, MapValueError> {
430 let normalized = Self::normalize_map_entries(entries)?;
431 Ok(Self::Map(normalized))
432 }
433
434 pub fn validate_map_entries(entries: &[(Self, Self)]) -> Result<(), MapValueError> {
436 for (index, (key, _value)) in entries.iter().enumerate() {
437 if matches!(key, Self::Null) {
438 return Err(MapValueError::EmptyKey { index });
439 }
440 if !key.is_scalar() {
441 return Err(MapValueError::NonScalarKey {
442 index,
443 key: key.clone(),
444 });
445 }
446 }
447
448 Ok(())
449 }
450
451 pub(crate) fn compare_map_entry_keys(left: &(Self, Self), right: &(Self, Self)) -> Ordering {
453 Self::canonical_cmp_key(&left.0, &right.0)
454 }
455
456 pub(crate) fn sort_map_entries_in_place(entries: &mut [(Self, Self)]) {
458 entries.sort_by(Self::compare_map_entry_keys);
459 }
460
461 pub(crate) fn map_entries_are_strictly_canonical(entries: &[(Self, Self)]) -> bool {
464 entries.windows(2).all(|pair| {
465 let [left, right] = pair else {
466 return true;
467 };
468
469 Self::compare_map_entry_keys(left, right) == Ordering::Less
470 })
471 }
472
473 pub fn normalize_map_entries(
475 mut entries: Vec<(Self, Self)>,
476 ) -> Result<Vec<(Self, Self)>, MapValueError> {
477 Self::validate_map_entries(&entries)?;
478 Self::sort_map_entries_in_place(entries.as_mut_slice());
479
480 for i in 1..entries.len() {
481 let (left_key, _) = &entries[i - 1];
482 let (right_key, _) = &entries[i];
483 if Self::canonical_cmp_key(left_key, right_key) == Ordering::Equal {
484 return Err(MapValueError::DuplicateKey {
485 left_index: i - 1,
486 right_index: i,
487 });
488 }
489 }
490
491 Ok(entries)
492 }
493
494 pub fn from_enum<E: EnumValue>(value: E) -> Self {
496 Self::Enum(value.to_value_enum())
497 }
498
499 #[must_use]
501 pub fn enum_strict<E: Path>(variant: &str) -> Self {
502 Self::Enum(ValueEnum::strict::<E>(variant))
503 }
504
505 #[must_use]
512 pub const fn is_numeric(&self) -> bool {
513 scalar_registry!(value_is_numeric_from_registry, self)
514 }
515
516 #[must_use]
518 pub const fn supports_numeric_coercion(&self) -> bool {
519 scalar_registry!(value_supports_numeric_coercion_from_registry, self)
520 }
521
522 #[must_use]
524 pub const fn is_text(&self) -> bool {
525 matches!(self, Self::Text(_))
526 }
527
528 #[must_use]
530 pub const fn is_unit(&self) -> bool {
531 matches!(self, Self::Unit)
532 }
533
534 #[must_use]
535 pub const fn is_scalar(&self) -> bool {
536 match self {
537 Self::List(_) | Self::Map(_) | Self::Unit => false,
539 _ => true,
540 }
541 }
542
543 #[must_use]
545 pub(crate) const fn canonical_tag(&self) -> ValueTag {
546 tag::canonical_tag(self)
547 }
548
549 #[must_use]
551 pub(crate) const fn canonical_rank(&self) -> u8 {
552 rank::canonical_rank(self)
553 }
554
555 #[must_use]
557 pub(crate) fn canonical_cmp(left: &Self, right: &Self) -> Ordering {
558 compare::canonical_cmp(left, right)
559 }
560
561 #[must_use]
563 pub fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
564 compare::canonical_cmp_key(left, right)
565 }
566
567 #[must_use]
572 pub(crate) fn canonical_cmp_map_entry(
573 left_key: &Self,
574 left_value: &Self,
575 right_key: &Self,
576 right_value: &Self,
577 ) -> Ordering {
578 Self::canonical_cmp_key(left_key, right_key)
579 .then_with(|| Self::canonical_cmp(left_value, right_value))
580 }
581
582 #[must_use]
585 pub(crate) fn ordered_map_entries(entries: &[(Self, Self)]) -> Vec<&(Self, Self)> {
586 let mut ordered = entries.iter().collect::<Vec<_>>();
587 ordered.sort_by(|left, right| {
588 Self::canonical_cmp_map_entry(&left.0, &left.1, &right.0, &right.1)
589 });
590
591 ordered
592 }
593
594 #[must_use]
598 pub(crate) fn strict_order_cmp(left: &Self, right: &Self) -> Option<Ordering> {
599 compare::strict_order_cmp(left, right)
600 }
601
602 fn numeric_repr(&self) -> NumericRepr {
603 if !self.supports_numeric_coercion() {
605 return NumericRepr::None;
606 }
607
608 if let Some(d) = self.to_decimal() {
609 return NumericRepr::Decimal(d);
610 }
611 if let Some(f) = self.to_f64_lossless() {
612 return NumericRepr::F64(f);
613 }
614 NumericRepr::None
615 }
616
617 #[must_use]
626 pub const fn as_storage_key(&self) -> Option<StorageKey> {
627 scalar_registry!(value_storage_key_from_registry, self)
628 }
629
630 #[must_use]
631 pub const fn as_text(&self) -> Option<&str> {
632 if let Self::Text(s) = self {
633 Some(s.as_str())
634 } else {
635 None
636 }
637 }
638
639 #[must_use]
640 pub const fn as_list(&self) -> Option<&[Self]> {
641 if let Self::List(xs) = self {
642 Some(xs.as_slice())
643 } else {
644 None
645 }
646 }
647
648 #[must_use]
649 pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
650 if let Self::Map(entries) = self {
651 Some(entries.as_slice())
652 } else {
653 None
654 }
655 }
656
657 fn to_decimal(&self) -> Option<Decimal> {
658 match self {
659 Self::Decimal(d) => d.try_to_decimal(),
660 Self::Duration(d) => d.try_to_decimal(),
661 Self::Float64(f) => f.try_to_decimal(),
662 Self::Float32(f) => f.try_to_decimal(),
663 Self::Int(i) => i.try_to_decimal(),
664 Self::Int128(i) => i.try_to_decimal(),
665 Self::IntBig(i) => i.try_to_decimal(),
666 Self::Timestamp(t) => t.try_to_decimal(),
667 Self::Uint(u) => u.try_to_decimal(),
668 Self::Uint128(u) => u.try_to_decimal(),
669 Self::UintBig(u) => u.try_to_decimal(),
670
671 _ => None,
672 }
673 }
674
675 pub(crate) fn to_numeric_decimal(&self) -> Option<Decimal> {
677 self.to_decimal()
678 }
679
680 #[expect(clippy::cast_precision_loss)]
682 fn to_f64_lossless(&self) -> Option<f64> {
683 match self {
684 Self::Duration(d) if d.repr() <= F64_SAFE_U64 => Some(d.repr() as f64),
685 Self::Float64(f) => Some(f.get()),
686 Self::Float32(f) => Some(f64::from(f.get())),
687 Self::Int(i) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(i) => Some(*i as f64),
688 Self::Int128(i) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(&i.get()) => {
689 Some(i.get() as f64)
690 }
691 Self::IntBig(i) => i.to_i128().and_then(|v| {
692 (-F64_SAFE_I128..=F64_SAFE_I128)
693 .contains(&v)
694 .then_some(v as f64)
695 }),
696 Self::Timestamp(t) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(&t.repr()) => {
697 Some(t.repr() as f64)
698 }
699 Self::Uint(u) if *u <= F64_SAFE_U64 => Some(*u as f64),
700 Self::Uint128(u) if u.get() <= F64_SAFE_U128 => Some(u.get() as f64),
701 Self::UintBig(u) => u
702 .to_u128()
703 .and_then(|v| (v <= F64_SAFE_U128).then_some(v as f64)),
704
705 _ => None,
706 }
707 }
708
709 #[must_use]
711 pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
712 if !self.supports_numeric_coercion() || !other.supports_numeric_coercion() {
713 return None;
714 }
715
716 match (self.numeric_repr(), other.numeric_repr()) {
717 (NumericRepr::Decimal(a), NumericRepr::Decimal(b)) => a.partial_cmp(&b),
718 (NumericRepr::F64(a), NumericRepr::F64(b)) => a.partial_cmp(&b),
719 _ => None,
720 }
721 }
722
723 fn fold_ci(s: &str) -> std::borrow::Cow<'_, str> {
728 if s.is_ascii() {
729 return std::borrow::Cow::Owned(s.to_ascii_lowercase());
730 }
731 std::borrow::Cow::Owned(s.to_lowercase())
734 }
735
736 fn text_with_mode(s: &'_ str, mode: TextMode) -> std::borrow::Cow<'_, str> {
737 match mode {
738 TextMode::Cs => std::borrow::Cow::Borrowed(s),
739 TextMode::Ci => Self::fold_ci(s),
740 }
741 }
742
743 fn text_op(
744 &self,
745 other: &Self,
746 mode: TextMode,
747 f: impl Fn(&str, &str) -> bool,
748 ) -> Option<bool> {
749 let (a, b) = (self.as_text()?, other.as_text()?);
750 let a = Self::text_with_mode(a, mode);
751 let b = Self::text_with_mode(b, mode);
752 Some(f(&a, &b))
753 }
754
755 fn ci_key(&self) -> Option<String> {
756 match self {
757 Self::Text(s) => Some(Self::fold_ci(s).into_owned()),
758 Self::Ulid(u) => Some(u.to_string().to_ascii_lowercase()),
759 Self::Principal(p) => Some(p.to_string().to_ascii_lowercase()),
760 Self::Account(a) => Some(a.to_string().to_ascii_lowercase()),
761 _ => None,
762 }
763 }
764
765 fn eq_ci(a: &Self, b: &Self) -> bool {
766 if let (Some(ak), Some(bk)) = (a.ci_key(), b.ci_key()) {
767 return ak == bk;
768 }
769
770 a == b
771 }
772
773 fn normalize_list_ref(v: &Self) -> Vec<&Self> {
774 match v {
775 Self::List(vs) => vs.iter().collect(),
776 v => vec![v],
777 }
778 }
779
780 fn contains_by<F>(&self, needle: &Self, eq: F) -> Option<bool>
781 where
782 F: Fn(&Self, &Self) -> bool,
783 {
784 self.as_list()
785 .map(|items| items.iter().any(|v| eq(v, needle)))
786 }
787
788 #[expect(clippy::unnecessary_wraps)]
789 fn contains_any_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
790 where
791 F: Fn(&Self, &Self) -> bool,
792 {
793 let needles = Self::normalize_list_ref(needles);
794 match self {
795 Self::List(items) => Some(needles.iter().any(|n| items.iter().any(|v| eq(v, n)))),
796 scalar => Some(needles.iter().any(|n| eq(scalar, n))),
797 }
798 }
799
800 #[expect(clippy::unnecessary_wraps)]
801 fn contains_all_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
802 where
803 F: Fn(&Self, &Self) -> bool,
804 {
805 let needles = Self::normalize_list_ref(needles);
806 match self {
807 Self::List(items) => Some(needles.iter().all(|n| items.iter().any(|v| eq(v, n)))),
808 scalar => Some(needles.len() == 1 && eq(scalar, needles[0])),
809 }
810 }
811
812 fn in_list_by<F>(&self, haystack: &Self, eq: F) -> Option<bool>
813 where
814 F: Fn(&Self, &Self) -> bool,
815 {
816 if let Self::List(items) = haystack {
817 Some(items.iter().any(|h| eq(h, self)))
818 } else {
819 None
820 }
821 }
822
823 #[must_use]
825 pub fn text_eq(&self, other: &Self, mode: TextMode) -> Option<bool> {
826 self.text_op(other, mode, |a, b| a == b)
827 }
828
829 #[must_use]
831 pub fn text_contains(&self, needle: &Self, mode: TextMode) -> Option<bool> {
832 self.text_op(needle, mode, |a, b| a.contains(b))
833 }
834
835 #[must_use]
837 pub fn text_starts_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
838 self.text_op(needle, mode, |a, b| a.starts_with(b))
839 }
840
841 #[must_use]
843 pub fn text_ends_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
844 self.text_op(needle, mode, |a, b| a.ends_with(b))
845 }
846
847 #[must_use]
852 pub const fn is_empty(&self) -> Option<bool> {
853 match self {
854 Self::List(xs) => Some(xs.is_empty()),
855 Self::Map(entries) => Some(entries.is_empty()),
856 Self::Text(s) => Some(s.is_empty()),
857 Self::Blob(b) => Some(b.is_empty()),
858
859 Self::Null => Some(true),
861
862 _ => None,
863 }
864 }
865
866 #[must_use]
868 pub fn is_not_empty(&self) -> Option<bool> {
869 self.is_empty().map(|b| !b)
870 }
871
872 #[must_use]
878 pub fn contains(&self, needle: &Self) -> Option<bool> {
879 self.contains_by(needle, |a, b| a == b)
880 }
881
882 #[must_use]
884 pub fn contains_any(&self, needles: &Self) -> Option<bool> {
885 self.contains_any_by(needles, |a, b| a == b)
886 }
887
888 #[must_use]
890 pub fn contains_all(&self, needles: &Self) -> Option<bool> {
891 self.contains_all_by(needles, |a, b| a == b)
892 }
893
894 #[must_use]
896 pub fn in_list(&self, haystack: &Self) -> Option<bool> {
897 self.in_list_by(haystack, |a, b| a == b)
898 }
899
900 #[must_use]
902 pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
903 match self {
904 Self::List(_) => self.contains_by(needle, Self::eq_ci),
905 _ => Some(Self::eq_ci(self, needle)),
906 }
907 }
908
909 #[must_use]
911 pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
912 self.contains_any_by(needles, Self::eq_ci)
913 }
914
915 #[must_use]
917 pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
918 self.contains_all_by(needles, Self::eq_ci)
919 }
920
921 #[must_use]
923 pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
924 self.in_list_by(haystack, Self::eq_ci)
925 }
926}
927
928impl RuntimeValueMeta for Value {
929 fn kind() -> crate::traits::RuntimeValueKind {
930 crate::traits::RuntimeValueKind::Atomic
931 }
932}
933
934impl RuntimeValueEncode for Value {
935 fn to_value(&self) -> Value {
936 self.clone()
937 }
938}
939
940impl RuntimeValueDecode for Value {
941 fn from_value(value: &Value) -> Option<Self> {
942 Some(value.clone())
943 }
944}
945
946#[macro_export]
947macro_rules! impl_from_for {
948 ( $( $type:ty => $variant:ident ),* $(,)? ) => {
949 $(
950 impl From<$type> for Value {
951 fn from(v: $type) -> Self {
952 Self::$variant(v.into())
953 }
954 }
955 )*
956 };
957}
958
959impl_from_for! {
960 Account => Account,
961 Date => Date,
962 Decimal => Decimal,
963 Duration => Duration,
964 bool => Bool,
965 i8 => Int,
966 i16 => Int,
967 i32 => Int,
968 i64 => Int,
969 i128 => Int128,
970 Int => IntBig,
971 Principal => Principal,
972 Subaccount => Subaccount,
973 &str => Text,
974 String => Text,
975 Timestamp => Timestamp,
976 u8 => Uint,
977 u16 => Uint,
978 u32 => Uint,
979 u64 => Uint,
980 u128 => Uint128,
981 Nat => UintBig,
982 Ulid => Ulid,
983}
984
985impl CoercionFamilyExt for Value {
986 fn coercion_family(&self) -> CoercionFamily {
992 scalar_registry!(value_coercion_family_from_registry, self)
993 }
994}
995
996impl From<Vec<Self>> for Value {
997 fn from(vec: Vec<Self>) -> Self {
998 Self::List(vec)
999 }
1000}
1001
1002impl TryFrom<Vec<(Self, Self)>> for Value {
1003 type Error = SchemaInvariantError;
1004
1005 fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
1006 Self::from_map(entries).map_err(Self::Error::from)
1007 }
1008}
1009
1010impl From<()> for Value {
1011 fn from((): ()) -> Self {
1012 Self::Unit
1013 }
1014}
1015
1016impl PartialOrd for Value {
1022 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1023 match (self, other) {
1024 (Self::Bool(a), Self::Bool(b)) => a.partial_cmp(b),
1025 (Self::Date(a), Self::Date(b)) => a.partial_cmp(b),
1026 (Self::Decimal(a), Self::Decimal(b)) => a.partial_cmp(b),
1027 (Self::Duration(a), Self::Duration(b)) => a.partial_cmp(b),
1028 (Self::Enum(a), Self::Enum(b)) => a.partial_cmp(b),
1029 (Self::Float32(a), Self::Float32(b)) => a.partial_cmp(b),
1030 (Self::Float64(a), Self::Float64(b)) => a.partial_cmp(b),
1031 (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
1032 (Self::Int128(a), Self::Int128(b)) => a.partial_cmp(b),
1033 (Self::IntBig(a), Self::IntBig(b)) => a.partial_cmp(b),
1034 (Self::Principal(a), Self::Principal(b)) => a.partial_cmp(b),
1035 (Self::Subaccount(a), Self::Subaccount(b)) => a.partial_cmp(b),
1036 (Self::Text(a), Self::Text(b)) => a.partial_cmp(b),
1037 (Self::Timestamp(a), Self::Timestamp(b)) => a.partial_cmp(b),
1038 (Self::Uint(a), Self::Uint(b)) => a.partial_cmp(b),
1039 (Self::Uint128(a), Self::Uint128(b)) => a.partial_cmp(b),
1040 (Self::UintBig(a), Self::UintBig(b)) => a.partial_cmp(b),
1041 (Self::Ulid(a), Self::Ulid(b)) => a.partial_cmp(b),
1042 (Self::Map(a), Self::Map(b)) => {
1043 for ((left_key, left_value), (right_key, right_value)) in a.iter().zip(b.iter()) {
1044 let key_cmp = Self::canonical_cmp_key(left_key, right_key);
1045 if key_cmp != Ordering::Equal {
1046 return Some(key_cmp);
1047 }
1048
1049 match left_value.partial_cmp(right_value) {
1050 Some(Ordering::Equal) => {}
1051 non_eq => return non_eq,
1052 }
1053 }
1054 a.len().partial_cmp(&b.len())
1055 }
1056
1057 _ => None,
1059 }
1060 }
1061}
1062
1063#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd)]
1069pub struct ValueEnum {
1070 variant: String,
1071 path: Option<String>,
1072 payload: Option<Box<Value>>,
1073}
1074
1075impl ValueEnum {
1076 #[must_use]
1078 pub fn new(variant: &str, path: Option<&str>) -> Self {
1079 Self {
1080 variant: variant.to_string(),
1081 path: path.map(ToString::to_string),
1082 payload: None,
1083 }
1084 }
1085
1086 #[must_use]
1088 pub fn strict<E: Path>(variant: &str) -> Self {
1089 Self::new(variant, Some(E::PATH))
1090 }
1091
1092 #[must_use]
1094 pub fn from_enum<E: EnumValue>(value: E) -> Self {
1095 value.to_value_enum()
1096 }
1097
1098 #[must_use]
1101 pub fn loose(variant: &str) -> Self {
1102 Self::new(variant, None)
1103 }
1104
1105 #[must_use]
1107 pub fn with_payload(mut self, payload: Value) -> Self {
1108 self.payload = Some(Box::new(payload));
1109 self
1110 }
1111
1112 #[must_use]
1113 pub fn variant(&self) -> &str {
1114 &self.variant
1115 }
1116
1117 #[must_use]
1118 pub fn path(&self) -> Option<&str> {
1119 self.path.as_deref()
1120 }
1121
1122 #[must_use]
1123 pub fn payload(&self) -> Option<&Value> {
1124 self.payload.as_deref()
1125 }
1126
1127 pub(crate) fn set_path(&mut self, path: Option<String>) {
1128 self.path = path;
1129 }
1130}