Skip to main content

icydb_core/value/
runtime.rs

1//! Module: value::runtime
2//!
3//! Responsibility: typed runtime conversion contracts and shared collection
4//! lowering/reconstruction helpers for the canonical [`Value`] domain.
5//! Does not own: primary-key encoding or persisted field codecs.
6//! Boundary: runtime-only typed value conversion used by generated wrappers,
7//! query values, and accepted-catalog enum reconstruction.
8
9use super::{Value, ValueEnum};
10use std::{
11    cmp::Ordering,
12    collections::{BTreeMap, BTreeSet},
13};
14
15/// Schema affordance classification for query planning and validation.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum RuntimeValueKind {
18    /// Planner-addressable atomic value.
19    Atomic,
20
21    /// Structured value whose internal fields are not planner-addressable.
22    Structured {
23        /// Whether predicates may be expressed against this field.
24        queryable: bool,
25    },
26}
27
28impl RuntimeValueKind {
29    /// Return whether this runtime value kind may be used in predicates.
30    #[must_use]
31    pub const fn is_queryable(self) -> bool {
32        match self {
33            Self::Atomic => true,
34            Self::Structured { queryable } => queryable,
35        }
36    }
37}
38
39/// Schema/queryability metadata for one typed runtime value surface.
40pub trait RuntimeValueMeta {
41    /// Return the planner affordance category for this value type.
42    fn kind() -> RuntimeValueKind
43    where
44        Self: Sized;
45}
46
47/// Runtime-only lowering from a typed value into the canonical [`Value`] union.
48///
49/// This contract must not be used as a persisted-row or primary-key codec.
50pub trait RuntimeValueEncode {
51    /// Lower this typed value into its canonical runtime representation.
52    fn to_value(&self) -> Value;
53}
54
55/// Runtime-only reconstruction of a typed value from the canonical [`Value`]
56/// union.
57///
58/// This contract must not be used as a persisted-row or primary-key codec.
59pub trait RuntimeValueDecode {
60    /// Reconstruct a typed value when the runtime representation matches.
61    #[must_use]
62    fn from_value(value: &Value) -> Option<Self>
63    where
64        Self: Sized;
65
66    /// Reconstruct through accepted catalog authority when the value graph may
67    /// contain store-local enum IDs.
68    #[doc(hidden)]
69    fn from_value_with_enum_context(
70        value: &Value,
71        _context: &dyn RuntimeEnumContext,
72    ) -> Option<Self>
73    where
74        Self: Sized,
75    {
76        Self::from_value(value)
77    }
78}
79
80/// Catalog-resolved view of one canonical runtime enum value.
81#[doc(hidden)]
82pub struct RuntimeEnumSelection<'a> {
83    pub path: &'a str,
84    pub variant: &'a str,
85    pub payload: Option<&'a Value>,
86}
87
88/// Opaque accepted-catalog resolver used by generated typed decode.
89#[doc(hidden)]
90pub trait RuntimeEnumContext {
91    fn resolve_enum<'a>(&'a self, value: &'a ValueEnum) -> Option<RuntimeEnumSelection<'a>>;
92}
93
94/// Explicit iteration contract for generated list and set wrapper types.
95pub trait Collection {
96    /// Element type exposed by the collection.
97    type Item;
98
99    /// Iterator tied to the borrow of this collection.
100    type Iter<'a>: Iterator<Item = &'a Self::Item> + 'a
101    where
102        Self: 'a;
103
104    /// Iterate over the collection's items.
105    fn iter(&self) -> Self::Iter<'_>;
106
107    /// Return the number of items in the collection.
108    fn len(&self) -> usize;
109
110    /// Return whether the collection contains no items.
111    fn is_empty(&self) -> bool {
112        self.len() == 0
113    }
114}
115
116/// Explicit iteration contract for generated map wrapper types.
117pub trait MapCollection {
118    /// Key type exposed by the map.
119    type Key;
120
121    /// Value type exposed by the map.
122    type Value;
123
124    /// Iterator tied to the borrow of this map.
125    type Iter<'a>: Iterator<Item = (&'a Self::Key, &'a Self::Value)> + 'a
126    where
127        Self: 'a;
128
129    /// Iterate over the map's entries.
130    fn iter(&self) -> Self::Iter<'_>;
131
132    /// Return the number of entries in the map.
133    fn len(&self) -> usize;
134
135    /// Return whether the map contains no entries.
136    fn is_empty(&self) -> bool {
137        self.len() == 0
138    }
139}
140
141impl<T> Collection for Vec<T> {
142    type Item = T;
143    type Iter<'a>
144        = std::slice::Iter<'a, T>
145    where
146        Self: 'a;
147
148    fn iter(&self) -> Self::Iter<'_> {
149        self.as_slice().iter()
150    }
151
152    fn len(&self) -> usize {
153        self.as_slice().len()
154    }
155}
156
157impl<T> Collection for BTreeSet<T> {
158    type Item = T;
159    type Iter<'a>
160        = std::collections::btree_set::Iter<'a, T>
161    where
162        Self: 'a;
163
164    fn iter(&self) -> Self::Iter<'_> {
165        self.iter()
166    }
167
168    fn len(&self) -> usize {
169        self.len()
170    }
171}
172
173impl<K, V> MapCollection for BTreeMap<K, V> {
174    type Key = K;
175    type Value = V;
176    type Iter<'a>
177        = std::collections::btree_map::Iter<'a, K, V>
178    where
179        Self: 'a;
180
181    fn iter(&self) -> Self::Iter<'_> {
182        self.iter()
183    }
184
185    fn len(&self) -> usize {
186        self.len()
187    }
188}
189
190/// Lower one typed runtime value without naming its encode trait at the call
191/// site.
192pub fn runtime_value_to_value<T>(value: &T) -> Value
193where
194    T: ?Sized + RuntimeValueEncode,
195{
196    value.to_value()
197}
198
199/// Reconstruct one typed runtime value without naming its decode trait at the
200/// call site.
201#[must_use]
202pub fn runtime_value_from_value<T>(value: &Value) -> Option<T>
203where
204    T: RuntimeValueDecode,
205{
206    T::from_value(value)
207}
208
209/// Reconstruct one generated enum graph through accepted catalog authority.
210#[doc(hidden)]
211pub fn runtime_value_from_value_with_enum_context<T>(
212    value: &Value,
213    context: &dyn RuntimeEnumContext,
214) -> Option<T>
215where
216    T: RuntimeValueDecode,
217{
218    T::from_value_with_enum_context(value, context)
219}
220
221/// Reconstruct with accepted enum authority when the row reader carries it.
222#[doc(hidden)]
223#[must_use]
224pub fn runtime_value_from_value_with_optional_enum_context<T>(
225    value: &Value,
226    context: Option<&dyn RuntimeEnumContext>,
227) -> Option<T>
228where
229    T: RuntimeValueDecode,
230{
231    match context {
232        Some(context) => T::from_value_with_enum_context(value, context),
233        None => T::from_value(value),
234    }
235}
236
237/// Lower a generated collection wrapper into a runtime list value.
238pub fn runtime_value_collection_to_value<C>(collection: &C) -> Value
239where
240    C: Collection,
241    C::Item: RuntimeValueEncode,
242{
243    Value::List(
244        collection
245            .iter()
246            .map(RuntimeValueEncode::to_value)
247            .collect(),
248    )
249}
250
251/// Decode a runtime list into a typed vector.
252#[must_use]
253pub fn runtime_value_vec_from_value<T>(value: &Value) -> Option<Vec<T>>
254where
255    T: RuntimeValueDecode,
256{
257    decode_runtime_value_list(value, T::from_value)
258}
259
260fn decode_runtime_value_list<T>(
261    value: &Value,
262    mut decode: impl FnMut(&Value) -> Option<T>,
263) -> Option<Vec<T>> {
264    let Value::List(values) = value else {
265        return None;
266    };
267
268    let mut out = Vec::with_capacity(values.len());
269    for value in values {
270        out.push(decode(value)?);
271    }
272
273    Some(out)
274}
275
276/// Decode a runtime list into a typed set, rejecting decoded duplicates.
277#[must_use]
278pub fn runtime_value_btree_set_from_value<T>(value: &Value) -> Option<BTreeSet<T>>
279where
280    T: Ord + RuntimeValueDecode,
281{
282    decode_runtime_value_set(value, T::from_value)
283}
284
285fn decode_runtime_value_set<T>(
286    value: &Value,
287    mut decode: impl FnMut(&Value) -> Option<T>,
288) -> Option<BTreeSet<T>>
289where
290    T: Ord,
291{
292    let Value::List(values) = value else {
293        return None;
294    };
295
296    let mut out = BTreeSet::new();
297    for value in values {
298        let item = decode(value)?;
299        if !out.insert(item) {
300            return None;
301        }
302    }
303
304    Some(out)
305}
306
307/// Lower a generated map wrapper into a canonical runtime map value.
308pub fn runtime_value_map_collection_to_value<M>(map: &M, path: &'static str) -> Value
309where
310    M: MapCollection,
311    M::Key: RuntimeValueEncode,
312    M::Value: RuntimeValueEncode,
313{
314    let mut entries: Vec<(Value, Value)> = map
315        .iter()
316        .map(|(key, value)| {
317            (
318                RuntimeValueEncode::to_value(key),
319                RuntimeValueEncode::to_value(value),
320            )
321        })
322        .collect();
323
324    if let Err(err) = Value::validate_map_entries(entries.as_slice()) {
325        debug_assert!(false, "invalid map field value for {path}: {err}");
326        return Value::Map(entries);
327    }
328
329    Value::sort_map_entries_in_place(entries.as_mut_slice());
330
331    for i in 1..entries.len() {
332        let (left_key, _) = &entries[i - 1];
333        let (right_key, _) = &entries[i];
334        if Value::canonical_cmp_key(left_key, right_key) == Ordering::Equal {
335            debug_assert!(
336                false,
337                "duplicate map key in {path} after value-surface canonicalization",
338            );
339            break;
340        }
341    }
342
343    Value::Map(entries)
344}
345
346/// Decode a canonical runtime map into a typed map, rejecting decoded key
347/// collisions.
348#[must_use]
349pub fn runtime_value_btree_map_from_value<K, V>(value: &Value) -> Option<BTreeMap<K, V>>
350where
351    K: Ord + RuntimeValueDecode,
352    V: RuntimeValueDecode,
353{
354    decode_runtime_value_map(value, K::from_value, V::from_value)
355}
356
357fn decode_runtime_value_map<K, V>(
358    value: &Value,
359    mut decode_key: impl FnMut(&Value) -> Option<K>,
360    mut decode_value: impl FnMut(&Value) -> Option<V>,
361) -> Option<BTreeMap<K, V>>
362where
363    K: Ord,
364{
365    let Value::Map(entries) = value else {
366        return None;
367    };
368
369    Value::validate_map_entries(entries).ok()?;
370    if !Value::map_entries_are_strictly_canonical(entries) {
371        return None;
372    }
373
374    let mut map = BTreeMap::new();
375    for (entry_key, entry_value) in entries {
376        let key = decode_key(entry_key)?;
377        let value = decode_value(entry_value)?;
378        match map.entry(key) {
379            std::collections::btree_map::Entry::Vacant(entry) => {
380                entry.insert(value);
381            }
382            std::collections::btree_map::Entry::Occupied(_) => return None,
383        }
384    }
385
386    Some(map)
387}
388
389/// Convert generated list wrapper input entries into their stored item type.
390#[must_use]
391pub fn runtime_value_from_vec_into<T, I>(entries: Vec<I>) -> Vec<T>
392where
393    I: Into<T>,
394{
395    entries.into_iter().map(Into::into).collect()
396}
397
398/// Convert generated set wrapper input entries into their stored item type.
399#[must_use]
400pub fn runtime_value_from_vec_into_btree_set<T, I>(entries: Vec<I>) -> BTreeSet<T>
401where
402    I: Into<T>,
403    T: Ord,
404{
405    entries.into_iter().map(Into::into).collect()
406}
407
408/// Convert generated map wrapper input entries into their stored key/value
409/// types.
410#[must_use]
411pub fn runtime_value_from_vec_into_btree_map<K, V, IK, IV>(entries: Vec<(IK, IV)>) -> BTreeMap<K, V>
412where
413    IK: Into<K>,
414    IV: Into<V>,
415    K: Ord,
416{
417    entries
418        .into_iter()
419        .map(|(key, value)| (key.into(), value.into()))
420        .collect()
421}
422
423/// Apply one generated newtype input conversion.
424#[must_use]
425pub fn runtime_value_into<T, U>(value: U) -> T
426where
427    U: Into<T>,
428{
429    value.into()
430}
431
432impl RuntimeValueMeta for Value {
433    fn kind() -> RuntimeValueKind {
434        RuntimeValueKind::Atomic
435    }
436}
437
438impl RuntimeValueEncode for Value {
439    fn to_value(&self) -> Value {
440        self.clone()
441    }
442}
443
444impl RuntimeValueDecode for Value {
445    fn from_value(value: &Value) -> Option<Self> {
446        Some(value.clone())
447    }
448}
449
450impl RuntimeValueMeta for &str {
451    fn kind() -> RuntimeValueKind {
452        RuntimeValueKind::Atomic
453    }
454}
455
456impl RuntimeValueEncode for &str {
457    fn to_value(&self) -> Value {
458        Value::Text((*self).to_string())
459    }
460}
461
462impl RuntimeValueMeta for String {
463    fn kind() -> RuntimeValueKind {
464        RuntimeValueKind::Atomic
465    }
466}
467
468impl RuntimeValueEncode for String {
469    fn to_value(&self) -> Value {
470        Value::Text(self.clone())
471    }
472}
473
474impl RuntimeValueDecode for String {
475    fn from_value(value: &Value) -> Option<Self> {
476        match value {
477            Value::Text(value) => Some(value.clone()),
478            _ => None,
479        }
480    }
481}
482
483impl<T: RuntimeValueMeta> RuntimeValueMeta for Option<T> {
484    fn kind() -> RuntimeValueKind {
485        T::kind()
486    }
487}
488
489impl<T: RuntimeValueEncode> RuntimeValueEncode for Option<T> {
490    fn to_value(&self) -> Value {
491        match self {
492            Some(value) => value.to_value(),
493            None => Value::Null,
494        }
495    }
496}
497
498impl<T: RuntimeValueDecode> RuntimeValueDecode for Option<T> {
499    fn from_value(value: &Value) -> Option<Self> {
500        if matches!(value, Value::Null) {
501            return Some(None);
502        }
503
504        T::from_value(value).map(Some)
505    }
506
507    fn from_value_with_enum_context(
508        value: &Value,
509        context: &dyn RuntimeEnumContext,
510    ) -> Option<Self> {
511        if matches!(value, Value::Null) {
512            return Some(None);
513        }
514
515        T::from_value_with_enum_context(value, context).map(Some)
516    }
517}
518
519impl<T: RuntimeValueMeta> RuntimeValueMeta for Box<T> {
520    fn kind() -> RuntimeValueKind {
521        T::kind()
522    }
523}
524
525impl<T: RuntimeValueEncode> RuntimeValueEncode for Box<T> {
526    fn to_value(&self) -> Value {
527        (**self).to_value()
528    }
529}
530
531impl<T: RuntimeValueDecode> RuntimeValueDecode for Box<T> {
532    fn from_value(value: &Value) -> Option<Self> {
533        T::from_value(value).map(Self::new)
534    }
535
536    fn from_value_with_enum_context(
537        value: &Value,
538        context: &dyn RuntimeEnumContext,
539    ) -> Option<Self> {
540        T::from_value_with_enum_context(value, context).map(Self::new)
541    }
542}
543
544impl<T> RuntimeValueMeta for Vec<T> {
545    fn kind() -> RuntimeValueKind {
546        RuntimeValueKind::Structured { queryable: true }
547    }
548}
549
550impl<T: RuntimeValueEncode> RuntimeValueEncode for Vec<T> {
551    fn to_value(&self) -> Value {
552        runtime_value_collection_to_value(self)
553    }
554}
555
556impl<T: RuntimeValueDecode> RuntimeValueDecode for Vec<T> {
557    fn from_value(value: &Value) -> Option<Self> {
558        runtime_value_vec_from_value(value)
559    }
560
561    fn from_value_with_enum_context(
562        value: &Value,
563        context: &dyn RuntimeEnumContext,
564    ) -> Option<Self> {
565        decode_runtime_value_list(value, |value| {
566            T::from_value_with_enum_context(value, context)
567        })
568    }
569}
570
571impl<T> RuntimeValueMeta for BTreeSet<T>
572where
573    T: Ord,
574{
575    fn kind() -> RuntimeValueKind {
576        RuntimeValueKind::Structured { queryable: true }
577    }
578}
579
580impl<T> RuntimeValueEncode for BTreeSet<T>
581where
582    T: Ord + RuntimeValueEncode,
583{
584    fn to_value(&self) -> Value {
585        runtime_value_collection_to_value(self)
586    }
587}
588
589impl<T> RuntimeValueDecode for BTreeSet<T>
590where
591    T: Ord + RuntimeValueDecode,
592{
593    fn from_value(value: &Value) -> Option<Self> {
594        runtime_value_btree_set_from_value(value)
595    }
596
597    fn from_value_with_enum_context(
598        value: &Value,
599        context: &dyn RuntimeEnumContext,
600    ) -> Option<Self> {
601        decode_runtime_value_set(value, |value| {
602            T::from_value_with_enum_context(value, context)
603        })
604    }
605}
606
607impl<K, V> RuntimeValueMeta for BTreeMap<K, V>
608where
609    K: Ord,
610{
611    fn kind() -> RuntimeValueKind {
612        RuntimeValueKind::Structured { queryable: true }
613    }
614}
615
616impl<K, V> RuntimeValueEncode for BTreeMap<K, V>
617where
618    K: Ord + RuntimeValueEncode,
619    V: RuntimeValueEncode,
620{
621    fn to_value(&self) -> Value {
622        runtime_value_map_collection_to_value(self, std::any::type_name::<Self>())
623    }
624}
625
626impl<K, V> RuntimeValueDecode for BTreeMap<K, V>
627where
628    K: Ord + RuntimeValueDecode,
629    V: RuntimeValueDecode,
630{
631    fn from_value(value: &Value) -> Option<Self> {
632        runtime_value_btree_map_from_value(value)
633    }
634
635    fn from_value_with_enum_context(
636        value: &Value,
637        context: &dyn RuntimeEnumContext,
638    ) -> Option<Self> {
639        decode_runtime_value_map(
640            value,
641            |key| K::from_value_with_enum_context(key, context),
642            |value| V::from_value_with_enum_context(value, context),
643        )
644    }
645}
646
647macro_rules! impl_runtime_value {
648    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
649        $(
650            impl RuntimeValueMeta for $type {
651                fn kind() -> RuntimeValueKind {
652                    RuntimeValueKind::Atomic
653                }
654            }
655
656            impl RuntimeValueEncode for $type {
657                fn to_value(&self) -> Value {
658                    Value::$variant((*self).into())
659                }
660            }
661
662            impl RuntimeValueDecode for $type {
663                fn from_value(value: &Value) -> Option<Self> {
664                    match value {
665                        Value::$variant(value) => (*value).try_into().ok(),
666                        _ => None,
667                    }
668                }
669            }
670        )*
671    };
672}
673
674impl_runtime_value!(
675    i8 => Int64,
676    i16 => Int64,
677    i32 => Int64,
678    i64 => Int64,
679    i128 => Int128,
680    u8 => Nat64,
681    u16 => Nat64,
682    u32 => Nat64,
683    u64 => Nat64,
684    u128 => Nat128,
685    bool => Bool,
686);