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]
715 pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
716 if !self.supports_numeric_coercion() || !other.supports_numeric_coercion() {
717 return None;
718 }
719
720 match (self.numeric_repr(), other.numeric_repr()) {
721 (NumericRepr::Decimal(a), NumericRepr::Decimal(b)) => a.partial_cmp(&b),
722 (NumericRepr::F64(a), NumericRepr::F64(b)) => a.partial_cmp(&b),
723 _ => None,
724 }
725 }
726
727 fn fold_ci(s: &str) -> std::borrow::Cow<'_, str> {
732 if s.is_ascii() {
733 return std::borrow::Cow::Owned(s.to_ascii_lowercase());
734 }
735 std::borrow::Cow::Owned(s.to_lowercase())
738 }
739
740 fn text_with_mode(s: &'_ str, mode: TextMode) -> std::borrow::Cow<'_, str> {
741 match mode {
742 TextMode::Cs => std::borrow::Cow::Borrowed(s),
743 TextMode::Ci => Self::fold_ci(s),
744 }
745 }
746
747 fn text_op(
748 &self,
749 other: &Self,
750 mode: TextMode,
751 f: impl Fn(&str, &str) -> bool,
752 ) -> Option<bool> {
753 let (a, b) = (self.as_text()?, other.as_text()?);
754 let a = Self::text_with_mode(a, mode);
755 let b = Self::text_with_mode(b, mode);
756 Some(f(&a, &b))
757 }
758
759 fn ci_key(&self) -> Option<String> {
760 match self {
761 Self::Text(s) => Some(Self::fold_ci(s).into_owned()),
762 Self::Ulid(u) => Some(u.to_string().to_ascii_lowercase()),
763 Self::Principal(p) => Some(p.to_string().to_ascii_lowercase()),
764 Self::Account(a) => Some(a.to_string().to_ascii_lowercase()),
765 _ => None,
766 }
767 }
768
769 fn eq_ci(a: &Self, b: &Self) -> bool {
770 if let (Some(ak), Some(bk)) = (a.ci_key(), b.ci_key()) {
771 return ak == bk;
772 }
773
774 a == b
775 }
776
777 fn normalize_list_ref(v: &Self) -> Vec<&Self> {
778 match v {
779 Self::List(vs) => vs.iter().collect(),
780 v => vec![v],
781 }
782 }
783
784 fn contains_by<F>(&self, needle: &Self, eq: F) -> Option<bool>
785 where
786 F: Fn(&Self, &Self) -> bool,
787 {
788 self.as_list()
789 .map(|items| items.iter().any(|v| eq(v, needle)))
790 }
791
792 #[expect(clippy::unnecessary_wraps)]
793 fn contains_any_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
794 where
795 F: Fn(&Self, &Self) -> bool,
796 {
797 let needles = Self::normalize_list_ref(needles);
798 match self {
799 Self::List(items) => Some(needles.iter().any(|n| items.iter().any(|v| eq(v, n)))),
800 scalar => Some(needles.iter().any(|n| eq(scalar, n))),
801 }
802 }
803
804 #[expect(clippy::unnecessary_wraps)]
805 fn contains_all_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
806 where
807 F: Fn(&Self, &Self) -> bool,
808 {
809 let needles = Self::normalize_list_ref(needles);
810 match self {
811 Self::List(items) => Some(needles.iter().all(|n| items.iter().any(|v| eq(v, n)))),
812 scalar => Some(needles.len() == 1 && eq(scalar, needles[0])),
813 }
814 }
815
816 fn in_list_by<F>(&self, haystack: &Self, eq: F) -> Option<bool>
817 where
818 F: Fn(&Self, &Self) -> bool,
819 {
820 if let Self::List(items) = haystack {
821 Some(items.iter().any(|h| eq(h, self)))
822 } else {
823 None
824 }
825 }
826
827 #[must_use]
829 pub fn text_eq(&self, other: &Self, mode: TextMode) -> Option<bool> {
830 self.text_op(other, mode, |a, b| a == b)
831 }
832
833 #[must_use]
835 pub fn text_contains(&self, needle: &Self, mode: TextMode) -> Option<bool> {
836 self.text_op(needle, mode, |a, b| a.contains(b))
837 }
838
839 #[must_use]
841 pub fn text_starts_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
842 self.text_op(needle, mode, |a, b| a.starts_with(b))
843 }
844
845 #[must_use]
847 pub fn text_ends_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
848 self.text_op(needle, mode, |a, b| a.ends_with(b))
849 }
850
851 #[must_use]
856 pub const fn is_empty(&self) -> Option<bool> {
857 match self {
858 Self::List(xs) => Some(xs.is_empty()),
859 Self::Map(entries) => Some(entries.is_empty()),
860 Self::Text(s) => Some(s.is_empty()),
861 Self::Blob(b) => Some(b.is_empty()),
862
863 Self::Null => Some(true),
865
866 _ => None,
867 }
868 }
869
870 #[must_use]
872 pub fn is_not_empty(&self) -> Option<bool> {
873 self.is_empty().map(|b| !b)
874 }
875
876 #[must_use]
882 pub fn contains(&self, needle: &Self) -> Option<bool> {
883 self.contains_by(needle, |a, b| a == b)
884 }
885
886 #[must_use]
888 pub fn contains_any(&self, needles: &Self) -> Option<bool> {
889 self.contains_any_by(needles, |a, b| a == b)
890 }
891
892 #[must_use]
894 pub fn contains_all(&self, needles: &Self) -> Option<bool> {
895 self.contains_all_by(needles, |a, b| a == b)
896 }
897
898 #[must_use]
900 pub fn in_list(&self, haystack: &Self) -> Option<bool> {
901 self.in_list_by(haystack, |a, b| a == b)
902 }
903
904 #[must_use]
906 pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
907 match self {
908 Self::List(_) => self.contains_by(needle, Self::eq_ci),
909 _ => Some(Self::eq_ci(self, needle)),
910 }
911 }
912
913 #[must_use]
915 pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
916 self.contains_any_by(needles, Self::eq_ci)
917 }
918
919 #[must_use]
921 pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
922 self.contains_all_by(needles, Self::eq_ci)
923 }
924
925 #[must_use]
927 pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
928 self.in_list_by(haystack, Self::eq_ci)
929 }
930}
931
932impl RuntimeValueMeta for Value {
933 fn kind() -> crate::traits::RuntimeValueKind {
934 crate::traits::RuntimeValueKind::Atomic
935 }
936}
937
938impl RuntimeValueEncode for Value {
939 fn to_value(&self) -> Value {
940 self.clone()
941 }
942}
943
944impl RuntimeValueDecode for Value {
945 fn from_value(value: &Value) -> Option<Self> {
946 Some(value.clone())
947 }
948}
949
950#[macro_export]
951macro_rules! impl_from_for {
952 ( $( $type:ty => $variant:ident ),* $(,)? ) => {
953 $(
954 impl From<$type> for Value {
955 fn from(v: $type) -> Self {
956 Self::$variant(v.into())
957 }
958 }
959 )*
960 };
961}
962
963impl_from_for! {
964 Account => Account,
965 Date => Date,
966 Decimal => Decimal,
967 Duration => Duration,
968 bool => Bool,
969 i8 => Int,
970 i16 => Int,
971 i32 => Int,
972 i64 => Int,
973 i128 => Int128,
974 Int => IntBig,
975 Principal => Principal,
976 Subaccount => Subaccount,
977 &str => Text,
978 String => Text,
979 Timestamp => Timestamp,
980 u8 => Uint,
981 u16 => Uint,
982 u32 => Uint,
983 u64 => Uint,
984 u128 => Uint128,
985 Nat => UintBig,
986 Ulid => Ulid,
987}
988
989impl CoercionFamilyExt for Value {
990 fn coercion_family(&self) -> CoercionFamily {
996 scalar_registry!(value_coercion_family_from_registry, self)
997 }
998}
999
1000impl From<Vec<Self>> for Value {
1001 fn from(vec: Vec<Self>) -> Self {
1002 Self::List(vec)
1003 }
1004}
1005
1006impl TryFrom<Vec<(Self, Self)>> for Value {
1007 type Error = SchemaInvariantError;
1008
1009 fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
1010 Self::from_map(entries).map_err(Self::Error::from)
1011 }
1012}
1013
1014impl From<()> for Value {
1015 fn from((): ()) -> Self {
1016 Self::Unit
1017 }
1018}
1019
1020impl PartialOrd for Value {
1026 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1027 match (self, other) {
1028 (Self::Bool(a), Self::Bool(b)) => a.partial_cmp(b),
1029 (Self::Date(a), Self::Date(b)) => a.partial_cmp(b),
1030 (Self::Decimal(a), Self::Decimal(b)) => a.partial_cmp(b),
1031 (Self::Duration(a), Self::Duration(b)) => a.partial_cmp(b),
1032 (Self::Enum(a), Self::Enum(b)) => a.partial_cmp(b),
1033 (Self::Float32(a), Self::Float32(b)) => a.partial_cmp(b),
1034 (Self::Float64(a), Self::Float64(b)) => a.partial_cmp(b),
1035 (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
1036 (Self::Int128(a), Self::Int128(b)) => a.partial_cmp(b),
1037 (Self::IntBig(a), Self::IntBig(b)) => a.partial_cmp(b),
1038 (Self::Principal(a), Self::Principal(b)) => a.partial_cmp(b),
1039 (Self::Subaccount(a), Self::Subaccount(b)) => a.partial_cmp(b),
1040 (Self::Text(a), Self::Text(b)) => a.partial_cmp(b),
1041 (Self::Timestamp(a), Self::Timestamp(b)) => a.partial_cmp(b),
1042 (Self::Uint(a), Self::Uint(b)) => a.partial_cmp(b),
1043 (Self::Uint128(a), Self::Uint128(b)) => a.partial_cmp(b),
1044 (Self::UintBig(a), Self::UintBig(b)) => a.partial_cmp(b),
1045 (Self::Ulid(a), Self::Ulid(b)) => a.partial_cmp(b),
1046 (Self::Map(a), Self::Map(b)) => {
1047 for ((left_key, left_value), (right_key, right_value)) in a.iter().zip(b.iter()) {
1048 let key_cmp = Self::canonical_cmp_key(left_key, right_key);
1049 if key_cmp != Ordering::Equal {
1050 return Some(key_cmp);
1051 }
1052
1053 match left_value.partial_cmp(right_value) {
1054 Some(Ordering::Equal) => {}
1055 non_eq => return non_eq,
1056 }
1057 }
1058 a.len().partial_cmp(&b.len())
1059 }
1060
1061 _ => None,
1063 }
1064 }
1065}
1066
1067#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd)]
1073pub struct ValueEnum {
1074 variant: String,
1075 path: Option<String>,
1076 payload: Option<Box<Value>>,
1077}
1078
1079impl ValueEnum {
1080 #[must_use]
1082 pub fn new(variant: &str, path: Option<&str>) -> Self {
1083 Self {
1084 variant: variant.to_string(),
1085 path: path.map(ToString::to_string),
1086 payload: None,
1087 }
1088 }
1089
1090 #[must_use]
1092 pub fn strict<E: Path>(variant: &str) -> Self {
1093 Self::new(variant, Some(E::PATH))
1094 }
1095
1096 #[must_use]
1098 pub fn from_enum<E: EnumValue>(value: E) -> Self {
1099 value.to_value_enum()
1100 }
1101
1102 #[must_use]
1105 pub fn loose(variant: &str) -> Self {
1106 Self::new(variant, None)
1107 }
1108
1109 #[must_use]
1111 pub fn with_payload(mut self, payload: Value) -> Self {
1112 self.payload = Some(Box::new(payload));
1113 self
1114 }
1115
1116 #[must_use]
1117 pub fn variant(&self) -> &str {
1118 &self.variant
1119 }
1120
1121 #[must_use]
1122 pub fn path(&self) -> Option<&str> {
1123 self.path.as_deref()
1124 }
1125
1126 #[must_use]
1127 pub fn payload(&self) -> Option<&Value> {
1128 self.payload.as_deref()
1129 }
1130
1131 pub(crate) fn set_path(&mut self, path: Option<String>) {
1132 self.path = path;
1133 }
1134}