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