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///
39/// Async functions bind their request root when first polled, not when the
40/// future is constructed. Nested calls reuse the active root. Keep a started
41/// future within that request; do not share it across independent request roots
42/// through a singleflight or shared-future cache. Share completed application
43/// data instead, leaving each request responsible for its own database work.
44///
45/// Async functions must return `Result<T, E>` with `E: From<icydb::Error>`.
46/// A started future polled beneath a different active request root returns the
47/// existing `RequestExecutionRootMismatch` error before polling its child.
48/// Child success/application errors are preserved, and synchronous return types
49/// are unchanged. See `icydb::db::with_request_execution_async`.
50#[proc_macro_attribute]
51pub fn request_execution(
52    args: proc_macro::TokenStream,
53    input: proc_macro::TokenStream,
54) -> proc_macro::TokenStream {
55    request_execution::expand_request_execution(args.into(), input.into())
56        .unwrap_or_else(syn::Error::into_compile_error)
57        .into()
58}
59
60/// Declare a synchronous unit test with the production request boundary.
61///
62/// The entire test body shares one aggregate request budget. Nested
63/// `icydb::db::with_request_execution` calls reuse that budget; they do not
64/// start separate simulated messages or reset counters.
65///
66/// For a test that simulates multiple IC messages, use plain `#[test]` and
67/// wrap each message separately in `icydb::db::with_request_execution`.
68#[proc_macro_attribute]
69pub fn test(
70    args: proc_macro::TokenStream,
71    input: proc_macro::TokenStream,
72) -> proc_macro::TokenStream {
73    request_execution::expand_test(args.into(), input.into())
74        .unwrap_or_else(syn::Error::into_compile_error)
75        .into()
76}
77
78/// Derive `Deref` for a one-field application wrapper.
79#[proc_macro_derive(Deref)]
80pub fn derive_deref(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
81    helper_deref::derive_deref(input.into()).into()
82}
83
84/// Derive `DerefMut` for a one-field application wrapper.
85#[proc_macro_derive(DerefMut)]
86pub fn derive_deref_mut(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
87    helper_deref::derive_deref_mut(input.into()).into()
88}
89
90///
91/// Prelude
92///
93/// Internal proc-macro prelude shared by node parsing and generator code.
94/// Keeps proc-macro essentials and schema helpers in one crate-local bundle.
95/// This is not part of the external derive surface.
96///
97
98mod prelude {
99    pub use crate::authoring_types::{Cardinality, Primitive};
100    pub use crate::{
101        crate_path::CratePaths,
102        r#gen::{Imp, Implementor},
103        helper::{quote_one, quote_option, quote_slice, to_path, to_str_lit},
104        node::*,
105        trait_kind::{
106            ApplicationTypeKind, TraitBuilder, TraitKind, TraitSet, application_type_trait_set,
107            generated_node_trait_set,
108        },
109        types::TraitStrategy,
110    };
111
112    pub const MAX_FIELD_NAME_LEN: usize = 64;
113
114    // proc-macro essentials
115    pub use darling::{Error as DarlingError, FromMeta};
116    pub use proc_macro2::{Span, TokenStream};
117    pub use quote::{ToTokens, format_ident, quote};
118    pub use serde::Deserialize;
119    pub use syn::{Ident, ItemStruct, LitStr, Path};
120}
121
122///
123/// Node Macros
124///
125
126macro_rules! macro_node {
127    ($fn_name:ident, $node_type:ty) => {
128        macro_node!($fn_name, $node_type, false, |_node: &mut $node_type| {});
129    };
130    ($fn_name:ident, $node_type:ty, $runtime:expr, $configure:expr) => {
131        /// Expand an application declaration for its selected authoring surface.
132        #[proc_macro_attribute]
133        pub fn $fn_name(
134            args: proc_macro::TokenStream,
135            input: proc_macro::TokenStream,
136        ) -> proc_macro::TokenStream {
137            match NestedMeta::parse_meta_list(args.into()) {
138                Ok(mut args) => {
139                    let crate_paths = match CratePathOverrides::extract(&mut args) {
140                        Ok(crate_paths) => crate_paths,
141                        Err(err) => return err.write_errors().into(),
142                    };
143                    let item = parse_macro_input!(input as ItemStruct);
144
145                    // validate
146                    if !matches!(item.vis, Visibility::Public(_)) {
147                        return proc_macro::TokenStream::from(
148                            DarlingError::custom("expected public visibility").write_errors(),
149                        );
150                    }
151
152                    // Hook macros preserve the authored struct, including its fields and generics.
153                    if !matches!(stringify!($fn_name), "validator" | "normalizer")
154                        && let Err(error) = Def::validate_declaration(&item)
155                    {
156                        return error.write_errors().into();
157                    }
158
159                    // build def
160                    let debug = item.attrs.iter().any(|attr| attr.path().is_ident("debug"));
161                    let mut node = match <$node_type>::from_list(&args) {
162                        Ok(node) => node,
163                        Err(err) => return proc_macro::TokenStream::from(err.write_errors()),
164                    };
165                    let paths = match crate_paths.resolve($runtime) {
166                        Ok(paths) => paths,
167                        Err(err) => return err.write_errors().into(),
168                    };
169                    node.def = Def::new(item, paths);
170                    ($configure)(&mut node);
171                    if let Err(err) = node.validate() {
172                        return proc_macro::TokenStream::from(err.write_errors());
173                    }
174
175                    // fatal schema errors
176                    let fatal_errors = node.fatal_errors();
177                    if !fatal_errors.is_empty() {
178                        let tokens: proc_macro2::TokenStream = fatal_errors
179                            .into_iter()
180                            .map(|err| err.to_compile_error())
181                            .collect();
182
183                        return tokens.into();
184                    }
185
186                    // Emit only framework-owned references through the resolved paths.
187                    let q = node.generate_tokens(node.def().paths());
188
189                    if debug {
190                        quote! { compile_error!(stringify! { #q }); }
191                    } else {
192                        q
193                    }
194                    .into()
195                }
196                Err(e) => proc_macro::TokenStream::from(DarlingError::from(e).write_errors()),
197            }
198        }
199    };
200}
201
202macro_node!(canister, node::Canister);
203macro_node!(entity, node::Entity);
204macro_node!(
205    runtime_entity,
206    node::Entity,
207    true,
208    |entity: &mut node::Entity| entity.emit_runtime_adapters = true
209);
210macro_node!(enum_, node::Enum);
211macro_node!(list, node::List);
212macro_node!(map, node::Map);
213macro_node!(newtype, node::Newtype);
214macro_node!(record, node::Record);
215macro_node!(
216    runtime_record,
217    node::Record,
218    true,
219    |record: &mut node::Record| record.emit_runtime_references = true
220);
221macro_node!(normalizer, node::Normalizer);
222macro_node!(set, node::Set);
223macro_node!(store, node::Store);
224macro_node!(tuple, node::Tuple);
225macro_node!(validator, node::Validator);
226
227/// Derive addition for one-field application wrappers.
228#[proc_macro_derive(Add)]
229pub fn derive_add(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
230    helper_ops::derive_add(input.into()).into()
231}
232
233/// Derive additive assignment for one-field application wrappers.
234#[proc_macro_derive(AddAssign)]
235pub fn derive_add_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
236    helper_ops::derive_add_assign(input.into()).into()
237}
238
239/// Derive subtraction for one-field application wrappers.
240#[proc_macro_derive(Sub)]
241pub fn derive_sub(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
242    helper_ops::derive_sub(input.into()).into()
243}
244
245/// Derive subtractive assignment for one-field application wrappers.
246#[proc_macro_derive(SubAssign)]
247pub fn derive_sub_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
248    helper_ops::derive_sub_assign(input.into()).into()
249}
250
251/// Derive multiplication for one-field application wrappers.
252#[proc_macro_derive(Mul)]
253pub fn derive_mul(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
254    helper_ops::derive_mul(input.into()).into()
255}
256
257/// Derive multiplicative assignment for one-field application wrappers.
258#[proc_macro_derive(MulAssign)]
259pub fn derive_mul_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
260    helper_ops::derive_mul_assign(input.into()).into()
261}
262
263/// Derive division for one-field application wrappers.
264#[proc_macro_derive(Div)]
265pub fn derive_div(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
266    helper_ops::derive_div(input.into()).into()
267}
268
269/// Derive divisive assignment for one-field application wrappers.
270#[proc_macro_derive(DivAssign)]
271pub fn derive_div_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
272    helper_ops::derive_div_assign(input.into()).into()
273}
274
275/// Derive remainder for one-field application wrappers.
276#[proc_macro_derive(Rem)]
277pub fn derive_rem(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
278    helper_ops::derive_rem(input.into()).into()
279}
280
281/// Derive remainder assignment for one-field application wrappers.
282#[proc_macro_derive(RemAssign)]
283pub fn derive_rem_assign(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
284    helper_ops::derive_rem_assign(input.into()).into()
285}
286
287/// Derive negation for one-field application wrappers.
288#[proc_macro_derive(Neg)]
289pub fn derive_neg(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
290    helper_ops::derive_neg(input.into()).into()
291}
292
293/// Derive iterator multiplication for one-field application wrappers.
294#[proc_macro_derive(Product)]
295pub fn derive_product(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
296    helper_ops::derive_product(input.into()).into()
297}
298
299/// Derive iterator summation for one-field application wrappers.
300#[proc_macro_derive(Sum)]
301pub fn derive_sum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
302    helper_ops::derive_sum(input.into()).into()
303}
304
305/// Derive display forwarding for one-field application wrappers.
306#[proc_macro_derive(Display)]
307pub fn derive_display(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
308    helper_display::derive_display(input.into()).into()
309}
310
311/// Derive borrowed and consuming inner access for one-field wrappers.
312#[proc_macro_derive(Inner)]
313pub fn derive_inner(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
314    helper_inner::derive_inner(input.into()).into()
315}