Skip to main content

architecturekit_macros/
lib.rs

1//! The derive macros of architecturekit.
2//!
3//! Do not depend on this crate directly. Use the derives through architecturekit,
4//! which re-exports them, and whose documentation describes them.
5#![deny(
6    ambiguous_negative_literals,
7    clippy::pedantic,
8    missing_debug_implementations,
9    missing_docs,
10    trivial_casts,
11    trivial_numeric_casts,
12    unused_extern_crates,
13    unused_import_braces,
14    unused_qualifications,
15    unused_results,
16    unsafe_code,
17    warnings
18)]
19
20mod attributes;
21mod events;
22mod rename;
23
24use proc_macro::TokenStream;
25use proc_macro2::TokenStream as TokenStream2;
26use quote::quote;
27use syn::{
28    Data, DeriveInput, Error, Fields, GenericParam, LitStr, Result, ext::IdentExt,
29    parse_macro_input, parse_quote, spanned::Spanned,
30};
31
32use crate::attributes::{ContainerAttributes, FieldAttributes};
33
34/// Derives `architecturekit::Event` and `architecturekit::Schema` for the data
35/// of an event. See `architecturekit::Event`.
36#[proc_macro_derive(Event, attributes(event))]
37pub fn derive_event(input: TokenStream) -> TokenStream {
38    let input = parse_macro_input!(input as DeriveInput);
39    expand_event(&input)
40        .unwrap_or_else(Error::into_compile_error)
41        .into()
42}
43
44/// Derives `architecturekit::Events` for the enum of the events a state evolves
45/// by. See `architecturekit::Events`.
46#[proc_macro_derive(Events)]
47pub fn derive_events(input: TokenStream) -> TokenStream {
48    let input = parse_macro_input!(input as DeriveInput);
49    events::expand_events(&input)
50        .unwrap_or_else(Error::into_compile_error)
51        .into()
52}
53
54/// Derives `architecturekit::Schema` for a type in the data of an event. See
55/// `architecturekit::Schema`.
56#[proc_macro_derive(Schema)]
57pub fn derive_schema(input: TokenStream) -> TokenStream {
58    let input = parse_macro_input!(input as DeriveInput);
59    expand_schema(&input)
60        .unwrap_or_else(Error::into_compile_error)
61        .into()
62}
63
64fn expand_event(input: &DeriveInput) -> Result<TokenStream2> {
65    // One event type names one shape of data. A generic event would give
66    // several shapes the same event type, and so the same schema.
67    if let Some(param) = input.generics.params.first() {
68        return Err(Error::new(
69            param.span(),
70            "an event can not be generic, since its event type names exactly one shape of data",
71        ));
72    }
73
74    let event_type = event_type_of(input)?;
75    let schema = expand_schema(input)?;
76    let name = &input.ident;
77
78    Ok(quote! {
79        #schema
80
81        impl ::architecturekit::Event for #name {
82            const EVENT_TYPE: &'static str = #event_type;
83        }
84    })
85}
86
87fn event_type_of(input: &DeriveInput) -> Result<LitStr> {
88    let mut event_type: Option<LitStr> = None;
89
90    for attribute in input.attrs.iter().filter(|a| a.path().is_ident("event")) {
91        attribute.parse_nested_meta(|meta| {
92            if !meta.path.is_ident("type") {
93                return Err(meta.error("expected `type = \"...\"`"));
94            }
95            let value: LitStr = meta.value()?.parse()?;
96            if value.value().is_empty() {
97                return Err(Error::new(value.span(), "the event type must not be empty"));
98            }
99            if event_type.replace(value).is_some() {
100                return Err(meta.error("the event type is given more than once"));
101            }
102            Ok(())
103        })?;
104    }
105
106    event_type.ok_or_else(|| {
107        Error::new(
108            input.ident.span(),
109            "an event needs its event type, as in #[event(type = \"io.eventsourcingdb.library.book-acquired\")]",
110        )
111    })
112}
113
114fn expand_schema(input: &DeriveInput) -> Result<TokenStream2> {
115    let container = ContainerAttributes::of(&input.attrs)?;
116    let body = match &input.data {
117        Data::Struct(data) => schema_of_struct(&container, &data.fields, input)?,
118        Data::Enum(data) => {
119            return Err(Error::new(
120                data.enum_token.span,
121                "the schema of an enum can not be derived, since serde writes enums in several shapes; \
122                 implement architecturekit::Schema by hand",
123            ));
124        }
125        Data::Union(data) => {
126            return Err(Error::new(
127                data.union_token.span,
128                "the schema of a union can not be derived; implement architecturekit::Schema by hand",
129            ));
130        }
131    };
132
133    let mut generics = input.generics.clone();
134    for param in &mut generics.params {
135        if let GenericParam::Type(param) = param {
136            param.bounds.push(parse_quote!(::architecturekit::Schema));
137        }
138    }
139    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
140    let name = &input.ident;
141
142    Ok(quote! {
143        impl #impl_generics ::architecturekit::Schema for #name #type_generics #where_clause {
144            fn schema() -> ::architecturekit::__private::Value {
145                ::architecturekit::__private::derive(::std::any::type_name::<Self>(), || #body)
146            }
147        }
148    })
149}
150
151fn schema_of_struct(
152    container: &ContainerAttributes,
153    fields: &Fields,
154    input: &DeriveInput,
155) -> Result<TokenStream2> {
156    container.refuse_what_can_not_be_derived()?;
157
158    match fields {
159        Fields::Named(named) => {
160            let mut written = Vec::new();
161            for field in &named.named {
162                let attributes = FieldAttributes::of(&field.attrs)?;
163                if attributes.is_skipped {
164                    continue;
165                }
166                attributes.refuse_what_can_not_be_derived()?;
167                written.push((field, attributes));
168            }
169
170            if container.is_transparent {
171                return match written.as_slice() {
172                    [(field, _)] => {
173                        let field_type = &field.ty;
174                        Ok(quote! { <#field_type as ::architecturekit::Schema>::schema() })
175                    }
176                    _ => Err(Error::new(
177                        input.ident.span(),
178                        "a transparent struct needs exactly one field that is not skipped",
179                    )),
180                };
181            }
182
183            let entries = written.iter().map(|(field, attributes)| {
184                let name = attributes.rename.clone().unwrap_or_else(|| {
185                    let ident = field.ident.as_ref().map(IdentExt::unraw).map(|i| i.to_string());
186                    container.rename_all.apply_to_field(&ident.unwrap_or_default())
187                });
188                let field_type = &field.ty;
189                if attributes.is_optional {
190                    quote! { (#name, <#field_type as ::architecturekit::Schema>::schema_without_null(), true) }
191                } else {
192                    quote! { (#name, <#field_type as ::architecturekit::Schema>::schema(), false) }
193                }
194            });
195
196            Ok(quote! {
197                ::architecturekit::__private::object(::std::any::type_name::<Self>(), ::std::vec![#(#entries),*])
198            })
199        }
200        Fields::Unnamed(unnamed) => match unnamed.unnamed.first() {
201            Some(field) if unnamed.unnamed.len() == 1 => {
202                // serde writes a newtype struct as the value it wraps.
203                let field_type = &field.ty;
204                Ok(quote! { <#field_type as ::architecturekit::Schema>::schema() })
205            }
206            _ => Err(Error::new(
207                unnamed.span(),
208                "the schema of a tuple struct with several fields can not be derived, since serde writes it \
209                 as an array of mixed values; use named fields, or implement architecturekit::Schema by hand",
210            )),
211        },
212        Fields::Unit => Err(Error::new(
213            input.ident.span(),
214            "the schema of a unit struct can not be derived, since serde writes it as null; \
215             to describe data without fields, use `struct Name {}`",
216        )),
217    }
218}