Skip to main content

icydb_core/value/
mod.rs

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