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///
266/// EntityCreateType
267///
268/// Entity-owned association from one entity type to its generated create
269/// input shape.
270/// This keeps the public create-input surface generic at the facade boundary
271/// while generated code remains free to pick any concrete backing type name.
272///
273
274pub trait EntityCreateType: EntityValue {
275 type Create: EntityCreateInput<Entity = Self>;
276}
277
278/// Marker for entities with exactly one logical row.
279pub trait SingletonEntity: EntityValue {}
280
281///
282// ============================================================================
283// TYPE SYSTEM CONTRACTS
284// ============================================================================
285//
286// These traits define behavioral expectations for schema-defined types.
287//
288
289///
290/// TypeKind
291///
292/// Any schema-defined data type.
293///
294/// This is a *strong* contract and should only be required
295/// where full lifecycle semantics are needed.
296///
297
298pub trait TypeKind:
299 Kind + Clone + Default + Serialize + DeserializeOwned + Sanitize + Validate + Visitable + PartialEq
300{
301}
302
303impl<T> TypeKind for T where
304 T: Kind
305 + Clone
306 + Default
307 + DeserializeOwned
308 + PartialEq
309 + Serialize
310 + Sanitize
311 + Validate
312 + Visitable
313{
314}
315
316///
317/// FieldTypeMeta
318///
319/// Static runtime field metadata for one schema-facing value type.
320/// This is the single authority for generated field kind and storage-decode
321/// metadata, so callers do not need per-type inherent constants.
322///
323
324pub trait FieldTypeMeta {
325 /// Semantic field kind used for runtime planning and validation.
326 const KIND: FieldKind;
327
328 /// Persisted decode contract used by row and payload decoding.
329 const STORAGE_DECODE: FieldStorageDecode;
330}
331
332/// ============================================================================
333/// QUERY VALUE BOUNDARIES
334/// ============================================================================
335
336///
337/// Collection
338///
339/// Explicit iteration contract for list/set wrapper types.
340/// Keeps generic collection code on one stable boundary even when concrete
341/// wrapper types opt into direct container ergonomics.
342///
343
344pub trait Collection {
345 type Item;
346
347 /// Iterator over the collection's items, tied to the borrow of `self`.
348 type Iter<'a>: Iterator<Item = &'a Self::Item> + 'a
349 where
350 Self: 'a;
351
352 /// Returns an iterator over the collection's items.
353 fn iter(&self) -> Self::Iter<'_>;
354
355 /// Returns the number of items in the collection.
356 fn len(&self) -> usize;
357
358 /// Returns true if the collection contains no items.
359 fn is_empty(&self) -> bool {
360 self.len() == 0
361 }
362}
363
364///
365/// MapCollection
366///
367/// Explicit iteration contract for map wrapper types.
368/// Keeps generic map code on one stable boundary even when concrete wrapper
369/// types opt into direct container ergonomics.
370///
371
372pub trait MapCollection {
373 type Key;
374 type Value;
375
376 /// Iterator over the map's key/value pairs, tied to the borrow of `self`.
377 type Iter<'a>: Iterator<Item = (&'a Self::Key, &'a Self::Value)> + 'a
378 where
379 Self: 'a;
380
381 /// Returns an iterator over the map's key/value pairs.
382 fn iter(&self) -> Self::Iter<'_>;
383
384 /// Returns the number of entries in the map.
385 fn len(&self) -> usize;
386
387 /// Returns true if the map contains no entries.
388 fn is_empty(&self) -> bool {
389 self.len() == 0
390 }
391}
392
393pub trait EnumValue {
394 fn to_value_enum(&self) -> ValueEnum;
395}
396
397pub trait FieldProjection {
398 /// Resolve one field value by stable field slot index.
399 fn get_value_by_index(&self, index: usize) -> Option<Value>;
400}
401
402///
403/// FieldValueKind
404///
405/// Schema affordance classification for query planning and validation.
406/// Describes whether a field is planner-addressable and predicate-queryable.
407///
408
409#[derive(Clone, Copy, Debug, Eq, PartialEq)]
410pub enum FieldValueKind {
411 /// Planner-addressable atomic value.
412 Atomic,
413
414 /// Structured value with known internal fields that the planner
415 /// does not reason about as an addressable query target.
416 Structured {
417 /// Whether predicates may be expressed against this field.
418 queryable: bool,
419 },
420}
421
422impl FieldValueKind {
423 #[must_use]
424 pub const fn is_queryable(self) -> bool {
425 match self {
426 Self::Atomic => true,
427 Self::Structured { queryable } => queryable,
428 }
429 }
430}
431
432///
433/// FieldValue
434///
435/// Conversion boundary for values used in query predicates.
436///
437/// Represents values that can appear on the *right-hand side* of predicates.
438///
439
440pub trait FieldValue {
441 fn kind() -> FieldValueKind
442 where
443 Self: Sized;
444
445 fn to_value(&self) -> Value;
446
447 #[must_use]
448 fn from_value(value: &Value) -> Option<Self>
449 where
450 Self: Sized;
451}
452
453///
454/// field_value_collection_to_value
455///
456/// Shared collection-to-`Value::List` lowering for generated wrapper types.
457/// This keeps list and set `FieldValue` impls from re-emitting the same item
458/// iteration body for every generated schema type.
459///
460
461pub fn field_value_collection_to_value<C>(collection: &C) -> Value
462where
463 C: Collection,
464 C::Item: FieldValue,
465{
466 Value::List(collection.iter().map(FieldValue::to_value).collect())
467}
468
469///
470/// field_value_vec_from_value
471///
472/// Shared `Value::List` decode for generated list wrapper types.
473/// This preserves typed `FieldValue` decoding while avoiding one repeated loop
474/// body per generated list schema type.
475///
476
477#[must_use]
478pub fn field_value_vec_from_value<T>(value: &Value) -> Option<Vec<T>>
479where
480 T: FieldValue,
481{
482 let Value::List(values) = value else {
483 return None;
484 };
485
486 let mut out = Vec::with_capacity(values.len());
487 for value in values {
488 out.push(T::from_value(value)?);
489 }
490
491 Some(out)
492}
493
494///
495/// field_value_btree_set_from_value
496///
497/// Shared `Value::List` decode for generated set wrapper types.
498/// This preserves duplicate rejection while avoiding one repeated loop body
499/// per generated set schema type.
500///
501
502#[must_use]
503pub fn field_value_btree_set_from_value<T>(value: &Value) -> Option<BTreeSet<T>>
504where
505 T: FieldValue + Ord,
506{
507 let Value::List(values) = value else {
508 return None;
509 };
510
511 let mut out = BTreeSet::new();
512 for value in values {
513 let item = T::from_value(value)?;
514 if !out.insert(item) {
515 return None;
516 }
517 }
518
519 Some(out)
520}
521
522///
523/// field_value_map_collection_to_value
524///
525/// Shared map-to-`Value::Map` lowering for generated map wrapper types.
526/// This keeps canonicalization and duplicate-key checks in one runtime helper
527/// instead of re-emitting the same map conversion body per generated schema
528/// type.
529///
530
531pub fn field_value_map_collection_to_value<M>(map: &M, path: &'static str) -> Value
532where
533 M: MapCollection,
534 M::Key: FieldValue,
535 M::Value: FieldValue,
536{
537 let mut entries: Vec<(Value, Value)> = map
538 .iter()
539 .map(|(key, value)| (FieldValue::to_value(key), FieldValue::to_value(value)))
540 .collect();
541
542 if let Err(err) = Value::validate_map_entries(entries.as_slice()) {
543 debug_assert!(false, "invalid map field value for {path}: {err}");
544 return Value::Map(entries);
545 }
546
547 Value::sort_map_entries_in_place(entries.as_mut_slice());
548
549 for i in 1..entries.len() {
550 let (left_key, _) = &entries[i - 1];
551 let (right_key, _) = &entries[i];
552 if Value::canonical_cmp_key(left_key, right_key) == Ordering::Equal {
553 debug_assert!(
554 false,
555 "duplicate map key in {path} after FieldValue::to_value canonicalization",
556 );
557 break;
558 }
559 }
560
561 Value::Map(entries)
562}
563
564///
565/// field_value_btree_map_from_value
566///
567/// Shared `Value::Map` decode for generated map wrapper types.
568/// This keeps canonical-entry normalization in one runtime helper instead of
569/// re-emitting the same decode body per generated schema type.
570///
571
572#[must_use]
573pub fn field_value_btree_map_from_value<K, V>(value: &Value) -> Option<BTreeMap<K, V>>
574where
575 K: FieldValue + Ord,
576 V: FieldValue,
577{
578 let Value::Map(entries) = value else {
579 return None;
580 };
581
582 let normalized = Value::normalize_map_entries(entries.clone()).ok()?;
583 if normalized.as_slice() != entries.as_slice() {
584 return None;
585 }
586
587 let mut map = BTreeMap::new();
588 for (entry_key, entry_value) in normalized {
589 let key = K::from_value(&entry_key)?;
590 let value = V::from_value(&entry_value)?;
591 map.insert(key, value);
592 }
593
594 Some(map)
595}
596
597///
598/// field_value_from_vec_into
599///
600/// Shared `Vec<I> -> Vec<T>` conversion for generated wrapper `From<Vec<I>>`
601/// impls. This keeps list wrappers from re-emitting the same `into_iter` /
602/// `map(Into::into)` collection body for every generated schema type.
603///
604
605#[must_use]
606pub fn field_value_from_vec_into<T, I>(entries: Vec<I>) -> Vec<T>
607where
608 I: Into<T>,
609{
610 entries.into_iter().map(Into::into).collect()
611}
612
613///
614/// field_value_from_vec_into_btree_set
615///
616/// Shared `Vec<I> -> BTreeSet<T>` conversion for generated set wrapper
617/// `From<Vec<I>>` impls. This keeps set wrappers from re-emitting the same
618/// collection conversion body for every generated schema type.
619///
620
621#[must_use]
622pub fn field_value_from_vec_into_btree_set<T, I>(entries: Vec<I>) -> BTreeSet<T>
623where
624 I: Into<T>,
625 T: Ord,
626{
627 entries.into_iter().map(Into::into).collect()
628}
629
630///
631/// field_value_from_vec_into_btree_map
632///
633/// Shared `Vec<(IK, IV)> -> BTreeMap<K, V>` conversion for generated map
634/// wrapper `From<Vec<(IK, IV)>>` impls. This keeps map wrappers from
635/// re-emitting the same pair-conversion body for every generated schema type.
636///
637
638#[must_use]
639pub fn field_value_from_vec_into_btree_map<K, V, IK, IV>(entries: Vec<(IK, IV)>) -> BTreeMap<K, V>
640where
641 IK: Into<K>,
642 IV: Into<V>,
643 K: Ord,
644{
645 entries
646 .into_iter()
647 .map(|(key, value)| (key.into(), value.into()))
648 .collect()
649}
650
651///
652/// field_value_into
653///
654/// Shared `Into<T>` lowering for generated newtype `From<U>` impls.
655/// This keeps newtype wrappers from re-emitting the same single-field
656/// conversion body for every generated schema type.
657///
658
659#[must_use]
660pub fn field_value_into<T, U>(value: U) -> T
661where
662 U: Into<T>,
663{
664 value.into()
665}
666
667impl FieldValue for &str {
668 fn kind() -> FieldValueKind {
669 FieldValueKind::Atomic
670 }
671
672 fn to_value(&self) -> Value {
673 Value::Text((*self).to_string())
674 }
675
676 fn from_value(_value: &Value) -> Option<Self> {
677 None
678 }
679}
680
681impl FieldValue for String {
682 fn kind() -> FieldValueKind {
683 FieldValueKind::Atomic
684 }
685
686 fn to_value(&self) -> Value {
687 Value::Text(self.clone())
688 }
689
690 fn from_value(value: &Value) -> Option<Self> {
691 match value {
692 Value::Text(v) => Some(v.clone()),
693 _ => None,
694 }
695 }
696}
697
698impl<T: FieldValue> FieldValue for Option<T> {
699 fn kind() -> FieldValueKind {
700 T::kind()
701 }
702
703 fn to_value(&self) -> Value {
704 match self {
705 Some(v) => v.to_value(),
706 None => Value::Null,
707 }
708 }
709
710 fn from_value(value: &Value) -> Option<Self> {
711 if matches!(value, Value::Null) {
712 return Some(None);
713 }
714
715 T::from_value(value).map(Some)
716 }
717}
718
719impl<T: FieldValue> FieldValue for Box<T> {
720 fn kind() -> FieldValueKind {
721 T::kind()
722 }
723
724 fn to_value(&self) -> Value {
725 (**self).to_value()
726 }
727
728 fn from_value(value: &Value) -> Option<Self> {
729 T::from_value(value).map(Self::new)
730 }
731}
732
733impl<T: FieldValue> FieldValue for Vec<Box<T>> {
734 fn kind() -> FieldValueKind {
735 FieldValueKind::Structured { queryable: true }
736 }
737
738 fn to_value(&self) -> Value {
739 Value::List(self.iter().map(FieldValue::to_value).collect())
740 }
741
742 fn from_value(value: &Value) -> Option<Self> {
743 let Value::List(items) = value else {
744 return None;
745 };
746
747 let mut out = Self::with_capacity(items.len());
748 for item in items {
749 out.push(Box::new(T::from_value(item)?));
750 }
751
752 Some(out)
753 }
754}
755
756// impl_field_value
757#[macro_export]
758macro_rules! impl_field_value {
759 ( $( $type:ty => $variant:ident ),* $(,)? ) => {
760 $(
761 impl FieldValue for $type {
762 fn kind() -> FieldValueKind {
763 FieldValueKind::Atomic
764 }
765
766 fn to_value(&self) -> Value {
767 Value::$variant((*self).into())
768 }
769
770 fn from_value(value: &Value) -> Option<Self> {
771 match value {
772 Value::$variant(v) => (*v).try_into().ok(),
773 _ => None,
774 }
775 }
776 }
777 )*
778 };
779}
780
781impl_field_value!(
782 i8 => Int,
783 i16 => Int,
784 i32 => Int,
785 i64 => Int,
786 u8 => Uint,
787 u16 => Uint,
788 u32 => Uint,
789 u64 => Uint,
790 bool => Bool,
791);
792
793/// ============================================================================
794/// MISC HELPERS
795/// ============================================================================
796
797///
798/// Inner
799///
800/// For newtypes to expose their innermost value.
801///
802
803pub trait Inner<T> {
804 fn inner(&self) -> &T;
805 fn into_inner(self) -> T;
806}
807
808impl<T> Inner<T> for T
809where
810 T: Atomic,
811{
812 fn inner(&self) -> &T {
813 self
814 }
815
816 fn into_inner(self) -> T {
817 self
818 }
819}
820
821///
822/// Repr
823///
824/// Internal representation boundary for scalar wrapper types.
825///
826
827pub trait Repr {
828 type Inner;
829
830 fn repr(&self) -> Self::Inner;
831 fn from_repr(inner: Self::Inner) -> Self;
832}
833
834/// ============================================================================
835/// SANITIZATION / VALIDATION
836/// ============================================================================
837
838///
839/// Sanitizer
840///
841/// Transforms a value into a sanitized version.
842///
843
844pub trait Sanitizer<T> {
845 fn sanitize(&self, value: &mut T) -> Result<(), String>;
846
847 fn sanitize_with_context(
848 &self,
849 value: &mut T,
850 ctx: &mut dyn VisitorContext,
851 ) -> Result<(), String> {
852 let _ = ctx;
853
854 self.sanitize(value)
855 }
856}
857
858///
859/// Validator
860///
861/// Allows a node to validate values.
862///
863
864pub trait Validator<T: ?Sized> {
865 fn validate(&self, value: &T, ctx: &mut dyn VisitorContext);
866}