Skip to main content

icydb_core/traits/
mod.rs

1//! Module: traits
2//!
3//! Responsibility: module-local ownership and contracts for traits.
4//! Does not own: cross-module orchestration outside this module.
5//! Boundary: exposes this module API while keeping implementation details internal.
6
7#[macro_use]
8mod macros;
9mod atomic;
10mod visitor;
11
12use crate::{
13    model::field::{FieldKind, FieldStorageDecode},
14    prelude::*,
15    types::{EntityTag, Id},
16    value::ValueEnum,
17    visitor::VisitorContext,
18};
19use std::collections::{BTreeMap, BTreeSet};
20
21pub use atomic::*;
22pub use visitor::*;
23
24// -----------------------------------------------------------------------------
25// Standard re-exports for `traits::X` ergonomics
26// -----------------------------------------------------------------------------
27
28pub use canic_cdk::structures::storable::Storable;
29pub use num_traits::{FromPrimitive as NumFromPrimitive, NumCast, ToPrimitive as NumToPrimitive};
30pub use serde::{Deserialize, Serialize, de::DeserializeOwned};
31pub use std::{
32    cmp::{Eq, Ordering, PartialEq},
33    convert::From,
34    default::Default,
35    fmt::Debug,
36    hash::Hash,
37    ops::{Add, AddAssign, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign},
38};
39
40// ============================================================================
41// FOUNDATIONAL KINDS
42// ============================================================================
43//
44// These traits define *where* something lives in the system,
45// not what data it contains.
46//
47
48///
49/// Path
50/// Fully-qualified schema path.
51///
52
53pub trait Path {
54    const PATH: &'static str;
55}
56
57///
58/// Kind
59/// Marker for all schema/runtime nodes.
60///
61
62pub trait Kind: Path + 'static {}
63impl<T> Kind for T where T: Path + 'static {}
64
65///
66/// CanisterKind
67/// Marker for canister namespaces
68///
69
70pub trait CanisterKind: Kind {
71    /// Stable memory slot used for commit marker storage.
72    const COMMIT_MEMORY_ID: u8;
73}
74
75///
76/// StoreKind
77/// Marker for data stores bound to a canister
78///
79
80pub trait StoreKind: Kind {
81    type Canister: CanisterKind;
82}
83
84// ============================================================================
85// ENTITY IDENTITY & SCHEMA
86// ============================================================================
87//
88// These traits describe *what an entity is*, not how it is stored
89// or manipulated at runtime.
90//
91
92///
93/// EntityKey
94///
95/// Associates an entity with the primitive type used as its primary key.
96///
97/// ## Semantics
98/// - Implemented for entity types
99/// - `Self::Key` is the *storage representation* of the primary key
100/// - Keys are plain values (Ulid, u64, Principal, …)
101/// - Typed identity is provided by `Id<Self>`, not by the key itself
102/// - Keys are public identifiers and are never authority-bearing capabilities
103///
104
105pub trait EntityKey {
106    type Key: Copy + Debug + Eq + Ord + FieldValue + EntityKeyBytes + 'static;
107}
108
109///
110/// EntityKeyBytes
111///
112
113pub trait EntityKeyBytes {
114    /// Exact number of bytes produced.
115    const BYTE_LEN: usize;
116
117    /// Write bytes into the provided buffer.
118    fn write_bytes(&self, out: &mut [u8]);
119}
120
121macro_rules! impl_entity_key_bytes_numeric {
122    ($($ty:ty),* $(,)?) => {
123        $(
124            impl EntityKeyBytes for $ty {
125                const BYTE_LEN: usize = ::core::mem::size_of::<Self>();
126
127                fn write_bytes(&self, out: &mut [u8]) {
128                    assert_eq!(out.len(), Self::BYTE_LEN);
129                    out.copy_from_slice(&self.to_be_bytes());
130                }
131            }
132        )*
133    };
134}
135
136impl_entity_key_bytes_numeric!(i8, i16, i32, i64, u8, u16, u32, u64);
137
138impl EntityKeyBytes for () {
139    const BYTE_LEN: usize = 0;
140
141    fn write_bytes(&self, out: &mut [u8]) {
142        assert_eq!(out.len(), Self::BYTE_LEN);
143    }
144}
145
146///
147/// EntitySchema
148///
149/// Declared runtime schema facts for an entity.
150///
151/// `NAME` seeds self-referential model construction for relation metadata.
152/// `MODEL` remains the authoritative runtime authority for field, primary-key,
153/// and index metadata consumed by planning and execution.
154///
155
156pub trait EntitySchema: EntityKey {
157    const NAME: &'static str;
158    const MODEL: &'static EntityModel;
159}
160
161// ============================================================================
162// ENTITY RUNTIME COMPOSITION
163// ============================================================================
164//
165// These traits bind schema-defined entities into runtime placement.
166//
167
168///
169/// EntityPlacement
170///
171/// Runtime placement of an entity
172///
173
174pub trait EntityPlacement {
175    type Store: StoreKind;
176    type Canister: CanisterKind;
177}
178
179///
180/// EntityKind
181///
182/// Fully runtime-bound entity.
183///
184/// This is the *maximum* entity contract and should only be
185/// required by code that actually touches storage or execution.
186///
187
188pub trait EntityKind: EntitySchema + EntityPlacement + Kind + TypeKind {
189    const ENTITY_TAG: EntityTag;
190}
191
192// ============================================================================
193// ENTITY VALUES
194// ============================================================================
195//
196// These traits describe *instances* of entities.
197//
198
199///
200/// EntityValue
201///
202/// A concrete entity value that can present a typed identity at boundaries.
203///
204/// Implementors store primitive key material internally.
205/// `id()` constructs a typed `Id<Self>` view on demand.
206/// The returned `Id<Self>` is a public identifier, not proof of authority.
207///
208
209pub trait EntityValue: EntityKey + FieldProjection + Sized {
210    fn id(&self) -> Id<Self>;
211}
212
213/// Marker for entities with exactly one logical row.
214pub trait SingletonEntity: EntityValue {}
215
216///
217// ============================================================================
218// TYPE SYSTEM CONTRACTS
219// ============================================================================
220//
221// These traits define behavioral expectations for schema-defined types.
222//
223
224///
225/// TypeKind
226///
227/// Any schema-defined data type.
228///
229/// This is a *strong* contract and should only be required
230/// where full lifecycle semantics are needed.
231///
232
233pub trait TypeKind:
234    Kind + Clone + Default + Serialize + DeserializeOwned + Sanitize + Validate + Visitable + PartialEq
235{
236}
237
238impl<T> TypeKind for T where
239    T: Kind
240        + Clone
241        + Default
242        + DeserializeOwned
243        + PartialEq
244        + Serialize
245        + Sanitize
246        + Validate
247        + Visitable
248{
249}
250
251///
252/// FieldTypeMeta
253///
254/// Static runtime field metadata for one schema-facing value type.
255/// This is the single authority for generated field kind and storage-decode
256/// metadata, so callers do not need per-type inherent constants.
257///
258
259pub trait FieldTypeMeta {
260    /// Semantic field kind used for runtime planning and validation.
261    const KIND: FieldKind;
262
263    /// Persisted decode contract used by row and payload decoding.
264    const STORAGE_DECODE: FieldStorageDecode;
265}
266
267/// ============================================================================
268/// QUERY VALUE BOUNDARIES
269/// ============================================================================
270
271///
272/// Collection
273///
274/// Explicit iteration contract for list/set wrapper types.
275/// Keeps generic collection code on one stable boundary even when concrete
276/// wrapper types opt into direct container ergonomics.
277///
278
279pub trait Collection {
280    type Item;
281
282    /// Iterator over the collection's items, tied to the borrow of `self`.
283    type Iter<'a>: Iterator<Item = &'a Self::Item> + 'a
284    where
285        Self: 'a;
286
287    /// Returns an iterator over the collection's items.
288    fn iter(&self) -> Self::Iter<'_>;
289
290    /// Returns the number of items in the collection.
291    fn len(&self) -> usize;
292
293    /// Returns true if the collection contains no items.
294    fn is_empty(&self) -> bool {
295        self.len() == 0
296    }
297}
298
299///
300/// MapCollection
301///
302/// Explicit iteration contract for map wrapper types.
303/// Keeps generic map code on one stable boundary even when concrete wrapper
304/// types opt into direct container ergonomics.
305///
306
307pub trait MapCollection {
308    type Key;
309    type Value;
310
311    /// Iterator over the map's key/value pairs, tied to the borrow of `self`.
312    type Iter<'a>: Iterator<Item = (&'a Self::Key, &'a Self::Value)> + 'a
313    where
314        Self: 'a;
315
316    /// Returns an iterator over the map's key/value pairs.
317    fn iter(&self) -> Self::Iter<'_>;
318
319    /// Returns the number of entries in the map.
320    fn len(&self) -> usize;
321
322    /// Returns true if the map contains no entries.
323    fn is_empty(&self) -> bool {
324        self.len() == 0
325    }
326}
327
328pub trait EnumValue {
329    fn to_value_enum(&self) -> ValueEnum;
330}
331
332pub trait FieldProjection {
333    /// Resolve one field value by stable field slot index.
334    fn get_value_by_index(&self, index: usize) -> Option<Value>;
335}
336
337///
338/// FieldValueKind
339///
340/// Schema affordance classification for query planning and validation.
341/// Describes whether a field is planner-addressable and predicate-queryable.
342///
343
344#[derive(Clone, Copy, Debug, Eq, PartialEq)]
345pub enum FieldValueKind {
346    /// Planner-addressable atomic value.
347    Atomic,
348
349    /// Structured value with known internal fields that the planner
350    /// does not reason about as an addressable query target.
351    Structured {
352        /// Whether predicates may be expressed against this field.
353        queryable: bool,
354    },
355}
356
357impl FieldValueKind {
358    #[must_use]
359    pub const fn is_queryable(self) -> bool {
360        match self {
361            Self::Atomic => true,
362            Self::Structured { queryable } => queryable,
363        }
364    }
365}
366
367///
368/// FieldValue
369///
370/// Conversion boundary for values used in query predicates.
371///
372/// Represents values that can appear on the *right-hand side* of predicates.
373///
374
375pub trait FieldValue {
376    fn kind() -> FieldValueKind
377    where
378        Self: Sized;
379
380    fn to_value(&self) -> Value;
381
382    #[must_use]
383    fn from_value(value: &Value) -> Option<Self>
384    where
385        Self: Sized;
386}
387
388///
389/// field_value_collection_to_value
390///
391/// Shared collection-to-`Value::List` lowering for generated wrapper types.
392/// This keeps list and set `FieldValue` impls from re-emitting the same item
393/// iteration body for every generated schema type.
394///
395
396pub fn field_value_collection_to_value<C>(collection: &C) -> Value
397where
398    C: Collection,
399    C::Item: FieldValue,
400{
401    Value::List(collection.iter().map(FieldValue::to_value).collect())
402}
403
404///
405/// field_value_vec_from_value
406///
407/// Shared `Value::List` decode for generated list wrapper types.
408/// This preserves typed `FieldValue` decoding while avoiding one repeated loop
409/// body per generated list schema type.
410///
411
412#[must_use]
413pub fn field_value_vec_from_value<T>(value: &Value) -> Option<Vec<T>>
414where
415    T: FieldValue,
416{
417    let Value::List(values) = value else {
418        return None;
419    };
420
421    let mut out = Vec::with_capacity(values.len());
422    for value in values {
423        out.push(T::from_value(value)?);
424    }
425
426    Some(out)
427}
428
429///
430/// field_value_btree_set_from_value
431///
432/// Shared `Value::List` decode for generated set wrapper types.
433/// This preserves duplicate rejection while avoiding one repeated loop body
434/// per generated set schema type.
435///
436
437#[must_use]
438pub fn field_value_btree_set_from_value<T>(value: &Value) -> Option<BTreeSet<T>>
439where
440    T: FieldValue + Ord,
441{
442    let Value::List(values) = value else {
443        return None;
444    };
445
446    let mut out = BTreeSet::new();
447    for value in values {
448        let item = T::from_value(value)?;
449        if !out.insert(item) {
450            return None;
451        }
452    }
453
454    Some(out)
455}
456
457///
458/// field_value_map_collection_to_value
459///
460/// Shared map-to-`Value::Map` lowering for generated map wrapper types.
461/// This keeps canonicalization and duplicate-key checks in one runtime helper
462/// instead of re-emitting the same map conversion body per generated schema
463/// type.
464///
465
466pub fn field_value_map_collection_to_value<M>(map: &M, path: &'static str) -> Value
467where
468    M: MapCollection,
469    M::Key: FieldValue,
470    M::Value: FieldValue,
471{
472    let mut entries: Vec<(Value, Value)> = map
473        .iter()
474        .map(|(key, value)| (FieldValue::to_value(key), FieldValue::to_value(value)))
475        .collect();
476
477    if let Err(err) = Value::validate_map_entries(entries.as_slice()) {
478        debug_assert!(false, "invalid map field value for {path}: {err}");
479        return Value::Map(entries);
480    }
481
482    Value::sort_map_entries_in_place(entries.as_mut_slice());
483
484    for i in 1..entries.len() {
485        let (left_key, _) = &entries[i - 1];
486        let (right_key, _) = &entries[i];
487        if Value::canonical_cmp_key(left_key, right_key) == Ordering::Equal {
488            debug_assert!(
489                false,
490                "duplicate map key in {path} after FieldValue::to_value canonicalization",
491            );
492            break;
493        }
494    }
495
496    Value::Map(entries)
497}
498
499///
500/// field_value_btree_map_from_value
501///
502/// Shared `Value::Map` decode for generated map wrapper types.
503/// This keeps canonical-entry normalization in one runtime helper instead of
504/// re-emitting the same decode body per generated schema type.
505///
506
507#[must_use]
508pub fn field_value_btree_map_from_value<K, V>(value: &Value) -> Option<BTreeMap<K, V>>
509where
510    K: FieldValue + Ord,
511    V: FieldValue,
512{
513    let Value::Map(entries) = value else {
514        return None;
515    };
516
517    let normalized = Value::normalize_map_entries(entries.clone()).ok()?;
518    if normalized.as_slice() != entries.as_slice() {
519        return None;
520    }
521
522    let mut map = BTreeMap::new();
523    for (entry_key, entry_value) in normalized {
524        let key = K::from_value(&entry_key)?;
525        let value = V::from_value(&entry_value)?;
526        map.insert(key, value);
527    }
528
529    Some(map)
530}
531
532///
533/// field_value_from_vec_into
534///
535/// Shared `Vec<I> -> Vec<T>` conversion for generated wrapper `From<Vec<I>>`
536/// impls. This keeps list wrappers from re-emitting the same `into_iter` /
537/// `map(Into::into)` collection body for every generated schema type.
538///
539
540#[must_use]
541pub fn field_value_from_vec_into<T, I>(entries: Vec<I>) -> Vec<T>
542where
543    I: Into<T>,
544{
545    entries.into_iter().map(Into::into).collect()
546}
547
548///
549/// field_value_from_vec_into_btree_set
550///
551/// Shared `Vec<I> -> BTreeSet<T>` conversion for generated set wrapper
552/// `From<Vec<I>>` impls. This keeps set wrappers from re-emitting the same
553/// collection conversion body for every generated schema type.
554///
555
556#[must_use]
557pub fn field_value_from_vec_into_btree_set<T, I>(entries: Vec<I>) -> BTreeSet<T>
558where
559    I: Into<T>,
560    T: Ord,
561{
562    entries.into_iter().map(Into::into).collect()
563}
564
565///
566/// field_value_from_vec_into_btree_map
567///
568/// Shared `Vec<(IK, IV)> -> BTreeMap<K, V>` conversion for generated map
569/// wrapper `From<Vec<(IK, IV)>>` impls. This keeps map wrappers from
570/// re-emitting the same pair-conversion body for every generated schema type.
571///
572
573#[must_use]
574pub fn field_value_from_vec_into_btree_map<K, V, IK, IV>(entries: Vec<(IK, IV)>) -> BTreeMap<K, V>
575where
576    IK: Into<K>,
577    IV: Into<V>,
578    K: Ord,
579{
580    entries
581        .into_iter()
582        .map(|(key, value)| (key.into(), value.into()))
583        .collect()
584}
585
586///
587/// field_value_into
588///
589/// Shared `Into<T>` lowering for generated newtype `From<U>` impls.
590/// This keeps newtype wrappers from re-emitting the same single-field
591/// conversion body for every generated schema type.
592///
593
594#[must_use]
595pub fn field_value_into<T, U>(value: U) -> T
596where
597    U: Into<T>,
598{
599    value.into()
600}
601
602impl FieldValue for &str {
603    fn kind() -> FieldValueKind {
604        FieldValueKind::Atomic
605    }
606
607    fn to_value(&self) -> Value {
608        Value::Text((*self).to_string())
609    }
610
611    fn from_value(_value: &Value) -> Option<Self> {
612        None
613    }
614}
615
616impl FieldValue for String {
617    fn kind() -> FieldValueKind {
618        FieldValueKind::Atomic
619    }
620
621    fn to_value(&self) -> Value {
622        Value::Text(self.clone())
623    }
624
625    fn from_value(value: &Value) -> Option<Self> {
626        match value {
627            Value::Text(v) => Some(v.clone()),
628            _ => None,
629        }
630    }
631}
632
633impl<T: FieldValue> FieldValue for Option<T> {
634    fn kind() -> FieldValueKind {
635        T::kind()
636    }
637
638    fn to_value(&self) -> Value {
639        match self {
640            Some(v) => v.to_value(),
641            None => Value::Null,
642        }
643    }
644
645    fn from_value(value: &Value) -> Option<Self> {
646        if matches!(value, Value::Null) {
647            return Some(None);
648        }
649
650        T::from_value(value).map(Some)
651    }
652}
653
654impl<T: FieldValue> FieldValue for Box<T> {
655    fn kind() -> FieldValueKind {
656        T::kind()
657    }
658
659    fn to_value(&self) -> Value {
660        (**self).to_value()
661    }
662
663    fn from_value(value: &Value) -> Option<Self> {
664        T::from_value(value).map(Self::new)
665    }
666}
667
668impl<T: FieldValue> FieldValue for Vec<Box<T>> {
669    fn kind() -> FieldValueKind {
670        FieldValueKind::Structured { queryable: true }
671    }
672
673    fn to_value(&self) -> Value {
674        Value::List(self.iter().map(FieldValue::to_value).collect())
675    }
676
677    fn from_value(value: &Value) -> Option<Self> {
678        let Value::List(items) = value else {
679            return None;
680        };
681
682        let mut out = Self::with_capacity(items.len());
683        for item in items {
684            out.push(Box::new(T::from_value(item)?));
685        }
686
687        Some(out)
688    }
689}
690
691// impl_field_value
692#[macro_export]
693macro_rules! impl_field_value {
694    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
695        $(
696            impl FieldValue for $type {
697                fn kind() -> FieldValueKind {
698                    FieldValueKind::Atomic
699                }
700
701                fn to_value(&self) -> Value {
702                    Value::$variant((*self).into())
703                }
704
705                fn from_value(value: &Value) -> Option<Self> {
706                    match value {
707                        Value::$variant(v) => (*v).try_into().ok(),
708                        _ => None,
709                    }
710                }
711            }
712        )*
713    };
714}
715
716impl_field_value!(
717    i8 => Int,
718    i16 => Int,
719    i32 => Int,
720    i64 => Int,
721    u8 => Uint,
722    u16 => Uint,
723    u32 => Uint,
724    u64 => Uint,
725    bool => Bool,
726);
727
728/// ============================================================================
729/// MISC HELPERS
730/// ============================================================================
731
732///
733/// Inner
734///
735/// For newtypes to expose their innermost value.
736///
737
738pub trait Inner<T> {
739    fn inner(&self) -> &T;
740    fn into_inner(self) -> T;
741}
742
743impl<T> Inner<T> for T
744where
745    T: Atomic,
746{
747    fn inner(&self) -> &T {
748        self
749    }
750
751    fn into_inner(self) -> T {
752        self
753    }
754}
755
756///
757/// Repr
758///
759/// Internal representation boundary for scalar wrapper types.
760///
761
762pub trait Repr {
763    type Inner;
764
765    fn repr(&self) -> Self::Inner;
766    fn from_repr(inner: Self::Inner) -> Self;
767}
768
769/// ============================================================================
770/// SANITIZATION / VALIDATION
771/// ============================================================================
772
773///
774/// Sanitizer
775///
776/// Transforms a value into a sanitized version.
777///
778
779pub trait Sanitizer<T> {
780    fn sanitize(&self, value: &mut T) -> Result<(), String>;
781}
782
783///
784/// Validator
785///
786/// Allows a node to validate values.
787///
788
789pub trait Validator<T: ?Sized> {
790    fn validate(&self, value: &T, ctx: &mut dyn VisitorContext);
791}