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