Skip to main content

icydb_core/traits/
mod.rs

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