Skip to main content

icydb_model/
lib.rs

1//! IcyDB application-model authoring and code generation.
2//!
3//! This package owns application declarations, the host-only authoring graph,
4//! explicit application validation and normalization, and lowering into the
5//! public [`icydb_schema`] proposal contract. It is not database authority.
6
7extern crate self as icydb_model;
8
9pub mod application;
10pub mod base;
11pub mod build;
12pub mod error;
13pub mod fragment;
14// Declarations remain available on Wasm, while their whole-graph validation
15// helpers are deliberately host-only work. Usage varies by feature profile,
16// so these Wasm allowances must not require dead code to be present.
17#[cfg_attr(
18    target_arch = "wasm32",
19    allow(
20        dead_code,
21        reason = "Wasm retains declaration nodes while host code owns whole-graph traversal"
22    )
23)]
24pub mod node;
25pub mod normalize;
26#[cfg_attr(
27    target_arch = "wasm32",
28    expect(dead_code, reason = "whole-graph schema validation is host-owned")
29)]
30mod schema_validate;
31mod typed_adapter;
32pub mod types;
33pub mod validate;
34#[cfg_attr(
35    target_arch = "wasm32",
36    allow(
37        dead_code,
38        reason = "Wasm retains declarations while host code owns graph visitation"
39    )
40)]
41mod visit;
42pub mod visitor;
43
44use crate::{build::BuildError, node::NodeError};
45use thiserror::Error as ThisError;
46
47/// Shared schema-building prelude used by validators, macros, and tests.
48pub mod prelude {
49    pub(crate) use crate::build::schema_read;
50    pub use crate::{
51        EntitySource as _, Inner as _, NormalizeAndValidate as _, Path as _, base, canister,
52        entity, enum_, err,
53        error::ErrorTree,
54        list, map, newtype,
55        node::*,
56        normalizer, record,
57        schema::*,
58        set, store, tuple,
59        types::{Cardinality, Primitive},
60        validator,
61        visitor::{
62            Issue, Normalize as _, NormalizeAuto, NormalizeCustom, Normalizer as _, Validate as _,
63            ValidateAuto, ValidateCustom, Validator as _, Visitable as _, VisitorContext,
64        },
65    };
66    pub(crate) use crate::{
67        node::{MacroNode, ValidateNode, VisitableNode},
68        visit::Visitor,
69    };
70
71    pub use serde::{Deserialize, Serialize};
72}
73
74pub use application::NormalizeAndValidate;
75pub use icydb_model_macros::{
76    Add, AddAssign, Deref, DerefMut, Display, Div, DivAssign, Inner, Mul, MulAssign, Neg, Product,
77    Rem, RemAssign, Sub, SubAssign, Sum, canister, entity, enum_, list, map, newtype, normalizer,
78    record, set, store, tuple, validator,
79};
80pub use icydb_schema::MAX_INDEX_FIELDS;
81pub use normalize::normalize;
82#[doc(hidden)]
83pub use typed_adapter::{
84    TypedAdapterContext, TypedEnumDescriptor, TypedEnumSelection, TypedInputValue, TypedNamedType,
85    TypedOutputValue, TypedScalarValue, TypedValueError,
86};
87pub use validate::validate;
88
89/// Fully-qualified path identity for generated application declarations.
90pub trait Path {
91    /// Stable Rust declaration path.
92    const PATH: &'static str;
93}
94
95/// Schema-authored entity source identity emitted for runtime-enabled models.
96///
97/// This is application vocabulary only. It cannot establish accepted-schema
98/// presence, freshness, or authority; runtime APIs resolve the source against
99/// their pinned accepted snapshot.
100pub trait EntitySource {
101    /// Exact authored entity source name.
102    const ENTITY: &'static str;
103}
104
105/// Borrowed and consuming access to one-field application wrappers.
106pub trait Inner<T> {
107    /// Borrow the wrapped value.
108    fn inner(&self) -> &T;
109
110    /// Consume the wrapper and return its value.
111    fn into_inner(self) -> T;
112}
113
114/// Exact public proposal vocabulary consumed by application-model lowering.
115pub mod schema {
116    pub use icydb_schema::*;
117}
118
119/// Dependencies intentionally exposed to generated model code.
120#[doc(hidden)]
121pub mod __reexports {
122
123    #[cfg(not(target_arch = "wasm32"))]
124    pub use ctor;
125    pub use icydb_model_macros;
126    pub use remain;
127    pub use serde;
128}
129
130///
131/// Error
132///
133/// Top-level schema error boundary spanning build-time validation and node
134/// lookup/type errors.
135///
136#[derive(Debug, ThisError)]
137pub enum Error {
138    #[error(transparent)]
139    BuildError(#[from] BuildError),
140
141    #[error(transparent)]
142    NodeError(#[from] NodeError),
143}
144
145//
146// TESTS
147//
148
149#[cfg(test)]
150mod tests {
151    use super::{Error, build::BuildError, error::ErrorTree, node::NodeError};
152
153    #[test]
154    fn build_errors_remain_in_build_boundary() {
155        let schema_error = Error::from(BuildError::Validation(ErrorTree::from(
156            "missing schema relation target",
157        )));
158
159        match schema_error {
160            Error::BuildError(BuildError::Validation(tree)) => {
161                assert!(
162                    tree.messages()
163                        .iter()
164                        .any(|message| message == "missing schema relation target"),
165                    "build validation errors must remain wrapped as build-boundary failures",
166                );
167            }
168            Error::BuildError(BuildError::Graph(error)) => {
169                panic!("unexpected graph error: {error}");
170            }
171            Error::NodeError(_) => {
172                panic!("build validation failures must not be remapped into node-boundary errors");
173            }
174        }
175    }
176
177    #[test]
178    fn node_errors_remain_in_node_boundary() {
179        let schema_error = Error::from(NodeError::PathNotFound("entity.user_id".to_string()));
180
181        match schema_error {
182            Error::NodeError(NodeError::PathNotFound(path)) => {
183                assert_eq!(path, "entity.user_id");
184            }
185            Error::NodeError(NodeError::IncorrectNodeType(path)) => {
186                panic!("unexpected node error kind after conversion for path {path}");
187            }
188            Error::BuildError(_) => {
189                panic!("node errors must not be remapped into build-boundary failures");
190            }
191        }
192    }
193}