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// re-exports of other traits
10// for the standard traits::X pattern
11pub use canic_cdk::structures::storable::Storable;
12pub use num_traits::{FromPrimitive as NumFromPrimitive, NumCast, ToPrimitive as NumToPrimitive};
13pub use serde::{Deserialize, Serialize, de::DeserializeOwned};
14pub use std::{
15    cmp::{Eq, Ordering, PartialEq},
16    convert::{AsRef, From, Into},
17    default::Default,
18    fmt::{Debug, Display},
19    hash::Hash,
20    iter::IntoIterator,
21    ops::{Add, AddAssign, Deref, DerefMut, Mul, MulAssign, Sub, SubAssign},
22    str::FromStr,
23};
24
25use crate::{
26    error::{ErrorClass, ErrorOrigin, InternalError},
27    model::field::EntityFieldKind,
28    prelude::*,
29    types::Unit,
30    value::ValueEnum,
31    visitor::VisitorContext,
32};
33
34/// ------------------------
35/// KIND TRAITS
36/// the Schema uses the term "Node" but when they're built it's "Kind"
37/// ------------------------
38
39///
40/// Kind
41///
42
43pub trait Kind: Path + 'static {}
44
45impl<T> Kind for T where T: Path + 'static {}
46
47///
48/// CanisterKind
49///
50
51pub trait CanisterKind: Kind {}
52
53///
54/// DataStoreKind
55///
56
57pub trait DataStoreKind: Kind {
58    type Canister: CanisterKind;
59}
60
61///
62/// EntityKind
63///
64
65pub trait EntityKind: Kind + TypeKind + FieldValues {
66    type PrimaryKey: Copy + Into<Key>;
67    type DataStore: DataStoreKind;
68    type Canister: CanisterKind; // Self::Store::Canister shortcut
69
70    const ENTITY_NAME: &'static str;
71    const PRIMARY_KEY: &'static str;
72    const FIELDS: &'static [&'static str];
73    const INDEXES: &'static [&'static IndexModel];
74    const MODEL: &'static crate::model::entity::EntityModel;
75
76    fn key(&self) -> Key;
77    fn primary_key(&self) -> Self::PrimaryKey;
78    fn set_primary_key(&mut self, key: Self::PrimaryKey);
79}
80
81///
82/// IndexStoreKind
83///
84
85pub trait IndexStoreKind: Kind {
86    type Canister: CanisterKind;
87}
88
89///
90/// UnitKey
91/// Marker trait for unit-valued primary keys used by singleton entities.
92///
93
94pub trait UnitKey: Copy + Into<Key> + unit_key::Sealed {}
95
96impl UnitKey for () {}
97impl UnitKey for Unit {}
98
99mod unit_key {
100    use crate::types::Unit;
101
102    // Seal UnitKey so only unit-equivalent key types can implement it.
103    pub trait Sealed {}
104
105    impl Sealed for () {}
106    impl Sealed for Unit {}
107}
108
109/// ------------------------
110/// TYPE TRAITS
111/// ------------------------
112
113///
114/// TypeKind
115/// any data type
116///
117
118pub trait TypeKind:
119    Kind
120    + View
121    + Clone
122    + Default
123    + Serialize
124    + DeserializeOwned
125    + Sanitize
126    + Validate
127    + Visitable
128    + PartialEq
129{
130}
131
132impl<T> TypeKind for T where
133    T: Kind
134        + View
135        + Clone
136        + Default
137        + DeserializeOwned
138        + PartialEq
139        + Serialize
140        + Sanitize
141        + Validate
142        + Visitable
143{
144}
145
146/// ------------------------
147/// OTHER TRAITS
148/// ------------------------
149
150///
151/// FieldValues
152///
153
154pub trait FieldValues {
155    fn get_value(&self, field: &str) -> Option<Value>;
156}
157
158///
159/// EntityRef
160///
161/// Concrete reference extracted from an entity instance.
162/// Carries the target entity path and the referenced key value.
163/// Produced by [`EntityReferences`] during pre-commit planning.
164///
165
166#[derive(Clone, Copy, Debug, Eq, PartialEq)]
167pub struct EntityRef {
168    pub target_path: &'static str,
169    pub key: Key,
170}
171
172///
173/// EntityReferences
174///
175/// Extract typed entity references from a concrete entity instance.
176/// This is a pure helper for pre-commit planning and RI checks.
177/// Only direct `Ref<T>` and `Option<Ref<T>>` fields are strong in 0.6.
178/// Nested and collection references are treated as weak and ignored.
179/// This is a shallow walk over entity fields only; no recursive traversal occurs.
180///
181pub trait EntityReferences {
182    /// Return all concrete references currently present on this entity.
183    fn entity_refs(&self) -> Result<Vec<EntityRef>, InternalError>;
184}
185
186impl<E> EntityReferences for E
187where
188    E: EntityKind,
189{
190    fn entity_refs(&self) -> Result<Vec<EntityRef>, InternalError> {
191        let mut refs = Vec::with_capacity(E::MODEL.fields.len());
192
193        for field in E::MODEL.fields {
194            // Phase 1: identify strong reference fields; weak shapes are ignored.
195            let target_path = match &field.kind {
196                &EntityFieldKind::Ref { target_path, .. } => target_path,
197                &EntityFieldKind::List(inner) | &EntityFieldKind::Set(inner) => {
198                    if matches!(inner, &EntityFieldKind::Ref { .. }) {
199                        // Weak references: collection refs are allowed but not validated in 0.6.
200                        continue;
201                    }
202                    continue;
203                }
204                &EntityFieldKind::Map { key, value } => {
205                    if matches!(key, &EntityFieldKind::Ref { .. })
206                        || matches!(value, &EntityFieldKind::Ref { .. })
207                    {
208                        // Weak references: map refs are allowed but not validated in 0.6.
209                        continue;
210                    }
211                    continue;
212                }
213                _ => continue,
214            };
215
216            // Phase 2: fetch the field value and skip absent references.
217            let Some(value) = self.get_value(field.name) else {
218                return Err(InternalError::new(
219                    ErrorClass::InvariantViolation,
220                    ErrorOrigin::Executor,
221                    format!("reference field missing: {} field={}", E::PATH, field.name),
222                ));
223            };
224
225            if matches!(value, Value::None) {
226                continue;
227            }
228
229            if matches!(value, Value::Unsupported) {
230                return Err(InternalError::new(
231                    ErrorClass::InvariantViolation,
232                    ErrorOrigin::Executor,
233                    format!(
234                        "reference field value is unsupported: {} field={}",
235                        E::PATH,
236                        field.name
237                    ),
238                ));
239            }
240
241            // Phase 3: normalize into a concrete key and record the reference.
242            let Some(key) = value.as_key() else {
243                return Err(InternalError::new(
244                    ErrorClass::InvariantViolation,
245                    ErrorOrigin::Executor,
246                    format!(
247                        "reference field value is not a key: {} field={}",
248                        E::PATH,
249                        field.name
250                    ),
251                ));
252            };
253
254            refs.push(EntityRef { target_path, key });
255        }
256
257        Ok(refs)
258    }
259}
260
261///
262/// FieldValue
263///
264/// Conversion boundary for values used in query predicates.
265///
266/// `FieldValue` represents any value that can appear on the *right-hand side*
267/// of a predicate (e.g. `field == value`, `field IN values`). Implementations
268/// convert Rust values into owned [`Value`] instances that are stored inside
269/// query plans and executed later.
270///
271
272pub trait FieldValue {
273    fn to_value(&self) -> Value {
274        Value::Unsupported
275    }
276}
277
278///
279/// EnumValue
280/// Explicit conversion boundary for domain enums used in query values.
281///
282
283pub trait EnumValue {
284    /// Convert this enum into a strict [`ValueEnum`] with its canonical path.
285    fn to_value_enum(&self) -> ValueEnum;
286}
287
288impl FieldValue for &str {
289    fn to_value(&self) -> Value {
290        Value::Text((*self).to_string())
291    }
292}
293
294impl FieldValue for String {
295    fn to_value(&self) -> Value {
296        Value::Text(self.clone())
297    }
298}
299
300impl<T> FieldValue for &'_ T
301where
302    T: FieldValue + Copy,
303{
304    fn to_value(&self) -> Value {
305        (*self).to_value()
306    }
307}
308
309impl<T: FieldValue> FieldValue for Option<T> {
310    fn to_value(&self) -> Value {
311        match self {
312            Some(v) => v.to_value(),
313            None => Value::None,
314        }
315    }
316}
317
318impl<T: FieldValue> FieldValue for Vec<T> {
319    fn to_value(&self) -> Value {
320        Value::List(self.iter().map(FieldValue::to_value).collect())
321    }
322}
323
324impl<T: FieldValue> FieldValue for Box<T> {
325    fn to_value(&self) -> Value {
326        (**self).to_value()
327    }
328}
329
330// impl_field_value
331#[macro_export]
332macro_rules! impl_field_value {
333    ( $( $type:ty => $variant:ident ),* $(,)? ) => {
334        $(
335            impl FieldValue for $type {
336                fn to_value(&self) -> Value {
337                    Value::$variant((*self).into())
338                }
339            }
340        )*
341    };
342}
343
344impl_field_value!(
345    i8 => Int,
346    i16 => Int,
347    i32 => Int,
348    i64 => Int,
349    u8 => Uint,
350    u16 => Uint,
351    u32 => Uint,
352    u64 => Uint,
353    bool => Bool,
354);
355
356///
357/// Inner
358/// for Newtypes to get the innermost value
359///
360/// DO NOT REMOVE - its been added and removed twice already, NumCast
361/// is a pain to use and won't work for half our types
362///
363
364pub trait Inner<T> {
365    fn inner(&self) -> &T;
366    fn into_inner(self) -> T;
367}
368
369// impl_inner
370#[macro_export]
371macro_rules! impl_inner {
372    ($($type:ty),*) => {
373        $(
374            impl Inner<$type> for $type {
375                fn inner(&self) -> &$type {
376                    &self
377                }
378                fn into_inner(self) -> $type {
379                    self
380                }
381            }
382        )*
383    };
384}
385
386impl_inner!(
387    bool, f32, f64, i8, i16, i32, i64, i128, String, u8, u16, u32, u64, u128
388);
389
390///
391/// Path
392///
393/// any node created via a macro has a Path
394/// ie. design::game::rarity::Rarity
395///
396
397pub trait Path {
398    const PATH: &'static str;
399}
400
401///
402/// Sanitizer
403/// transforms a value into a sanitized version
404///
405
406pub trait Sanitizer<T> {
407    /// Apply in-place sanitization.
408    ///
409    /// - `Ok(())` means success (possibly with issues recorded by the caller)
410    /// - `Err(String)` means a fatal sanitization failure
411    fn sanitize(&self, value: &mut T) -> Result<(), String>;
412}
413
414///
415/// Validator
416/// allows a node to validate different types of primitives
417/// ?Sized so we can operate on str
418///
419
420pub trait Validator<T: ?Sized> {
421    fn validate(&self, value: &T, ctx: &mut dyn VisitorContext);
422}
423
424#[cfg(test)]
425mod tests;