Skip to main content

sofie_macros/
lib.rs

1//! Derives emit trait impls and descriptions only; behaviour lives in the libraries.
2
3use proc_macro::TokenStream;
4use syn::{DeriveInput, parse_macro_input};
5
6mod aggregate;
7mod case;
8#[cfg(feature = "children")]
9mod children;
10#[cfg(feature = "config")]
11mod config;
12mod constraints;
13#[cfg(feature = "contracts")]
14mod contracts;
15mod crate_path;
16mod enumeration;
17mod event;
18mod every;
19#[cfg(feature = "factories")]
20mod factory;
21#[cfg(feature = "fixtures")]
22mod fixture;
23mod input;
24#[cfg(feature = "joins")]
25mod joins;
26#[cfg(feature = "retention")]
27mod pruned;
28mod read_model;
29#[cfg(feature = "remote-contracts")]
30mod remote;
31mod sensitive;
32mod serialize;
33mod shape;
34#[cfg(feature = "span")]
35mod span;
36#[cfg(feature = "tasks")]
37mod task;
38mod value;
39
40/// `#[owner]` marks the `Id<…>` field naming the owner; on the struct, a record that is its
41/// own owner, or with `(through = parent)` one owned by its parent's owner.
42/// `#[aggregate(behaviors(Slugged(from = name)))]` adds behaviors to `Versioned` and
43/// `Timestamps`. Field constraints are those of `#[derive(Input)]`. A `#[sensitive]` field shows
44/// as `***` in `Debug` and logs; it and one of a sensitive type stay out of JSON and search.
45#[proc_macro_derive(
46    Aggregate,
47    attributes(
48        sofie, owner, aggregate, min, max, length, range, required, unique, expose, mirror,
49        sensitive
50    )
51)]
52pub fn derive_aggregate(item: TokenStream) -> TokenStream {
53    expand(item, aggregate::derive)
54}
55
56/// Needs an `id: Id<Self>` field; stored in its own table beside the aggregate's.
57#[cfg(feature = "children")]
58#[proc_macro_derive(Child, attributes(sofie, min, max, length, range, required))]
59pub fn derive_child(item: TokenStream) -> TokenStream {
60    expand(item, children::derive)
61}
62
63/// `#[sensitive]` masks it in `Debug`. As a handler's input it is read from the body field named
64/// after the type: `cancel(order: Order, cancel_reason: CancelReason)` takes
65/// `{ "cancel_reason": … }`, and `Option<CancelReason>` takes it or nothing.
66#[proc_macro_derive(Value, attributes(sofie, sensitive))]
67pub fn derive_value(item: TokenStream) -> TokenStream {
68    expand(item, value::derive)
69}
70
71/// Read from its module's `<MODULE>_<FIELD>` keys; a field of another struct deriving it reads
72/// `<MODULE>_<FIELD>_*`.
73#[cfg(feature = "config")]
74#[proc_macro_derive(Config, attributes(sofie))]
75pub fn derive_config(item: TokenStream) -> TokenStream {
76    expand(item, config::derive)
77}
78
79/// `#[to(…)]` lists a variant's transitions.
80#[proc_macro_derive(Enum, attributes(sofie, to, tone))]
81pub fn derive_enum(item: TokenStream) -> TokenStream {
82    expand(item, enumeration::derive)
83}
84
85/// `#[read(from = Aggregate)]`; `#[read(via = Base::COLUMN, field = Other::COLUMN)]` reads a
86/// column of the record the base's id column names. A field reading a sensitive one needs
87/// `#[sensitive]`.
88#[proc_macro_derive(ReadModel, attributes(sofie, read, sensitive))]
89pub fn derive_read_model(item: TokenStream) -> TokenStream {
90    expand(item, read_model::derive)
91}
92
93#[proc_macro_derive(Event, attributes(sofie))]
94pub fn derive_event(item: TokenStream) -> TokenStream {
95    expand(item, event::derive)
96}
97
98/// `#[min(n)]`/`#[max(n)]` bound text characters or a number's value; `#[length(min, max)]`,
99/// `#[range(min, max)]`, `#[required]` (not blank) and `#[unique]` (held by the database only).
100/// `#[input(from = Order)]` inherits `Order`'s field constraints, `#[unconstrained]` drops
101/// them; `#[input(validate)]` runs `Validate` after them.
102#[proc_macro_derive(
103    Input,
104    attributes(sofie, input, min, max, length, range, required, unique, unconstrained)
105)]
106pub fn derive_input(item: TokenStream) -> TokenStream {
107    expand(item, input::derive)
108}
109
110/// Takes no arguments: naming the event again could only disagree with the argument's type.
111#[proc_macro_attribute]
112pub fn on(arguments: TokenStream, item: TokenStream) -> TokenStream {
113    if arguments.is_empty() {
114        return item;
115    }
116    let error = syn::Error::new_spanned(
117        proc_macro2::TokenStream::from(arguments),
118        "write a bare `#[on]`: the listener's first argument's type is the event",
119    );
120    [error.into_compile_error().into(), item]
121        .into_iter()
122        .collect()
123}
124
125/// Its arguments are never recorded.
126#[cfg(feature = "span")]
127#[proc_macro_attribute]
128pub fn span(arguments: TokenStream, item: TokenStream) -> TokenStream {
129    span::attribute(&arguments.into(), item.into()).into()
130}
131
132/// Read by `sofie gen`, which registers the job with the worker.
133#[proc_macro_attribute]
134pub fn job(_arguments: TokenStream, item: TokenStream) -> TokenStream {
135    item
136}
137
138/// Read by `sofie gen`, which registers the task as `<module>.<function>`.
139#[cfg(feature = "tasks")]
140#[proc_macro_attribute]
141pub fn task(arguments: TokenStream, item: TokenStream) -> TokenStream {
142    task::attribute(&arguments.into(), item.into()).into()
143}
144
145/// Each `async fn` returns a boxed future so the trait works as `dyn`.
146#[cfg(feature = "contracts")]
147#[proc_macro_attribute]
148pub fn contract(_arguments: TokenStream, item: TokenStream) -> TokenStream {
149    contracts::attribute(item.into(), contracts::Boundary::Contract).into()
150}
151
152/// Calls run in `<vendor>.<method>` spans; see `sofie::client`.
153#[cfg(feature = "gateways")]
154#[proc_macro_attribute]
155pub fn gateway(_arguments: TokenStream, item: TokenStream) -> TokenStream {
156    contracts::attribute(item.into(), contracts::Boundary::Gateway).into()
157}
158
159/// Interval as `30s`, `15m`, `1h`, `1d`; `each = query` runs it per listed record, one job each.
160/// Or a time on a zone's clock: `daily_at = "03:00", zone = "Europe/Istanbul"`, and with
161/// `weekly_on = "monday"` on that day only.
162///
163/// ```
164/// # async fn late() {}
165/// #[sofie_macros::every("1h", each = late)]
166/// pub async fn remind() {}
167/// ```
168///
169/// ```compile_fail
170/// #[sofie_macros::every("1h", each = no_such_query)]
171/// pub async fn remind() {}
172/// ```
173#[proc_macro_attribute]
174pub fn every(arguments: TokenStream, item: TokenStream) -> TokenStream {
175    every::attribute(arguments.into(), item.into()).into()
176}
177
178/// Written `#[sofie::feature(FLAG)]`: a bare `feature` is Rust's own crate attribute.
179#[proc_macro_attribute]
180pub fn feature(_arguments: TokenStream, item: TokenStream) -> TokenStream {
181    item
182}
183
184/// One database per test; the crate's wiring must export `MODULES`, `app` and `worker`.
185#[cfg(feature = "fixtures")]
186#[proc_macro_attribute]
187pub fn test_app(arguments: TokenStream, item: TokenStream) -> TokenStream {
188    let function = parse_macro_input!(item as syn::ItemFn);
189    fixture::test_app(arguments.into(), &function)
190        .unwrap_or_else(syn::Error::into_compile_error)
191        .into()
192}
193
194fn expand(
195    item: TokenStream,
196    derive: fn(&DeriveInput) -> syn::Result<proc_macro2::TokenStream>,
197) -> TokenStream {
198    let input = parse_macro_input!(item as DeriveInput);
199    derive(&input)
200        .and_then(|expanded| crate_path::with_crate_path(&input, expanded))
201        .unwrap_or_else(syn::Error::into_compile_error)
202        .into()
203}