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