Skip to main content

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