Skip to main content

icydb_model_macros/
lib.rs

1//! Module: lib
2//! Responsibility: application-model declarations plus thin runtime entry attributes.
3//! Does not own: runtime schema semantics.
4//! Boundary: macro input to generated tokens.
5
6#![doc = include_str!("../README.md")]
7
8mod authoring_types;
9mod case;
10mod crate_path;
11mod r#gen;
12mod helper;
13mod helper_deref;
14mod helper_display;
15mod helper_inner;
16mod helper_newtype;
17mod helper_ops;
18mod imp;
19mod node;
20mod predicate;
21mod request_execution;
22mod trait_kind;
23mod types;
24mod validate;
25
26use crate::{
27    crate_path::CratePathOverrides,
28    node::{Def, HasDef, ValidateNode},
29};
30use darling::{Error as DarlingError, FromMeta, ast::NestedMeta};
31use quote::quote;
32use syn::{ItemStruct, Visibility, parse_macro_input};
33
34/// Install one aggregate IcyDB execution root for a sync or async entry point.
35///
36/// Place this outside the framework export attribute so IC-CDK, Canic,
37/// lifecycle, and timer handlers all use the same boundary implementation.
38#[proc_macro_attribute]
39pub fn request_execution(
40    args: proc_macro::TokenStream,
41    input: proc_macro::TokenStream,
42) -> proc_macro::TokenStream {
43    request_execution::expand_request_execution(args.into(), input.into())
44        .unwrap_or_else(syn::Error::into_compile_error)
45        .into()
46}
47
48/// Declare a synchronous unit test with the production request boundary.
49///
50/// The entire test body shares one aggregate request budget. Nested
51/// `icydb::db::with_request_execution` calls reuse that budget; they do not
52/// start separate simulated messages or reset counters.
53///
54/// For a test that simulates multiple IC messages, use plain `#[test]` and
55/// wrap each message separately in `icydb::db::with_request_execution`.
56#[proc_macro_attribute]
57pub fn test(
58    args: proc_macro::TokenStream,
59    input: proc_macro::TokenStream,
60) -> proc_macro::TokenStream {
61    request_execution::expand_test(args.into(), input.into())
62        .unwrap_or_else(syn::Error::into_compile_error)
63        .into()
64}
65
66/// Derive `Deref` for a one-field application wrapper.
67#[proc_macro_derive(Deref)]
68pub fn derive_deref(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
69    helper_deref::derive_deref(input.into()).into()
70}
71
72/// Derive `DerefMut` for a one-field application wrapper.
73#[proc_macro_derive(DerefMut)]
74pub fn derive_deref_mut(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
75    helper_deref::derive_deref_mut(input.into()).into()
76}
77
78///
79/// Prelude
80///
81/// Internal proc-macro prelude shared by node parsing and generator code.
82/// Keeps proc-macro essentials and schema helpers in one crate-local bundle.
83/// This is not part of the external derive surface.
84///
85
86mod prelude {
87    pub use crate::authoring_types::{Cardinality, Primitive};
88    pub use crate::{
89        crate_path::CratePaths,
90        r#gen::{Imp, Implementor},
91        helper::{quote_one, quote_option, quote_slice, to_path, to_str_lit},
92        node::*,
93        trait_kind::{
94            ApplicationTypeKind, TraitBuilder, TraitKind, TraitSet, application_type_trait_set,
95            generated_node_trait_set,
96        },
97        types::TraitStrategy,
98    };
99
100    pub const MAX_FIELD_NAME_LEN: usize = 64;
101
102    // proc-macro essentials
103    pub use darling::{Error as DarlingError, FromMeta};
104    pub use proc_macro2::{Span, TokenStream};
105    pub use quote::{ToTokens, format_ident, quote};
106    pub use serde::Deserialize;
107    pub use syn::{Ident, ItemStruct, LitStr, Path};
108}
109
110///
111/// Node Macros
112///
113
114macro_rules! macro_node {
115    ($fn_name:ident, $node_type:ty $(, $configure:expr)?) => {
116        #[proc_macro_attribute]
117        pub fn $fn_name(
118            args: proc_macro::TokenStream,
119            input: proc_macro::TokenStream,
120        ) -> proc_macro::TokenStream {
121            match NestedMeta::parse_meta_list(args.into()) {
122                Ok(mut args) => {
123                    let crate_paths = match CratePathOverrides::extract(&mut args) {
124                        Ok(crate_paths) => crate_paths,
125                        Err(err) => return err.write_errors().into(),
126                    };
127                    let item = parse_macro_input!(input as ItemStruct);
128
129                    // validate
130                    if !matches!(item.vis, Visibility::Public(_)) {
131                        return proc_macro::TokenStream::from(
132                            DarlingError::custom("expected public visibility").write_errors(),
133                        );
134                    }
135
136                    // Hook macros preserve the authored struct, including its fields and generics.
137                    if !matches!(stringify!($fn_name), "validator" | "normalizer")
138                        && let Err(error) = Def::validate_declaration(&item) {
139                        return error.write_errors().into();
140                    }
141
142                    // build def
143                    let debug = item.attrs.iter().any(|attr| attr.path().is_ident("debug"));
144                    let mut node = match <$node_type>::from_list(&args) {
145                        Ok(node) => node,
146                        Err(err) => return proc_macro::TokenStream::from(err.write_errors()),
147                    };
148                    let paths = match crate_paths.resolve() {
149                        Ok(paths) => paths,
150                        Err(err) => return err.write_errors().into(),
151                    };
152                    node.def = Def::new(item, paths);
153                    $(($configure)(&mut node, CratePathOverrides::has_icydb_runtime());)?
154                    if let Err(err) = node.validate() {
155                        return proc_macro::TokenStream::from(err.write_errors());
156                    }
157
158                    // fatal schema errors
159                    let fatal_errors = node.fatal_errors();
160                    if !fatal_errors.is_empty() {
161                        let tokens: proc_macro2::TokenStream = fatal_errors
162                            .into_iter()
163                            .map(|err| err.to_compile_error())
164                            .collect();
165
166                        return tokens.into();
167                    }
168
169                    // Emit only framework-owned references through the resolved paths.
170                    let q = node.generate_tokens(node.def().paths());
171
172                    if debug {
173                        quote! { compile_error!(stringify! { #q }); }
174                    } else {
175                        q
176                    }
177                    .into()
178                }
179                Err(e) => proc_macro::TokenStream::from(DarlingError::from(e).write_errors()),
180            }
181        }
182    };
183}
184
185macro_node!(canister, node::Canister);
186macro_node!(
187    entity,
188    node::Entity,
189    |entity: &mut node::Entity, has_runtime| entity.emit_runtime_adapters = has_runtime
190);
191macro_node!(enum_, node::Enum);
192macro_node!(list, node::List);
193macro_node!(map, node::Map);
194macro_node!(newtype, node::Newtype);
195macro_node!(
196    record,
197    node::Record,
198    |record: &mut node::Record, has_runtime| record.emit_runtime_references = has_runtime
199);
200macro_node!(normalizer, node::Normalizer);
201macro_node!(set, node::Set);
202macro_node!(store, node::Store);
203macro_node!(tuple, node::Tuple);
204macro_node!(validator, node::Validator);
205
206/// Derive addition for one-field application wrappers.
207#[proc_macro_derive(Add)]
208pub fn derive_add(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
209    helper_ops::derive_add(input.into()).into()
210}
211
212/// Derive additive assignment for one-field application wrappers.
213#[proc_macro_derive(AddAssign)]
214pub fn derive_add_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
215    helper_ops::derive_add_assign(input.into()).into()
216}
217
218/// Derive subtraction for one-field application wrappers.
219#[proc_macro_derive(Sub)]
220pub fn derive_sub(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
221    helper_ops::derive_sub(input.into()).into()
222}
223
224/// Derive subtractive assignment for one-field application wrappers.
225#[proc_macro_derive(SubAssign)]
226pub fn derive_sub_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
227    helper_ops::derive_sub_assign(input.into()).into()
228}
229
230/// Derive multiplication for one-field application wrappers.
231#[proc_macro_derive(Mul)]
232pub fn derive_mul(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
233    helper_ops::derive_mul(input.into()).into()
234}
235
236/// Derive multiplicative assignment for one-field application wrappers.
237#[proc_macro_derive(MulAssign)]
238pub fn derive_mul_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
239    helper_ops::derive_mul_assign(input.into()).into()
240}
241
242/// Derive division for one-field application wrappers.
243#[proc_macro_derive(Div)]
244pub fn derive_div(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
245    helper_ops::derive_div(input.into()).into()
246}
247
248/// Derive divisive assignment for one-field application wrappers.
249#[proc_macro_derive(DivAssign)]
250pub fn derive_div_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
251    helper_ops::derive_div_assign(input.into()).into()
252}
253
254/// Derive remainder for one-field application wrappers.
255#[proc_macro_derive(Rem)]
256pub fn derive_rem(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
257    helper_ops::derive_rem(input.into()).into()
258}
259
260/// Derive remainder assignment for one-field application wrappers.
261#[proc_macro_derive(RemAssign)]
262pub fn derive_rem_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
263    helper_ops::derive_rem_assign(input.into()).into()
264}
265
266/// Derive negation for one-field application wrappers.
267#[proc_macro_derive(Neg)]
268pub fn derive_neg(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
269    helper_ops::derive_neg(input.into()).into()
270}
271
272/// Derive iterator multiplication for one-field application wrappers.
273#[proc_macro_derive(Product)]
274pub fn derive_product(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
275    helper_ops::derive_product(input.into()).into()
276}
277
278/// Derive iterator summation for one-field application wrappers.
279#[proc_macro_derive(Sum)]
280pub fn derive_sum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
281    helper_ops::derive_sum(input.into()).into()
282}
283
284/// Derive display forwarding for one-field application wrappers.
285#[proc_macro_derive(Display)]
286pub fn derive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
287    helper_display::derive_display(input.into()).into()
288}
289
290/// Derive borrowed and consuming inner access for one-field wrappers.
291#[proc_macro_derive(Inner)]
292pub fn derive_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
293    helper_inner::derive_inner(input.into()).into()
294}