Skip to main content

icydb_core/traits/
mod.rs

1#[macro_use]
2mod macros;
3mod view;
4mod visitor;
5
6pub use view::*;
7pub use visitor::*;
8
9// -----------------------------------------------------------------------------
10// Standard re-exports for `traits::X` ergonomics
11// -----------------------------------------------------------------------------
12
13pub use canic_cdk::structures::storable::Storable;
14pub use num_traits::{FromPrimitive as NumFromPrimitive, NumCast, ToPrimitive as NumToPrimitive};
15pub use serde::{Deserialize, Serialize, de::DeserializeOwned};
16pub use std::{
17    cmp::{Eq, Ordering, PartialEq},
18    convert::From,
19    default::Default,
20    fmt::Debug,
21    hash::Hash,
22    ops::{Add, AddAssign, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign},
23};
24
25use crate::{prelude::*, types::Id, value::ValueEnum, visitor::VisitorContext};
26
27// ============================================================================
28// FOUNDATIONAL KINDS
29// ============================================================================
30//
31// These traits define *where* something lives in the system,
32// not what data it contains.
33//
34
35///
36/// Path
37/// Fully-qualified schema path.
38///
39
40pub trait Path {
41    const PATH: &'static str;
42}
43
44/// Marker for all schema/runtime nodes.
45pub trait Kind: Path + 'static {}
46impl<T> Kind for T where T: Path + 'static {}
47
48/// Marker for canister namespaces.
49pub trait CanisterKind: Kind {}
50
51/// Marker for data stores bound to a canister.
52pub trait DataStoreKind: Kind {
53    type Canister: CanisterKind;
54}
55
56/// Marker for index stores bound to a canister.
57pub trait IndexStoreKind: Kind {
58    type Canister: CanisterKind;
59}
60
61// ============================================================================
62// ENTITY IDENTITY & SCHEMA
63// ============================================================================
64//
65// These traits describe *what an entity is*, not how it is stored
66// or manipulated at runtime.
67//
68
69///
70/// EntityKey
71///
72/// Marker trait for raw entity key material used at storage boundaries.
73///
74
75pub trait EntityKey: Copy + Debug + Eq + Ord + FieldValue + 'static {}
76impl<T> EntityKey for T where T: Copy + Debug + Eq + Ord + FieldValue + 'static {}
77
78///
79/// EntityStorageKey
80///
81/// Raw storage-key facts about an entity.
82///
83
84pub trait EntityStorageKey {
85    type Key: EntityKey;
86}
87
88///
89/// EntityIdentity
90///
91/// Semantic identity facts about an entity.
92/// `IDENTITY_NAMESPACE` is the stable namespace used for one-way
93/// external identity projection and SHOULD remain unchanged across
94/// schema/type renames when external identity continuity is required.
95///
96
97pub trait EntityIdentity: EntityStorageKey {
98    const ENTITY_NAME: &'static str;
99    const PRIMARY_KEY: &'static str;
100    const IDENTITY_NAMESPACE: &'static str;
101}
102
103///
104/// EntitySchema
105///
106/// Declared schema facts for an entity.
107///
108
109pub trait EntitySchema: EntityIdentity {
110    const MODEL: &'static EntityModel;
111    const FIELDS: &'static [&'static str];
112    const INDEXES: &'static [&'static IndexModel];
113}
114
115// ============================================================================
116// ENTITY RUNTIME COMPOSITION
117// ============================================================================
118//
119// These traits bind schema-defined entities into runtime placement.
120//
121
122///
123/// EntityPlacement
124///
125/// Runtime placement of an entity
126///
127
128pub trait EntityPlacement {
129    type DataStore: DataStoreKind;
130    type Canister: CanisterKind;
131}
132
133///
134/// EntityKind
135///
136/// Fully runtime-bound entity.
137///
138/// This is the *maximum* entity contract and should only be
139/// required by code that actually touches storage or execution.
140///
141
142pub trait EntityKind: EntitySchema + EntityPlacement + Kind + TypeKind {}
143
144// ============================================================================
145// ENTITY VALUES
146// ============================================================================
147//
148// These traits describe *instances* of entities.
149//
150
151///
152/// EntityValue
153///
154/// A concrete entity value.
155///
156/// This trait is intentionally lighter than `EntityKind`.
157/// It does NOT imply storage placement.
158///
159
160pub trait EntityValue: EntityIdentity + FieldValues + Sized {
161    fn id(&self) -> Id<Self>;
162}
163
164/// Marker for entities with exactly one logical row.
165pub trait SingletonEntity: EntityValue {}
166
167///
168// ============================================================================
169// TYPE SYSTEM CONTRACTS
170// ============================================================================
171//
172// These traits define behavioral expectations for schema-defined types.
173//
174
175///
176/// TypeKind
177///
178/// Any schema-defined data type.
179///
180/// This is a *strong* contract and should only be required
181/// where full lifecycle semantics are needed.
182///
183
184pub trait TypeKind:
185    Kind
186    + AsView
187    + Clone
188    + Default
189    + Serialize
190    + DeserializeOwned
191    + Sanitize
192    + Validate
193    + Visitable
194    + PartialEq
195{
196}
197
198impl<T> TypeKind for T where
199    T: Kind
200        + AsView
201        + Clone
202        + Default
203        + DeserializeOwned
204        + PartialEq
205        + Serialize
206        + Sanitize
207        + Validate
208        + Visitable
209{
210}
211
212/// ============================================================================
213/// QUERY VALUE BOUNDARIES
214/// ============================================================================
215
216///
217/// Collection
218///
219/// Explicit iteration contract for list/set wrapper types.
220/// Avoids implicit deref-based access to inner collections.
221///
222
223pub trait Collection {
224    type Item;
225
226    /// Iterator over the collection's items, tied to the borrow of `self`.
227    type Iter<'a>: Iterator<Item = &'a Self::Item> + 'a
228    where
229        Self: 'a;
230
231    /// Returns an iterator over the collection's items.
232    fn iter(&self) -> Self::Iter<'_>;
233
234    /// Returns the number of items in the collection.
235    fn len(&self) -> usize;
236
237    /// Returns true if the collection contains no items.
238    fn is_empty(&self) -> bool {
239        self.len() == 0
240    }
241}
242
243///
244/// MapCollection
245///
246/// Explicit iteration contract for map wrapper types.
247/// Avoids implicit deref-based access to inner collections.
248///
249
250pub trait MapCollection {
251    type Key;
252    type Value;
253
254    /// Iterator over the map's key/value pairs, tied to the borrow of `self`.
255    type Iter<'a>: Iterator<Item = (&'a Self::Key, &'a Self::Value)> + 'a
256    where
257        Self: 'a;
258
259    /// Returns an iterator over the map's key/value pairs.
260    fn iter(&self) -> Self::Iter<'_>;
261
262    /// Returns the number of entries in the map.
263    fn len(&self) -> usize;
264
265    /// Returns true if the map contains no entries.
266    fn is_empty(&self) -> bool {
267        self.len() == 0
268    }
269}
270
271pub trait EnumValue {
272    fn to_value_enum(&self) -> ValueEnum;
273}
274
275pub trait FieldValues {
276    fn get_value(&self, field: &str) -> Option<Value>;
277}
278
279///
280/// FieldValueKind
281///
282/// Schema affordance classification for query planning and validation.
283/// Describes whether a field is planner-addressable and predicate-queryable.
284///
285#[derive(Clone, Copy, Debug, Eq, PartialEq)]
286pub enum FieldValueKind {
287    /// Planner-addressable atomic value.
288    Atomic,
289
290    /// Structured value with known internal fields that the planner
291    /// does not reason about as an addressable query target.
292    Structured {
293        /// Whether predicates may be expressed against this field.
294        queryable: bool,
295    },
296}
297
298impl FieldValueKind {
299    #[must_use]
300    pub const fn is_queryable(self) -> bool {
301        match self {
302            Self::Atomic => true,
303            Self::Structured { queryable } => queryable,
304        }
305    }
306}
307
308///
309/// FieldValue
310///
311/// Conversion boundary for values used in query predicates.
312///
313/// Represents values that can appear on the *right-hand side* of predicates.
314///
315
316pub trait FieldValue {
317    fn kind() -> FieldValueKind
318    where
319        Self: Sized;
320
321    fn to_value(&self) -> Value;
322
323    #[must_use]
324    fn from_value(value: &Value) -> Option<Self>
325    where
326        Self: Sized;
327}
328
329impl FieldValue for &str {
330    fn kind() -> FieldValueKind {
331        FieldValueKind::Atomic
332    }
333
334    fn to_value(&self) -> Value {
335        Value::Text((*self).to_string())
336    }
337
338    fn from_value(_value: &Value) -> Option<Self> {
339        None
340    }
341}
342
343impl FieldValue for String {
344    fn kind() -> FieldValueKind {
345        FieldValueKind::Atomic
346    }
347
348    fn to_value(&self) -> Value {
349        Value::Text(self.clone())
350    }
351
352    fn from_value(value: &Value) -> Option<Self> {
353        match value {
354            Value::Text(v) => Some(v.clone()),
355            _ => None,
356        }
357    }
358}
359
360impl<T: FieldValue> FieldValue for Option<T> {
361    fn kind() -> FieldValueKind {
362        T::kind()
363    }
364
365    fn to_value(&self) -> Value {
366        match self {
367            Some(v) => v.to_value(),
368            None => Value::Null,
369        }
370    }
371
372    fn from_value(value: &Value) -> Option<Self> {
373        if matches!(value, Value::Null) {
374            return Some(None);
375        }
376
377        T::from_value(value).map(Some)
378    }
379}
380
381impl<T: FieldValue> FieldValue for Box<T> {
382    fn kind() -> FieldValueKind {
383        T::kind()
384    }
385
386    fn to_value(&self) -> Value {
387        (**self).to_value()
388    }
389
390    fn from_value(value: &Value) -> Option<Self> {
391        T::from_value(value).map(Self::new)
392    }
393}
394
395impl<T: FieldValue> FieldValue for Vec<Box<T>> {
396    fn kind() -> FieldValueKind {
397        FieldValueKind::Structured { queryable: true }
398    }
399
400    fn to_value(&self) -> Value {
401        Value::List(self.iter().map(FieldValue::to_value).collect())
402    }
403
404    fn from_value(value: &Value) -> Option<Self> {
405        let Value::List(items) = value else {
406            return None;
407        };
408
409        let mut out = Self::with_capacity(items.len());
410        for item in items {
411            out.push(Box::new(T::from_value(item)?));
412        }
413
414        Some(out)
415    }
416}
417
418// impl_field_value
419#[macro_export]
420macro_rules! impl_field_value {
421    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
422        $(
423            impl FieldValue for $type {
424                fn kind() -> FieldValueKind {
425                    FieldValueKind::Atomic
426                }
427
428                fn to_value(&self) -> Value {
429                    Value::$variant((*self).into())
430                }
431
432                fn from_value(value: &Value) -> Option<Self> {
433                    match value {
434                        Value::$variant(v) => (*v).try_into().ok(),
435                        _ => None,
436                    }
437                }
438            }
439        )*
440    };
441}
442
443impl_field_value!(
444    i8 => Int,
445    i16 => Int,
446    i32 => Int,
447    i64 => Int,
448    u8 => Uint,
449    u16 => Uint,
450    u32 => Uint,
451    u64 => Uint,
452    bool => Bool,
453);
454
455/// ============================================================================
456/// MISC HELPERS
457/// ============================================================================
458
459///
460/// Inner
461///
462/// For newtypes to expose their innermost value.
463///
464pub trait Inner<T> {
465    fn inner(&self) -> &T;
466    fn into_inner(self) -> T;
467}
468
469// impl_inner
470#[macro_export]
471macro_rules! impl_inner {
472    ($($type:ty),*) => {
473        $(
474            impl Inner<$type> for $type {
475                fn inner(&self) -> &$type {
476                    &self
477                }
478                fn into_inner(self) -> $type {
479                    self
480                }
481            }
482        )*
483    };
484}
485
486impl_inner!(
487    bool, f32, f64, i8, i16, i32, i64, i128, String, u8, u16, u32, u64, u128
488);
489
490/// ============================================================================
491/// SANITIZATION / VALIDATION
492/// ============================================================================
493
494///
495/// Sanitizer
496///
497/// Transforms a value into a sanitized version.
498///
499pub trait Sanitizer<T> {
500    fn sanitize(&self, value: &mut T) -> Result<(), String>;
501}
502
503///
504/// Validator
505///
506/// Allows a node to validate values.
507///
508pub trait Validator<T: ?Sized> {
509    fn validate(&self, value: &T, ctx: &mut dyn VisitorContext);
510}