Skip to main content

icydb_core/value/
mod.rs

1mod family;
2
3#[cfg(test)]
4mod tests;
5
6use crate::{
7    db::store::StorageKey,
8    prelude::*,
9    traits::{EnumValue, FieldValue, NumFromPrimitive},
10    types::*,
11};
12use candid::CandidType;
13use serde::{Deserialize, Serialize};
14use std::cmp::Ordering;
15
16// re-exports
17pub use family::{CoercionFamily, CoercionFamilyExt};
18
19///
20/// CONSTANTS
21///
22
23const F64_SAFE_I64: i64 = 1i64 << 53;
24const F64_SAFE_U64: u64 = 1u64 << 53;
25const F64_SAFE_I128: i128 = 1i128 << 53;
26const F64_SAFE_U128: u128 = 1u128 << 53;
27
28///
29/// NumericRepr
30///
31
32enum NumericRepr {
33    Decimal(Decimal),
34    F64(f64),
35    None,
36}
37
38///
39/// TextMode
40///
41
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum TextMode {
44    Cs, // case-sensitive
45    Ci, // case-insensitive
46}
47
48///
49/// MapValueError
50///
51/// Invariant violations for `Value::Map` construction/normalization.
52///
53
54#[derive(Clone, Debug, Eq, PartialEq)]
55pub enum MapValueError {
56    EmptyKey {
57        index: usize,
58    },
59    NonScalarKey {
60        index: usize,
61        key: Value,
62    },
63    NonScalarValue {
64        index: usize,
65        value: Value,
66    },
67    DuplicateKey {
68        left_index: usize,
69        right_index: usize,
70    },
71}
72
73impl std::fmt::Display for MapValueError {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        match self {
76            Self::EmptyKey { index } => write!(f, "map key at index {index} must be non-null"),
77            Self::NonScalarKey { index, key } => {
78                write!(f, "map key at index {index} is not scalar: {key:?}")
79            }
80            Self::NonScalarValue { index, value } => {
81                write!(
82                    f,
83                    "map value at index {index} is not scalar/ref-like: {value:?}"
84                )
85            }
86            Self::DuplicateKey {
87                left_index,
88                right_index,
89            } => write!(
90                f,
91                "map contains duplicate keys at normalized positions {left_index} and {right_index}"
92            ),
93        }
94    }
95}
96
97impl std::error::Error for MapValueError {}
98
99///
100/// Value
101/// can be used in WHERE statements
102///
103/// Null        → the field’s value is Option::None (i.e., SQL NULL).
104/// Unit        → internal placeholder for RHS; not a real value.
105///
106
107#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
108pub enum Value {
109    Account(Account),
110    Blob(Vec<u8>),
111    Bool(bool),
112    Date(Date),
113    Decimal(Decimal),
114    Duration(Duration),
115    Enum(ValueEnum),
116    E8s(E8s),
117    E18s(E18s),
118    Float32(Float32),
119    Float64(Float64),
120    Int(i64),
121    Int128(Int128),
122    IntBig(Int),
123    /// Ordered list of values.
124    /// Used for many-cardinality transport.
125    /// List order is preserved for normalization and fingerprints.
126    List(Vec<Self>),
127    /// Canonical map representation.
128    /// Entries are always sorted by canonical key order and keys are unique.
129    /// Maps are persistable but not queryable in 0.7.
130    Map(Vec<(Self, Self)>),
131    Null,
132    Principal(Principal),
133    Subaccount(Subaccount),
134    Text(String),
135    Timestamp(Timestamp),
136    Uint(u64),
137    Uint128(Nat128),
138    UintBig(Nat),
139    Ulid(Ulid),
140    Unit,
141}
142
143// Local helpers to expand the scalar registry into match arms.
144macro_rules! value_is_numeric_from_registry {
145    ( @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) ),* $(,)? ) => {
146        match $value {
147            $( $value_pat => $is_numeric, )*
148            _ => false,
149        }
150    };
151}
152
153macro_rules! value_supports_numeric_coercion_from_registry {
154    ( @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) ),* $(,)? ) => {
155        match $value {
156            $( $value_pat => $supports_numeric_coercion, )*
157            _ => false,
158        }
159    };
160}
161
162macro_rules! value_storage_key_case {
163    ( $value:expr, Unit, true ) => {
164        if let Value::Unit = $value {
165            Some(StorageKey::Unit)
166        } else {
167            None
168        }
169    };
170    ( $value:expr, $scalar:ident, true ) => {
171        if let Value::$scalar(v) = $value {
172            Some(StorageKey::$scalar(*v))
173        } else {
174            None
175        }
176    };
177    ( $value:expr, $scalar:ident, false ) => {
178        None
179    };
180}
181
182macro_rules! value_storage_key_from_registry {
183    ( @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:expr) ),* $(,)? ) => {
184        {
185            let mut key = None;
186            $(
187                match key {
188                    Some(_) => {}
189                    None => {
190                        key = value_storage_key_case!($value, $scalar, $is_keyable);
191                    }
192                }
193            )*
194            key
195        }
196    };
197}
198
199macro_rules! value_coercion_family_from_registry {
200    ( @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) ),* $(,)? ) => {
201        match $value {
202            $( $value_pat => $coercion_family, )*
203            Value::List(_) => CoercionFamily::Collection,
204            Value::Map(_) => CoercionFamily::Collection,
205            Value::Null => CoercionFamily::Null,
206        }
207    };
208}
209
210impl Value {
211    ///
212    /// CONSTRUCTION
213    ///
214
215    /// Build a `Value::List` from a list literal.
216    ///
217    /// Intended for tests and inline construction.
218    /// Requires `Clone` because items are borrowed.
219    pub fn from_slice<T>(items: &[T]) -> Self
220    where
221        T: Into<Self> + Clone,
222    {
223        Self::List(items.iter().cloned().map(Into::into).collect())
224    }
225
226    /// Build a `Value::List` from owned items.
227    ///
228    /// This is the canonical constructor for query / DTO boundaries.
229    pub fn from_list<T>(items: Vec<T>) -> Self
230    where
231        T: Into<Self>,
232    {
233        Self::List(items.into_iter().map(Into::into).collect())
234    }
235
236    /// Build a canonical `Value::Map` from owned key/value entries.
237    ///
238    /// Invariants are validated and entries are normalized:
239    /// - keys must be scalar and non-null
240    /// - values must be scalar/ref-like (no collections)
241    /// - entries are sorted by canonical key order
242    /// - duplicate keys are rejected
243    pub fn from_map(entries: Vec<(Self, Self)>) -> Result<Self, MapValueError> {
244        let normalized = Self::normalize_map_entries(entries)?;
245        Ok(Self::Map(normalized))
246    }
247
248    /// Validate map entry invariants without changing order.
249    pub fn validate_map_entries(entries: &[(Self, Self)]) -> Result<(), MapValueError> {
250        for (index, (key, value)) in entries.iter().enumerate() {
251            if matches!(key, Self::Null) {
252                return Err(MapValueError::EmptyKey { index });
253            }
254            if !key.is_scalar() {
255                return Err(MapValueError::NonScalarKey {
256                    index,
257                    key: key.clone(),
258                });
259            }
260
261            if !value.is_scalar() {
262                return Err(MapValueError::NonScalarValue {
263                    index,
264                    value: value.clone(),
265                });
266            }
267        }
268
269        Ok(())
270    }
271
272    /// Normalize map entries into canonical deterministic order.
273    pub fn normalize_map_entries(
274        mut entries: Vec<(Self, Self)>,
275    ) -> Result<Vec<(Self, Self)>, MapValueError> {
276        Self::validate_map_entries(&entries)?;
277        entries
278            .sort_by(|(left_key, _), (right_key, _)| Self::canonical_cmp_key(left_key, right_key));
279
280        for i in 1..entries.len() {
281            let (left_key, _) = &entries[i - 1];
282            let (right_key, _) = &entries[i];
283            if Self::canonical_cmp_key(left_key, right_key) == Ordering::Equal {
284                return Err(MapValueError::DuplicateKey {
285                    left_index: i - 1,
286                    right_index: i,
287                });
288            }
289        }
290
291        Ok(entries)
292    }
293
294    /// Build a `Value::Enum` from a domain enum using its explicit mapping.
295    pub fn from_enum<E: EnumValue>(value: E) -> Self {
296        Self::Enum(value.to_value_enum())
297    }
298
299    /// Build a strict enum value using the canonical path of `E`.
300    #[must_use]
301    pub fn enum_strict<E: Path>(variant: &str) -> Self {
302        Self::Enum(ValueEnum::strict::<E>(variant))
303    }
304
305    ///
306    /// TYPES
307    ///
308
309    /// Returns true if the value is one of the numeric-like variants
310    /// supported by numeric comparison/ordering.
311    #[must_use]
312    pub const fn is_numeric(&self) -> bool {
313        scalar_registry!(value_is_numeric_from_registry, self)
314    }
315
316    /// Returns true when numeric coercion/comparison is explicitly allowed.
317    #[must_use]
318    pub const fn supports_numeric_coercion(&self) -> bool {
319        scalar_registry!(value_supports_numeric_coercion_from_registry, self)
320    }
321
322    /// Returns true if the value is Text.
323    #[must_use]
324    pub const fn is_text(&self) -> bool {
325        matches!(self, Self::Text(_))
326    }
327
328    /// Returns true if the value is Unit (used for presence/null comparators).
329    #[must_use]
330    pub const fn is_unit(&self) -> bool {
331        matches!(self, Self::Unit)
332    }
333
334    #[must_use]
335    pub const fn is_scalar(&self) -> bool {
336        match self {
337            // definitely not scalar:
338            Self::List(_) | Self::Map(_) | Self::Unit => false,
339            _ => true,
340        }
341    }
342
343    /// Total canonical comparator used for map-key normalization.
344    #[must_use]
345    pub fn canonical_cmp_key(left: &Self, right: &Self) -> Ordering {
346        let rank = Self::canonical_key_rank(left).cmp(&Self::canonical_key_rank(right));
347        if rank != Ordering::Equal {
348            return rank;
349        }
350
351        match (left, right) {
352            (Self::Account(a), Self::Account(b)) => a.cmp(b),
353            (Self::Blob(a), Self::Blob(b)) => a.cmp(b),
354            (Self::Bool(a), Self::Bool(b)) => a.cmp(b),
355            (Self::Date(a), Self::Date(b)) => a.cmp(b),
356            (Self::Decimal(a), Self::Decimal(b)) => a.cmp(b),
357            (Self::Duration(a), Self::Duration(b)) => a.cmp(b),
358            (Self::Enum(a), Self::Enum(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
359            (Self::E8s(a), Self::E8s(b)) => a.cmp(b),
360            (Self::E18s(a), Self::E18s(b)) => a.cmp(b),
361            (Self::Float32(a), Self::Float32(b)) => a.cmp(b),
362            (Self::Float64(a), Self::Float64(b)) => a.cmp(b),
363            (Self::Int(a), Self::Int(b)) => a.cmp(b),
364            (Self::Int128(a), Self::Int128(b)) => a.cmp(b),
365            (Self::IntBig(a), Self::IntBig(b)) => a.cmp(b),
366            (Self::Principal(a), Self::Principal(b)) => a.cmp(b),
367            (Self::Subaccount(a), Self::Subaccount(b)) => a.cmp(b),
368            (Self::Text(a), Self::Text(b)) => a.cmp(b),
369            (Self::Timestamp(a), Self::Timestamp(b)) => a.cmp(b),
370            (Self::Uint(a), Self::Uint(b)) => a.cmp(b),
371            (Self::Uint128(a), Self::Uint128(b)) => a.cmp(b),
372            (Self::UintBig(a), Self::UintBig(b)) => a.cmp(b),
373            (Self::Ulid(a), Self::Ulid(b)) => a.cmp(b),
374            _ => Ordering::Equal,
375        }
376    }
377
378    const fn canonical_key_rank(value: &Self) -> u8 {
379        match value {
380            Self::Account(_) => 0,
381            Self::Blob(_) => 1,
382            Self::Bool(_) => 2,
383            Self::Date(_) => 3,
384            Self::Decimal(_) => 4,
385            Self::Duration(_) => 5,
386            Self::Enum(_) => 6,
387            Self::E8s(_) => 7,
388            Self::E18s(_) => 8,
389            Self::Float32(_) => 9,
390            Self::Float64(_) => 10,
391            Self::Int(_) => 11,
392            Self::Int128(_) => 12,
393            Self::IntBig(_) => 13,
394            Self::List(_) => 14,
395            Self::Map(_) => 15,
396            Self::Null => 16,
397            Self::Principal(_) => 17,
398            Self::Subaccount(_) => 18,
399            Self::Text(_) => 19,
400            Self::Timestamp(_) => 20,
401            Self::Uint(_) => 21,
402            Self::Uint128(_) => 22,
403            Self::UintBig(_) => 23,
404            Self::Ulid(_) => 24,
405            Self::Unit => 25,
406        }
407    }
408
409    fn numeric_repr(&self) -> NumericRepr {
410        // Numeric comparison eligibility is registry-authoritative.
411        if !self.supports_numeric_coercion() {
412            return NumericRepr::None;
413        }
414
415        if let Some(d) = self.to_decimal() {
416            return NumericRepr::Decimal(d);
417        }
418        if let Some(f) = self.to_f64_lossless() {
419            return NumericRepr::F64(f);
420        }
421        NumericRepr::None
422    }
423
424    ///
425    /// CONVERSION
426    ///
427
428    /// NOTE:
429    /// `Unit` is intentionally treated as a valid storage key and indexable,
430    /// used for singleton tables and synthetic identity entities.
431    /// Only `Null` is non-indexable.
432    #[must_use]
433    pub const fn as_storage_key(&self) -> Option<StorageKey> {
434        scalar_registry!(value_storage_key_from_registry, self)
435    }
436
437    #[must_use]
438    pub const fn as_text(&self) -> Option<&str> {
439        if let Self::Text(s) = self {
440            Some(s.as_str())
441        } else {
442            None
443        }
444    }
445
446    #[must_use]
447    pub const fn as_list(&self) -> Option<&[Self]> {
448        if let Self::List(xs) = self {
449            Some(xs.as_slice())
450        } else {
451            None
452        }
453    }
454
455    #[must_use]
456    pub const fn as_map(&self) -> Option<&[(Self, Self)]> {
457        if let Self::Map(entries) = self {
458            Some(entries.as_slice())
459        } else {
460            None
461        }
462    }
463
464    fn to_decimal(&self) -> Option<Decimal> {
465        match self {
466            Self::Decimal(d) => Some(*d),
467            Self::Duration(d) => Decimal::from_u64(d.get()),
468            Self::E8s(v) => Some(v.to_decimal()),
469            Self::E18s(v) => v.to_decimal(),
470            Self::Float64(f) => Decimal::from_f64(f.get()),
471            Self::Float32(f) => Decimal::from_f32(f.get()),
472            Self::Int(i) => Decimal::from_i64(*i),
473            Self::Int128(i) => Decimal::from_i128(i.get()),
474            Self::IntBig(i) => i.to_i128().and_then(Decimal::from_i128),
475            Self::Timestamp(t) => Decimal::from_u64(t.get()),
476            Self::Uint(u) => Decimal::from_u64(*u),
477            Self::Uint128(u) => Decimal::from_u128(u.get()),
478            Self::UintBig(u) => u.to_u128().and_then(Decimal::from_u128),
479
480            _ => None,
481        }
482    }
483
484    // it's lossless, trust me bro
485    #[allow(clippy::cast_precision_loss)]
486    fn to_f64_lossless(&self) -> Option<f64> {
487        match self {
488            Self::Duration(d) if d.get() <= F64_SAFE_U64 => Some(d.get() as f64),
489            Self::Float64(f) => Some(f.get()),
490            Self::Float32(f) => Some(f64::from(f.get())),
491            Self::Int(i) if (-F64_SAFE_I64..=F64_SAFE_I64).contains(i) => Some(*i as f64),
492            Self::Int128(i) if (-F64_SAFE_I128..=F64_SAFE_I128).contains(&i.get()) => {
493                Some(i.get() as f64)
494            }
495            Self::IntBig(i) => i.to_i128().and_then(|v| {
496                (-F64_SAFE_I128..=F64_SAFE_I128)
497                    .contains(&v)
498                    .then_some(v as f64)
499            }),
500            Self::Timestamp(t) if t.get() <= F64_SAFE_U64 => Some(t.get() as f64),
501            Self::Uint(u) if *u <= F64_SAFE_U64 => Some(*u as f64),
502            Self::Uint128(u) if u.get() <= F64_SAFE_U128 => Some(u.get() as f64),
503            Self::UintBig(u) => u
504                .to_u128()
505                .and_then(|v| (v <= F64_SAFE_U128).then_some(v as f64)),
506
507            _ => None,
508        }
509    }
510
511    /// Cross-type numeric comparison; returns None if non-numeric.
512    #[must_use]
513    pub fn cmp_numeric(&self, other: &Self) -> Option<Ordering> {
514        if !self.supports_numeric_coercion() || !other.supports_numeric_coercion() {
515            return None;
516        }
517
518        match (self.numeric_repr(), other.numeric_repr()) {
519            (NumericRepr::Decimal(a), NumericRepr::Decimal(b)) => a.partial_cmp(&b),
520            (NumericRepr::F64(a), NumericRepr::F64(b)) => a.partial_cmp(&b),
521            _ => None,
522        }
523    }
524
525    ///
526    /// TEXT COMPARISON
527    ///
528
529    fn fold_ci(s: &str) -> std::borrow::Cow<'_, str> {
530        if s.is_ascii() {
531            return std::borrow::Cow::Owned(s.to_ascii_lowercase());
532        }
533        // NOTE: Unicode fallback — temporary to_lowercase for non‑ASCII.
534        // Future: replace with proper NFKC + full casefold when available.
535        std::borrow::Cow::Owned(s.to_lowercase())
536    }
537
538    fn text_with_mode(s: &'_ str, mode: TextMode) -> std::borrow::Cow<'_, str> {
539        match mode {
540            TextMode::Cs => std::borrow::Cow::Borrowed(s),
541            TextMode::Ci => Self::fold_ci(s),
542        }
543    }
544
545    fn text_op(
546        &self,
547        other: &Self,
548        mode: TextMode,
549        f: impl Fn(&str, &str) -> bool,
550    ) -> Option<bool> {
551        let (a, b) = (self.as_text()?, other.as_text()?);
552        let a = Self::text_with_mode(a, mode);
553        let b = Self::text_with_mode(b, mode);
554        Some(f(&a, &b))
555    }
556
557    fn ci_key(&self) -> Option<String> {
558        match self {
559            Self::Text(s) => Some(Self::fold_ci(s).into_owned()),
560            Self::Ulid(u) => Some(u.to_string().to_ascii_lowercase()),
561            Self::Principal(p) => Some(p.to_string().to_ascii_lowercase()),
562            Self::Account(a) => Some(a.to_string().to_ascii_lowercase()),
563            _ => None,
564        }
565    }
566
567    fn eq_ci(a: &Self, b: &Self) -> bool {
568        if let (Some(ak), Some(bk)) = (a.ci_key(), b.ci_key()) {
569            return ak == bk;
570        }
571
572        a == b
573    }
574
575    fn normalize_list_ref(v: &Self) -> Vec<&Self> {
576        match v {
577            Self::List(vs) => vs.iter().collect(),
578            v => vec![v],
579        }
580    }
581
582    fn contains_by<F>(&self, needle: &Self, eq: F) -> Option<bool>
583    where
584        F: Fn(&Self, &Self) -> bool,
585    {
586        self.as_list()
587            .map(|items| items.iter().any(|v| eq(v, needle)))
588    }
589
590    #[allow(clippy::unnecessary_wraps)]
591    fn contains_any_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
592    where
593        F: Fn(&Self, &Self) -> bool,
594    {
595        let needles = Self::normalize_list_ref(needles);
596        match self {
597            Self::List(items) => Some(needles.iter().any(|n| items.iter().any(|v| eq(v, n)))),
598            scalar => Some(needles.iter().any(|n| eq(scalar, n))),
599        }
600    }
601
602    #[allow(clippy::unnecessary_wraps)]
603    fn contains_all_by<F>(&self, needles: &Self, eq: F) -> Option<bool>
604    where
605        F: Fn(&Self, &Self) -> bool,
606    {
607        let needles = Self::normalize_list_ref(needles);
608        match self {
609            Self::List(items) => Some(needles.iter().all(|n| items.iter().any(|v| eq(v, n)))),
610            scalar => Some(needles.len() == 1 && eq(scalar, needles[0])),
611        }
612    }
613
614    fn in_list_by<F>(&self, haystack: &Self, eq: F) -> Option<bool>
615    where
616        F: Fn(&Self, &Self) -> bool,
617    {
618        if let Self::List(items) = haystack {
619            Some(items.iter().any(|h| eq(h, self)))
620        } else {
621            None
622        }
623    }
624
625    #[must_use]
626    /// Case-sensitive/insensitive equality check for text-like values.
627    pub fn text_eq(&self, other: &Self, mode: TextMode) -> Option<bool> {
628        self.text_op(other, mode, |a, b| a == b)
629    }
630
631    #[must_use]
632    /// Check whether `other` is a substring of `self` under the given text mode.
633    pub fn text_contains(&self, needle: &Self, mode: TextMode) -> Option<bool> {
634        self.text_op(needle, mode, |a, b| a.contains(b))
635    }
636
637    #[must_use]
638    /// Check whether `self` starts with `other` under the given text mode.
639    pub fn text_starts_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
640        self.text_op(needle, mode, |a, b| a.starts_with(b))
641    }
642
643    #[must_use]
644    /// Check whether `self` ends with `other` under the given text mode.
645    pub fn text_ends_with(&self, needle: &Self, mode: TextMode) -> Option<bool> {
646        self.text_op(needle, mode, |a, b| a.ends_with(b))
647    }
648
649    ///
650    /// EMPTY
651    ///
652
653    #[must_use]
654    pub const fn is_empty(&self) -> Option<bool> {
655        match self {
656            Self::List(xs) => Some(xs.is_empty()),
657            Self::Map(entries) => Some(entries.is_empty()),
658            Self::Text(s) => Some(s.is_empty()),
659            Self::Blob(b) => Some(b.is_empty()),
660
661            //  fields represented as Value::Null:
662            Self::Null => Some(true),
663
664            _ => None,
665        }
666    }
667
668    #[must_use]
669    /// Logical negation of [`is_empty`](Self::is_empty).
670    pub fn is_not_empty(&self) -> Option<bool> {
671        self.is_empty().map(|b| !b)
672    }
673
674    ///
675    /// COLLECTIONS
676    ///
677
678    #[must_use]
679    /// Returns true if `self` contains `needle` (or equals it for scalars).
680    pub fn contains(&self, needle: &Self) -> Option<bool> {
681        self.contains_by(needle, |a, b| a == b)
682    }
683
684    #[must_use]
685    /// Returns true if any item in `needles` matches a member of `self`.
686    pub fn contains_any(&self, needles: &Self) -> Option<bool> {
687        self.contains_any_by(needles, |a, b| a == b)
688    }
689
690    #[must_use]
691    /// Returns true if every item in `needles` matches a member of `self`.
692    pub fn contains_all(&self, needles: &Self) -> Option<bool> {
693        self.contains_all_by(needles, |a, b| a == b)
694    }
695
696    #[must_use]
697    /// Returns true if `self` exists inside the provided list.
698    pub fn in_list(&self, haystack: &Self) -> Option<bool> {
699        self.in_list_by(haystack, |a, b| a == b)
700    }
701
702    #[must_use]
703    /// Case-insensitive `contains` supporting text and identifier variants.
704    pub fn contains_ci(&self, needle: &Self) -> Option<bool> {
705        match self {
706            Self::List(_) => self.contains_by(needle, Self::eq_ci),
707            _ => Some(Self::eq_ci(self, needle)),
708        }
709    }
710
711    #[must_use]
712    /// Case-insensitive variant of [`contains_any`](Self::contains_any).
713    pub fn contains_any_ci(&self, needles: &Self) -> Option<bool> {
714        self.contains_any_by(needles, Self::eq_ci)
715    }
716
717    #[must_use]
718    /// Case-insensitive variant of [`contains_all`](Self::contains_all).
719    pub fn contains_all_ci(&self, needles: &Self) -> Option<bool> {
720        self.contains_all_by(needles, Self::eq_ci)
721    }
722
723    #[must_use]
724    /// Case-insensitive variant of [`in_list`](Self::in_list).
725    pub fn in_list_ci(&self, haystack: &Self) -> Option<bool> {
726        self.in_list_by(haystack, Self::eq_ci)
727    }
728}
729
730impl FieldValue for Value {
731    fn kind() -> crate::traits::FieldValueKind {
732        crate::traits::FieldValueKind::Atomic
733    }
734
735    fn to_value(&self) -> Value {
736        self.clone()
737    }
738
739    fn from_value(value: &Value) -> Option<Self> {
740        Some(value.clone())
741    }
742}
743
744#[macro_export]
745macro_rules! impl_from_for {
746    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
747        $(
748            impl From<$type> for Value {
749                fn from(v: $type) -> Self {
750                    Self::$variant(v.into())
751                }
752            }
753        )*
754    };
755}
756
757impl_from_for! {
758    Account    => Account,
759    Date       => Date,
760    Decimal    => Decimal,
761    Duration   => Duration,
762    E8s        => E8s,
763    E18s       => E18s,
764    bool       => Bool,
765    i8         => Int,
766    i16        => Int,
767    i32        => Int,
768    i64        => Int,
769    i128       => Int128,
770    Int        => IntBig,
771    Principal  => Principal,
772    Subaccount => Subaccount,
773    &str       => Text,
774    String     => Text,
775    Timestamp  => Timestamp,
776    u8         => Uint,
777    u16        => Uint,
778    u32        => Uint,
779    u64        => Uint,
780    u128       => Uint128,
781    Nat        => UintBig,
782    Ulid       => Ulid,
783}
784
785impl CoercionFamilyExt for Value {
786    /// Returns the coercion-routing family for this value.
787    ///
788    /// NOTE:
789    /// This does NOT imply numeric, arithmetic, ordering, or keyability support.
790    /// All scalar capabilities are registry-driven.
791    fn coercion_family(&self) -> CoercionFamily {
792        scalar_registry!(value_coercion_family_from_registry, self)
793    }
794}
795
796impl From<Vec<Self>> for Value {
797    fn from(vec: Vec<Self>) -> Self {
798        Self::List(vec)
799    }
800}
801
802impl From<Vec<(Self, Self)>> for Value {
803    fn from(entries: Vec<(Self, Self)>) -> Self {
804        Self::from_map(entries).unwrap_or_else(|err| {
805            panic!("invalid map invariant while constructing Value::Map from Vec: {err}")
806        })
807    }
808}
809
810impl From<()> for Value {
811    fn from((): ()) -> Self {
812        Self::Unit
813    }
814}
815
816// NOTE:
817// Value::partial_cmp is NOT the canonical ordering for database semantics.
818// Some orderable scalar types (e.g. Account, Unit) intentionally do not
819// participate here. Use canonical_cmp / strict ordering for ORDER BY,
820// planning, and key-range validation.
821impl PartialOrd for Value {
822    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
823        match (self, other) {
824            (Self::Bool(a), Self::Bool(b)) => a.partial_cmp(b),
825            (Self::Date(a), Self::Date(b)) => a.partial_cmp(b),
826            (Self::Decimal(a), Self::Decimal(b)) => a.partial_cmp(b),
827            (Self::Duration(a), Self::Duration(b)) => a.partial_cmp(b),
828            (Self::E8s(a), Self::E8s(b)) => a.partial_cmp(b),
829            (Self::E18s(a), Self::E18s(b)) => a.partial_cmp(b),
830            (Self::Enum(a), Self::Enum(b)) => a.partial_cmp(b),
831            (Self::Float32(a), Self::Float32(b)) => a.partial_cmp(b),
832            (Self::Float64(a), Self::Float64(b)) => a.partial_cmp(b),
833            (Self::Int(a), Self::Int(b)) => a.partial_cmp(b),
834            (Self::Int128(a), Self::Int128(b)) => a.partial_cmp(b),
835            (Self::IntBig(a), Self::IntBig(b)) => a.partial_cmp(b),
836            (Self::Principal(a), Self::Principal(b)) => a.partial_cmp(b),
837            (Self::Subaccount(a), Self::Subaccount(b)) => a.partial_cmp(b),
838            (Self::Text(a), Self::Text(b)) => a.partial_cmp(b),
839            (Self::Timestamp(a), Self::Timestamp(b)) => a.partial_cmp(b),
840            (Self::Uint(a), Self::Uint(b)) => a.partial_cmp(b),
841            (Self::Uint128(a), Self::Uint128(b)) => a.partial_cmp(b),
842            (Self::UintBig(a), Self::UintBig(b)) => a.partial_cmp(b),
843            (Self::Ulid(a), Self::Ulid(b)) => a.partial_cmp(b),
844            (Self::Map(a), Self::Map(b)) => {
845                for ((left_key, left_value), (right_key, right_value)) in a.iter().zip(b.iter()) {
846                    let key_cmp = Self::canonical_cmp_key(left_key, right_key);
847                    if key_cmp != Ordering::Equal {
848                        return Some(key_cmp);
849                    }
850
851                    match left_value.partial_cmp(right_value) {
852                        Some(Ordering::Equal) => {}
853                        non_eq => return non_eq,
854                    }
855                }
856                a.len().partial_cmp(&b.len())
857            }
858
859            // Cross-type comparisons: no ordering
860            _ => None,
861        }
862    }
863}
864
865///
866/// ValueEnum
867/// handles the Enum case; `path` is optional to allow strict (typed) or loose matching.
868///
869
870#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd, Serialize)]
871pub struct ValueEnum {
872    pub variant: String,
873    pub path: Option<String>,
874    pub payload: Option<Box<Value>>,
875}
876
877impl ValueEnum {
878    #[must_use]
879    /// Build a strict enum value matching the provided variant and path.
880    pub fn new(variant: &str, path: Option<&str>) -> Self {
881        Self {
882            variant: variant.to_string(),
883            path: path.map(ToString::to_string),
884            payload: None,
885        }
886    }
887
888    #[must_use]
889    /// Build a strict enum value using the canonical path of `E`.
890    pub fn strict<E: Path>(variant: &str) -> Self {
891        Self::new(variant, Some(E::PATH))
892    }
893
894    #[must_use]
895    /// Build a strict enum value from a domain enum using its explicit mapping.
896    pub fn from_enum<E: EnumValue>(value: E) -> Self {
897        value.to_value_enum()
898    }
899
900    #[must_use]
901    /// Build an enum value that ignores the path for loose matching.
902    pub fn loose(variant: &str) -> Self {
903        Self::new(variant, None)
904    }
905
906    #[must_use]
907    /// Attach an enum payload (used for data-carrying variants).
908    pub fn with_payload(mut self, payload: Value) -> Self {
909        self.payload = Some(Box::new(payload));
910        self
911    }
912}