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, rewrite_generated_paths},
28    node::{Def, 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#[proc_macro_attribute]
50pub fn test(
51    args: proc_macro::TokenStream,
52    input: proc_macro::TokenStream,
53) -> proc_macro::TokenStream {
54    request_execution::expand_test(args.into(), input.into())
55        .unwrap_or_else(syn::Error::into_compile_error)
56        .into()
57}
58
59/// Derive `Deref` for a one-field application wrapper.
60#[proc_macro_derive(Deref)]
61pub fn derive_deref(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
62    helper_deref::derive_deref(input.into()).into()
63}
64
65/// Derive `DerefMut` for a one-field application wrapper.
66#[proc_macro_derive(DerefMut)]
67pub fn derive_deref_mut(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
68    helper_deref::derive_deref_mut(input.into()).into()
69}
70
71///
72/// Prelude
73///
74/// Internal proc-macro prelude shared by node parsing and generator code.
75/// Keeps proc-macro essentials and schema helpers in one crate-local bundle.
76/// This is not part of the external derive surface.
77///
78
79mod prelude {
80    pub use crate::authoring_types::{Cardinality, Primitive};
81    pub use crate::{
82        r#gen::{Imp, Implementor},
83        helper::{quote_one, quote_option, quote_slice, to_path, to_str_lit},
84        node::*,
85        trait_kind::{
86            ApplicationTypeKind, TraitBuilder, TraitKind, TraitSet, application_type_trait_set,
87            generated_node_trait_set,
88        },
89        types::TraitStrategy,
90    };
91
92    pub const MAX_FIELD_NAME_LEN: usize = 64;
93
94    // proc-macro essentials
95    pub use darling::{Error as DarlingError, FromMeta};
96    pub use proc_macro2::{Span, TokenStream};
97    pub use quote::{ToTokens, format_ident, quote};
98    pub use serde::Deserialize;
99    pub use syn::{Ident, ItemStruct, LitStr, Path};
100}
101
102///
103/// Node Macros
104///
105
106macro_rules! macro_node {
107    ($fn_name:ident, $node_type:ty, $gen_type:path $(, $configure:expr)?) => {
108        #[proc_macro_attribute]
109        pub fn $fn_name(
110            args: proc_macro::TokenStream,
111            input: proc_macro::TokenStream,
112        ) -> proc_macro::TokenStream {
113            match NestedMeta::parse_meta_list(args.into()) {
114                Ok(mut args) => {
115                    let crate_paths = match CratePathOverrides::extract(&mut args) {
116                        Ok(crate_paths) => crate_paths,
117                        Err(err) => return err.write_errors().into(),
118                    };
119                    let item = parse_macro_input!(input as ItemStruct);
120
121                    // validate
122                    if !matches!(item.vis, Visibility::Public(_)) {
123                        return proc_macro::TokenStream::from(
124                            DarlingError::custom("expected public visibility").write_errors(),
125                        );
126                    }
127
128                    // build def
129                    let debug = item.attrs.iter().any(|attr| attr.path().is_ident("debug"));
130                    let mut node = match <$node_type>::from_list(&args) {
131                        Ok(node) => node,
132                        Err(err) => return proc_macro::TokenStream::from(err.write_errors()),
133                    };
134                    node.def = Def::new(item);
135                    $(($configure)(&mut node, CratePathOverrides::has_icydb_runtime());)?
136                    if let Err(err) = node.validate() {
137                        return proc_macro::TokenStream::from(err.write_errors());
138                    }
139
140                    // fatal schema errors
141                    let fatal_errors = node.fatal_errors();
142                    if !fatal_errors.is_empty() {
143                        let tokens: proc_macro2::TokenStream = fatal_errors
144                            .into_iter()
145                            .map(|err| err.to_compile_error())
146                            .collect();
147
148                        return tokens.into();
149                    }
150
151                    // instantiate the generator
152                    let generator = $gen_type(&node);
153                    let q = match rewrite_generated_paths(quote!(#generator), &crate_paths) {
154                        Ok(tokens) => tokens,
155                        Err(err) => return err.write_errors().into(),
156                    };
157
158                    if debug {
159                        quote! { compile_error!(stringify! { #q }); }
160                    } else {
161                        q
162                    }
163                    .into()
164                }
165                Err(e) => proc_macro::TokenStream::from(DarlingError::from(e).write_errors()),
166            }
167        }
168    };
169}
170
171macro_node!(canister, node::Canister, r#gen::CanisterGen);
172macro_node!(
173    entity,
174    node::Entity,
175    r#gen::EntityGen,
176    |entity: &mut node::Entity, has_runtime| entity.emit_runtime_adapters = has_runtime
177);
178macro_node!(enum_, node::Enum, r#gen::EnumGen);
179macro_node!(list, node::List, r#gen::ListGen);
180macro_node!(map, node::Map, r#gen::MapGen);
181macro_node!(newtype, node::Newtype, r#gen::NewtypeGen);
182macro_node!(record, node::Record, r#gen::RecordGen);
183macro_node!(normalizer, node::Normalizer, r#gen::NormalizerGen);
184macro_node!(set, node::Set, r#gen::SetGen);
185macro_node!(store, node::Store, r#gen::StoreGen);
186macro_node!(tuple, node::Tuple, r#gen::TupleGen);
187macro_node!(validator, node::Validator, r#gen::ValidatorGen);
188
189/// Derive addition for one-field application wrappers.
190#[proc_macro_derive(Add)]
191pub fn derive_add(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
192    finish_helper_derive(helper_ops::derive_add(input.into()))
193}
194
195/// Derive additive assignment for one-field application wrappers.
196#[proc_macro_derive(AddAssign)]
197pub fn derive_add_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
198    finish_helper_derive(helper_ops::derive_add_assign(input.into()))
199}
200
201/// Derive subtraction for one-field application wrappers.
202#[proc_macro_derive(Sub)]
203pub fn derive_sub(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
204    finish_helper_derive(helper_ops::derive_sub(input.into()))
205}
206
207/// Derive subtractive assignment for one-field application wrappers.
208#[proc_macro_derive(SubAssign)]
209pub fn derive_sub_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
210    finish_helper_derive(helper_ops::derive_sub_assign(input.into()))
211}
212
213/// Derive multiplication for one-field application wrappers.
214#[proc_macro_derive(Mul)]
215pub fn derive_mul(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
216    finish_helper_derive(helper_ops::derive_mul(input.into()))
217}
218
219/// Derive multiplicative assignment for one-field application wrappers.
220#[proc_macro_derive(MulAssign)]
221pub fn derive_mul_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
222    finish_helper_derive(helper_ops::derive_mul_assign(input.into()))
223}
224
225/// Derive division for one-field application wrappers.
226#[proc_macro_derive(Div)]
227pub fn derive_div(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
228    finish_helper_derive(helper_ops::derive_div(input.into()))
229}
230
231/// Derive divisive assignment for one-field application wrappers.
232#[proc_macro_derive(DivAssign)]
233pub fn derive_div_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
234    finish_helper_derive(helper_ops::derive_div_assign(input.into()))
235}
236
237/// Derive remainder for one-field application wrappers.
238#[proc_macro_derive(Rem)]
239pub fn derive_rem(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
240    finish_helper_derive(helper_ops::derive_rem(input.into()))
241}
242
243/// Derive remainder assignment for one-field application wrappers.
244#[proc_macro_derive(RemAssign)]
245pub fn derive_rem_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
246    finish_helper_derive(helper_ops::derive_rem_assign(input.into()))
247}
248
249/// Derive negation for one-field application wrappers.
250#[proc_macro_derive(Neg)]
251pub fn derive_neg(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
252    finish_helper_derive(helper_ops::derive_neg(input.into()))
253}
254
255/// Derive iterator multiplication for one-field application wrappers.
256#[proc_macro_derive(Product)]
257pub fn derive_product(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
258    finish_helper_derive(helper_ops::derive_product(input.into()))
259}
260
261/// Derive iterator summation for one-field application wrappers.
262#[proc_macro_derive(Sum)]
263pub fn derive_sum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
264    finish_helper_derive(helper_ops::derive_sum(input.into()))
265}
266
267/// Derive display forwarding for one-field application wrappers.
268#[proc_macro_derive(Display)]
269pub fn derive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
270    finish_helper_derive(helper_display::derive_display(input.into()))
271}
272
273/// Derive borrowed and consuming inner access for one-field wrappers.
274#[proc_macro_derive(Inner)]
275pub fn derive_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
276    finish_helper_derive(helper_inner::derive_inner(input.into()))
277}
278
279fn finish_helper_derive(tokens: proc_macro2::TokenStream) -> proc_macro::TokenStream {
280    match rewrite_generated_paths(tokens, &CratePathOverrides::default()) {
281        Ok(tokens) => tokens.into(),
282        Err(err) => err.write_errors().into(),
283    }
284}