1mod coercion;
2mod compare;
3mod rank;
4mod tag;
5mod wire;
6
7#[cfg(test)]
8mod tests;
9
10use crate::{
11 db::StorageKey,
12 prelude::*,
13 traits::{EnumValue, FieldValue, NumFromPrimitive},
14 types::*,
15};
16use candid::CandidType;
17use serde::{Deserialize, Serialize};
18use std::cmp::Ordering;
19
20pub use coercion::{CoercionFamily, CoercionFamilyExt};
22pub use tag::ValueTag;
23
24const F64_SAFE_I64: i64 = 1i64 << 53;
29const F64_SAFE_U64: u64 = 1u64 << 53;
30const F64_SAFE_I128: i128 = 1i128 << 53;
31const F64_SAFE_U128: u128 = 1u128 << 53;
32
33enum NumericRepr {
38 Decimal(Decimal),
39 F64(f64),
40 None,
41}
42
43#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub enum TextMode {
49 Cs, Ci, }
52
53#[derive(Clone, Debug, Eq, PartialEq)]
60pub enum MapValueError {
61 EmptyKey {
62 index: usize,
63 },
64 NonScalarKey {
65 index: usize,
66 key: Value,
67 },
68 NonScalarValue {
69 index: usize,
70 value: Value,
71 },
72 DuplicateKey {
73 left_index: usize,
74 right_index: usize,
75 },
76}
77
78impl std::fmt::Display for MapValueError {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 match self {
81 Self::EmptyKey { index } => write!(f, "map key at index {index} must be non-null"),
82 Self::NonScalarKey { index, key } => {
83 write!(f, "map key at index {index} is not scalar: {key:?}")
84 }
85 Self::NonScalarValue { index, value } => {
86 write!(
87 f,
88 "map value at index {index} is not scalar/ref-like: {value:?}"
89 )
90 }
91 Self::DuplicateKey {
92 left_index,
93 right_index,
94 } => write!(
95 f,
96 "map contains duplicate keys at normalized positions {left_index} and {right_index}"
97 ),
98 }
99 }
100}
101
102impl std::error::Error for MapValueError {}
103
104#[derive(Clone, Debug, Eq, PartialEq)]
111pub enum SchemaInvariantError {
112 InvalidMapValue(MapValueError),
113}
114
115impl std::fmt::Display for SchemaInvariantError {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 match self {
118 Self::InvalidMapValue(err) => write!(f, "{err}"),
119 }
120 }
121}
122
123impl std::error::Error for SchemaInvariantError {}
124
125impl From<MapValueError> for SchemaInvariantError {
126 fn from(value: MapValueError) -> Self {
127 Self::InvalidMapValue(value)
128 }
129}
130
131#[derive(CandidType, Clone, Debug, Eq, PartialEq, Serialize)]
140pub enum Value {
141 Account(Account),
142 Blob(Vec<u8>),
143 Bool(bool),
144 Date(Date),
145 Decimal(Decimal),
146 Duration(Duration),
147 Enum(ValueEnum),
148 E8s(E8s),
149 E18s(E18s),
150 Float32(Float32),
151 Float64(Float64),
152 Int(i64),
153 Int128(Int128),
154 IntBig(Int),
155 List(Vec<Self>),
159 Map(Vec<(Self, Self)>),
166 Null,
167 Principal(Principal),
168 Subaccount(Subaccount),
169 Text(String),
170 Timestamp(Timestamp),
171 Uint(u64),
172 Uint128(Nat128),
173 UintBig(Nat),
174 Ulid(Ulid),
175 Unit,
176}
177
178macro_rules! value_is_numeric_from_registry {
180 ( @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) ),* $(,)? ) => {
181 match $value {
182 $( $value_pat => $is_numeric, )*
183 _ => false,
184 }
185 };
186}
187
188macro_rules! value_supports_numeric_coercion_from_registry {
189 ( @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) ),* $(,)? ) => {
190 match $value {
191 $( $value_pat => $supports_numeric_coercion, )*
192 _ => false,
193 }
194 };
195}
196
197macro_rules! value_storage_key_case {
198 ( $value:expr, Unit, true ) => {
199 if let Value::Unit = $value {
200 Some(StorageKey::Unit)
201 } else {
202 None
203 }
204 };
205 ( $value:expr, $scalar:ident, true ) => {
206 if let Value::$scalar(v) = $value {
207 Some(StorageKey::$scalar(*v))
208 } else {
209 None
210 }
211 };
212 ( $value:expr, $scalar:ident, false ) => {
213 None
214 };
215}
216
217macro_rules! value_storage_key_from_registry {
218 ( @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) ),* $(,)? ) => {
219 {
220 let mut key = None;
221 $(
222 match key {
223 Some(_) => {}
224 None => {
225 key = value_storage_key_case!($value, $scalar, $is_storage_key_encodable);
226 }
227 }
228 )*
229 key
230 }
231 };
232}
233
234macro_rules! value_coercion_family_from_registry {
235 ( @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) ),* $(,)? ) => {
236 match $value {
237 $( $value_pat => $coercion_family, )*
238 Value::List(_) => CoercionFamily::Collection,
239 Value::Map(_) => CoercionFamily::Collection,
240 Value::Null => CoercionFamily::Null,
241 }
242 };
243}
244
245impl Value {
246 pub fn from_slice<T>(items: &[T]) -> Self
255 where
256 T: Into<Self> + Clone,
257 {
258 Self::List(items.iter().cloned().map(Into::into).collect())
259 }
260
261 pub fn from_list<T>(items: Vec<T>) -> Self
265 where
266 T: Into<Self>,
267 {
268 Self::List(items.into_iter().map(Into::into).collect())
269 }
270
271 pub fn from_map(entries: Vec<(Self, Self)>) -> Result<Self, MapValueError> {
279 let normalized = Self::normalize_map_entries(entries)?;
280 Ok(Self::Map(normalized))
281 }
282
283 pub fn validate_map_entries(entries: &[(Self, Self)]) -> Result<(), MapValueError> {
285 for (index, (key, value)) in entries.iter().enumerate() {
286 if matches!(key, Self::Null) {
287 return Err(MapValueError::EmptyKey { index });
288 }
289 if !key.is_scalar() {
290 return Err(MapValueError::NonScalarKey {
291 index,
292 key: key.clone(),
293 });
294 }
295
296 if !value.is_scalar() {
297 return Err(MapValueError::NonScalarValue {
298 index,
299 value: value.clone(),
300 });
301 }
302 }
303
304 Ok(())
305 }
306
307 pub fn normalize_map_entries(
309 mut entries: Vec<(Self, Self)>,
310 ) -> Result<Vec<(Self, Self)>, MapValueError> {
311 Self::validate_map_entries(&entries)?;
312 entries
313 .sort_by(|(left_key, _), (right_key, _)| Self::canonical_cmp_key(left_key, right_key));
314
315 for i in 1..entries.len() {
316 let (left_key, _) = &entries[i - 1];
317 let (right_key, _) = &entries[i];
318 if Self::canonical_cmp_key(left_key, right_key) == Ordering::Equal {
319 return Err(MapValueError::DuplicateKey {
320 left_index: i - 1,
321 right_index: i,
322 });
323 }
324 }
325
326 Ok(entries)
327 }
328
329 pub fn from_enum<E: EnumValue>(value: E) -> Self {
331 Self::Enum(value.to_value_enum())
332 }
333
334 #[must_use]
336 pub fn enum_strict<E: Path>(variant: &str) -> Self {
337 Self::Enum(ValueEnum::strict::<E>(variant))
338 }
339
340 #[must_use]
347 pub const fn is_numeric(&self) -> bool {
348 scalar_registry!(value_is_numeric_from_registry, self)
349 }
350
351 #[must_use]
353 pub const fn supports_numeric_coercion(&self) -> bool {
354 scalar_registry!(value_supports_numeric_coercion_from_registry, self)
355 }
356
357 #[must_use]
359 pub const fn is_text(&self) -> bool {
360 matches!(self, Self::Text(_))
361 }
362
363 #[must_use]
365 pub const fn is_unit(&self) -> bool {
366 matches!(self, Self::Unit)
367 }
368
369 #[must_use]
370 pub const fn is_scalar(&self) -> bool {
371 match self {
372 Self::List(_) | Self::Map(_) | Self::Unit => false,
374 _ => true,
375 }
376 }
377
378 #[must_use]
380 pub(crate) const fn canonical_tag(&self) -> ValueTag {
381 tag::canonical_tag(self)
382 }
383
384 #[must_use]
386 pub(crate) const fn canonical_rank(&self) -> u8 {
387 rank::canonical_rank(self)
388 }
389
390 #[must_use]
392 pub(crate) fn canonical_cmp(left: &Self, right: &Self) -> Ordering {
393 compare::canonical_cmp(left, right)
394 }
395
396 #[must_use]
398 pub fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
399 compare::canonical_cmp_key(left, right)
400 }
401
402 #[must_use]
406 pub(crate) fn strict_order_cmp(left: &Self, right: &Self) -> Option<Ordering> {
407 compare::strict_order_cmp(left, right)
408 }
409
410 fn numeric_repr(&self) -> NumericRepr {
411 if !self.supports_numeric_coercion() {
413 return NumericRepr::None;
414 }
415
416 if let Some(d) = self.to_decimal() {
417 return NumericRepr::Decimal(d);
418 }
419 if let Some(f) = self.to_f64_lossless() {
420 return NumericRepr::F64(f);
421 }
422 NumericRepr::None
423 }
424
425 #[must_use]
434 pub const fn as_storage_key(&self) -> Option<StorageKey> {
435 scalar_registry!(value_storage_key_from_registry, self)
436 }
437
438 #[must_use]
439 pub const fn as_text(&self) -> Option<&str> {
440 if let Self::Text(s) = self {
441 Some(s.as_str())
442 } else {
443 None
444 }
445 }
446
447 #[must_use]
448 pub const fn as_list(&self) -> Option<&[Self]> {
449 if let Self::List(xs) = self {
450 Some(xs.as_slice())
451 } else {
452 None
453 }
454 }
455
456 #[must_use]
457 pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
458 if let Self::Map(entries) = self {
459 Some(entries.as_slice())
460 } else {
461 None
462 }
463 }
464
465 fn to_decimal(&self) -> Option<Decimal> {
466 match self {
467 Self::Decimal(d) => Some(*d),
468 Self::Duration(d) => Decimal::from_u64(d.get()),
469 Self::E8s(v) => Some(v.to_decimal()),
470 Self::E18s(v) => v.to_decimal(),
471 Self::Float64(f) => Decimal::from_f64(f.get()),
472 Self::Float32(f) => Decimal::from_f32(f.get()),
473 Self::Int(i) => Decimal::from_i64(*i),
474 Self::Int128(i) => Decimal::from_i128(i.get()),
475 Self::IntBig(i) => i.to_i128().and_then(Decimal::from_i128),
476 Self::Timestamp(t) => Decimal::from_u64(t.get()),
477 Self::Uint(u) => Decimal::from_u64(*u),
478 Self::Uint128(u) => Decimal::from_u128(u.get()),
479 Self::UintBig(u) => u.to_u128().and_then(Decimal::from_u128),
480
481 _ => None,
482 }
483 }
484
485 #[expect(clippy::cast_precision_loss)]
487 fn to_f64_lossless(&self) -> Option<f64> {
488 match self {
489 Self::Duration(d) if d.get() <= F64_SAFE_U64 => Some(d.get() as f64),
490 Self::Float64(f) => Some(f.get()),
491 Self::Float32(f) => Some(f64::from(f.get())),
492 Self::Int(i) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(i) => Some(*i as f64),
493 Self::Int128(i) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(&i.get()) => {
494 Some(i.get() as f64)
495 }
496 Self::IntBig(i) => i.to_i128().and_then(|v| {
497 (-F64_SAFE_I128..=F64_SAFE_I128)
498 .contains(&v)
499 .then_some(v as f64)
500 }),
501 Self::Timestamp(t) if t.get() <= F64_SAFE_U64 => Some(t.get() as f64),
502 Self::Uint(u) if *u <= F64_SAFE_U64 => Some(*u as f64),
503 Self::Uint128(u) if u.get() <= F64_SAFE_U128 => Some(u.get() as f64),
504 Self::UintBig(u) => u
505 .to_u128()
506 .and_then(|v| (v <= F64_SAFE_U128).then_some(v as f64)),
507
508 _ => None,
509 }
510 }
511
512 #[must_use]
514 pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
515 if !self.supports_numeric_coercion() || !other.supports_numeric_coercion() {
516 return None;
517 }
518
519 match (self.numeric_repr(), other.numeric_repr()) {
520 (NumericRepr::Decimal(a), NumericRepr::Decimal(b)) => a.partial_cmp(&b),
521 (NumericRepr::F64(a), NumericRepr::F64(b)) => a.partial_cmp(&b),
522 _ => None,
523 }
524 }
525
526 fn fold_ci(s: &str) -> std::borrow::Cow<'_, str> {
531 if s.is_ascii() {
532 return std::borrow::Cow::Owned(s.to_ascii_lowercase());
533 }
534 std::borrow::Cow::Owned(s.to_lowercase())
537 }
538
539 fn text_with_mode(s: &'_ str, mode: TextMode) -> std::borrow::Cow<'_, str> {
540 match mode {
541 TextMode::Cs => std::borrow::Cow::Borrowed(s),
542 TextMode::Ci => Self::fold_ci(s),
543 }
544 }
545
546 fn text_op(
547 &self,
548 other: &Self,
549 mode: TextMode,
550 f: impl Fn(&str, &str) -> bool,
551 ) -> Option<bool> {
552 let (a, b) = (self.as_text()?, other.as_text()?);
553 let a = Self::text_with_mode(a, mode);
554 let b = Self::text_with_mode(b, mode);
555 Some(f(&a, &b))
556 }
557
558 fn ci_key(&self) -> Option<String> {
559 match self {
560 Self::Text(s) => Some(Self::fold_ci(s).into_owned()),
561 Self::Ulid(u) => Some(u.to_string().to_ascii_lowercase()),
562 Self::Principal(p) => Some(p.to_string().to_ascii_lowercase()),
563 Self::Account(a) => Some(a.to_string().to_ascii_lowercase()),
564 _ => None,
565 }
566 }
567
568 fn eq_ci(a: &Self, b: &Self) -> bool {
569 if let (Some(ak), Some(bk)) = (a.ci_key(), b.ci_key()) {
570 return ak == bk;
571 }
572
573 a == b
574 }
575
576 fn normalize_list_ref(v: &Self) -> Vec<&Self> {
577 match v {
578 Self::List(vs) => vs.iter().collect(),
579 v => vec![v],
580 }
581 }
582
583 fn contains_by<F>(&self, needle: &Self, eq: F) -> Option<bool>
584 where
585 F: Fn(&Self, &Self) -> bool,
586 {
587 self.as_list()
588 .map(|items| items.iter().any(|v| eq(v, needle)))
589 }
590
591 #[expect(clippy::unnecessary_wraps)]
592 fn contains_any_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
593 where
594 F: Fn(&Self, &Self) -> bool,
595 {
596 let needles = Self::normalize_list_ref(needles);
597 match self {
598 Self::List(items) => Some(needles.iter().any(|n| items.iter().any(|v| eq(v, n)))),
599 scalar => Some(needles.iter().any(|n| eq(scalar, n))),
600 }
601 }
602
603 #[expect(clippy::unnecessary_wraps)]
604 fn contains_all_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
605 where
606 F: Fn(&Self, &Self) -> bool,
607 {
608 let needles = Self::normalize_list_ref(needles);
609 match self {
610 Self::List(items) => Some(needles.iter().all(|n| items.iter().any(|v| eq(v, n)))),
611 scalar => Some(needles.len() == 1 && eq(scalar, needles[0])),
612 }
613 }
614
615 fn in_list_by<F>(&self, haystack: &Self, eq: F) -> Option<bool>
616 where
617 F: Fn(&Self, &Self) -> bool,
618 {
619 if let Self::List(items) = haystack {
620 Some(items.iter().any(|h| eq(h, self)))
621 } else {
622 None
623 }
624 }
625
626 #[must_use]
627 pub fn text_eq(&self, other: &Self, mode: TextMode) -> Option<bool> {
629 self.text_op(other, mode, |a, b| a == b)
630 }
631
632 #[must_use]
633 pub fn text_contains(&self, needle: &Self, mode: TextMode) -> Option<bool> {
635 self.text_op(needle, mode, |a, b| a.contains(b))
636 }
637
638 #[must_use]
639 pub fn text_starts_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
641 self.text_op(needle, mode, |a, b| a.starts_with(b))
642 }
643
644 #[must_use]
645 pub fn text_ends_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
647 self.text_op(needle, mode, |a, b| a.ends_with(b))
648 }
649
650 #[must_use]
655 pub const fn is_empty(&self) -> Option<bool> {
656 match self {
657 Self::List(xs) => Some(xs.is_empty()),
658 Self::Map(entries) => Some(entries.is_empty()),
659 Self::Text(s) => Some(s.is_empty()),
660 Self::Blob(b) => Some(b.is_empty()),
661
662 Self::Null => Some(true),
664
665 _ => None,
666 }
667 }
668
669 #[must_use]
670 pub fn is_not_empty(&self) -> Option<bool> {
672 self.is_empty().map(|b| !b)
673 }
674
675 #[must_use]
680 pub fn contains(&self, needle: &Self) -> Option<bool> {
682 self.contains_by(needle, |a, b| a == b)
683 }
684
685 #[must_use]
686 pub fn contains_any(&self, needles: &Self) -> Option<bool> {
688 self.contains_any_by(needles, |a, b| a == b)
689 }
690
691 #[must_use]
692 pub fn contains_all(&self, needles: &Self) -> Option<bool> {
694 self.contains_all_by(needles, |a, b| a == b)
695 }
696
697 #[must_use]
698 pub fn in_list(&self, haystack: &Self) -> Option<bool> {
700 self.in_list_by(haystack, |a, b| a == b)
701 }
702
703 #[must_use]
704 pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
706 match self {
707 Self::List(_) => self.contains_by(needle, Self::eq_ci),
708 _ => Some(Self::eq_ci(self, needle)),
709 }
710 }
711
712 #[must_use]
713 pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
715 self.contains_any_by(needles, Self::eq_ci)
716 }
717
718 #[must_use]
719 pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
721 self.contains_all_by(needles, Self::eq_ci)
722 }
723
724 #[must_use]
725 pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
727 self.in_list_by(haystack, Self::eq_ci)
728 }
729}
730
731impl FieldValue for Value {
732 fn kind() -> crate::traits::FieldValueKind {
733 crate::traits::FieldValueKind::Atomic
734 }
735
736 fn to_value(&self) -> Value {
737 self.clone()
738 }
739
740 fn from_value(value: &Value) -> Option<Self> {
741 Some(value.clone())
742 }
743}
744
745#[macro_export]
746macro_rules! impl_from_for {
747 ( $( $type:ty => $variant:ident ),* $(,)? ) => {
748 $(
749 impl From<$type> for Value {
750 fn from(v: $type) -> Self {
751 Self::$variant(v.into())
752 }
753 }
754 )*
755 };
756}
757
758impl_from_for! {
759 Account => Account,
760 Date => Date,
761 Decimal => Decimal,
762 Duration => Duration,
763 E8s => E8s,
764 E18s => E18s,
765 bool => Bool,
766 i8 => Int,
767 i16 => Int,
768 i32 => Int,
769 i64 => Int,
770 i128 => Int128,
771 Int => IntBig,
772 Principal => Principal,
773 Subaccount => Subaccount,
774 &str => Text,
775 String => Text,
776 Timestamp => Timestamp,
777 u8 => Uint,
778 u16 => Uint,
779 u32 => Uint,
780 u64 => Uint,
781 u128 => Uint128,
782 Nat => UintBig,
783 Ulid => Ulid,
784}
785
786impl CoercionFamilyExt for Value {
787 fn coercion_family(&self) -> CoercionFamily {
793 scalar_registry!(value_coercion_family_from_registry, self)
794 }
795}
796
797impl From<Vec<Self>> for Value {
798 fn from(vec: Vec<Self>) -> Self {
799 Self::List(vec)
800 }
801}
802
803impl TryFrom<Vec<(Self, Self)>> for Value {
804 type Error = SchemaInvariantError;
805
806 fn try_from(entries: Vec<(Self, Self)>) -> Result<Self, Self::Error> {
807 Self::from_map(entries).map_err(Self::Error::from)
808 }
809}
810
811impl From<()> for Value {
812 fn from((): ()) -> Self {
813 Self::Unit
814 }
815}
816
817impl PartialOrd for Value {
823 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
824 match (self, other) {
825 (Self::Bool(a), Self::Bool(b)) => a.partial_cmp(b),
826 (Self::Date(a), Self::Date(b)) => a.partial_cmp(b),
827 (Self::Decimal(a), Self::Decimal(b)) => a.partial_cmp(b),
828 (Self::Duration(a), Self::Duration(b)) => a.partial_cmp(b),
829 (Self::E8s(a), Self::E8s(b)) => a.partial_cmp(b),
830 (Self::E18s(a), Self::E18s(b)) => a.partial_cmp(b),
831 (Self::Enum(a), Self::Enum(b)) => a.partial_cmp(b),
832 (Self::Float32(a), Self::Float32(b)) => a.partial_cmp(b),
833 (Self::Float64(a), Self::Float64(b)) => a.partial_cmp(b),
834 (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
835 (Self::Int128(a), Self::Int128(b)) => a.partial_cmp(b),
836 (Self::IntBig(a), Self::IntBig(b)) => a.partial_cmp(b),
837 (Self::Principal(a), Self::Principal(b)) => a.partial_cmp(b),
838 (Self::Subaccount(a), Self::Subaccount(b)) => a.partial_cmp(b),
839 (Self::Text(a), Self::Text(b)) => a.partial_cmp(b),
840 (Self::Timestamp(a), Self::Timestamp(b)) => a.partial_cmp(b),
841 (Self::Uint(a), Self::Uint(b)) => a.partial_cmp(b),
842 (Self::Uint128(a), Self::Uint128(b)) => a.partial_cmp(b),
843 (Self::UintBig(a), Self::UintBig(b)) => a.partial_cmp(b),
844 (Self::Ulid(a), Self::Ulid(b)) => a.partial_cmp(b),
845 (Self::Map(a), Self::Map(b)) => {
846 for ((left_key, left_value), (right_key, right_value)) in a.iter().zip(b.iter()) {
847 let key_cmp = Self::canonical_cmp_key(left_key, right_key);
848 if key_cmp != Ordering::Equal {
849 return Some(key_cmp);
850 }
851
852 match left_value.partial_cmp(right_value) {
853 Some(Ordering::Equal) => {}
854 non_eq => return non_eq,
855 }
856 }
857 a.len().partial_cmp(&b.len())
858 }
859
860 _ => None,
862 }
863 }
864}
865
866#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd, Serialize)]
872pub struct ValueEnum {
873 pub variant: String,
874 pub path: Option<String>,
875 pub payload: Option<Box<Value>>,
876}
877
878impl ValueEnum {
879 #[must_use]
880 pub fn new(variant: &str, path: Option<&str>) -> Self {
882 Self {
883 variant: variant.to_string(),
884 path: path.map(ToString::to_string),
885 payload: None,
886 }
887 }
888
889 #[must_use]
890 pub fn strict<E: Path>(variant: &str) -> Self {
892 Self::new(variant, Some(E::PATH))
893 }
894
895 #[must_use]
896 pub fn from_enum<E: EnumValue>(value: E) -> Self {
898 value.to_value_enum()
899 }
900
901 #[must_use]
902 pub fn loose(variant: &str) -> Self {
904 Self::new(variant, None)
905 }
906
907 #[must_use]
908 pub fn with_payload(mut self, payload: Value) -> Self {
910 self.payload = Some(Box::new(payload));
911 self
912 }
913}