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