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