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