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