Skip to main content

errlanes_derive/
lib.rs

1//! Proc macros for `errlanes`: `#[derive(Rejection)]`, `#[derive(Lift)]`, `#[derive(Failure)]`,
2//! `#[compose]`, and `#[instrument]`.
3
4mod classify;
5mod composition;
6mod failure;
7mod instrument;
8mod lift;
9mod rejection;
10mod std_error;
11
12use proc_macro::TokenStream;
13use syn::{DeriveInput, parse_macro_input};
14
15// Resolve the runtime where the macro is invoked, including dependency aliases
16// and EsRepo consumers that use es_entity's reexport without a direct dependency.
17fn runtime_path() -> proc_macro2::TokenStream {
18    use proc_macro_crate::{FoundCrate, crate_name};
19    match crate_name("errlanes") {
20        Ok(FoundCrate::Name(name)) => {
21            let name = quote::format_ident!("{name}");
22            quote::quote!(#name)
23        }
24        Ok(FoundCrate::Itself) => quote::quote!(errlanes),
25        Err(_) => match crate_name("es-entity") {
26            Ok(FoundCrate::Name(name)) => {
27                let name = quote::format_ident!("{name}");
28                quote::quote!(#name::errlanes)
29            }
30            Ok(FoundCrate::Itself) => quote::quote!(es_entity::errlanes),
31            Err(_) => quote::quote!(errlanes),
32        },
33    }
34}
35
36fn resolve_runtime(tokens: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
37    use proc_macro2::{Group, TokenTree};
38    let mut output = proc_macro2::TokenStream::new();
39    let mut input = tokens.into_iter().peekable();
40    let mut qualified = false;
41    while let Some(token) = input.next() {
42        let was_qualified = qualified;
43        qualified = matches!(&token, TokenTree::Punct(p) if p.as_char() == ':');
44        match token {
45            TokenTree::Ident(ref name)
46                if name == "errlanes"
47                    && !was_qualified
48                    && matches!(input.peek(), Some(TokenTree::Punct(p)) if p.as_char() == ':') =>
49            {
50                output.extend(runtime_path());
51            }
52            TokenTree::Group(group) => {
53                let mut rewritten = Group::new(group.delimiter(), resolve_runtime(group.stream()));
54                rewritten.set_span(group.span());
55                output.extend([TokenTree::Group(rewritten)]);
56            }
57            other => output.extend([other]),
58        }
59    }
60    output
61}
62
63fn expand(
64    input: TokenStream,
65    f: impl FnOnce(&DeriveInput) -> darling::Result<proc_macro2::TokenStream>,
66) -> TokenStream {
67    let ast = parse_macro_input!(input as DeriveInput);
68    match f(&ast) {
69        Ok(tokens) => resolve_runtime(tokens).into(),
70        Err(e) => e.write_errors().into(),
71    }
72}
73
74#[proc_macro_derive(Rejection, attributes(rejection, lift, error, source, from))]
75pub fn derive_rejection(input: TokenStream) -> TokenStream {
76    expand(input, rejection::derive)
77}
78
79#[proc_macro_derive(Classify, attributes(classify, rejection, lift, error, source, from))]
80pub fn derive_classify(input: TokenStream) -> TokenStream {
81    expand(input, classify::derive)
82}
83
84#[proc_macro_derive(Lift, attributes(lift))]
85pub fn derive_lift(input: TokenStream) -> TokenStream {
86    expand(input, |ast| lift::derive(ast).map_err(darling::Error::from))
87}
88
89#[proc_macro_derive(Failure, attributes(failure))]
90pub fn derive_failure(input: TokenStream) -> TokenStream {
91    expand(input, failure::derive)
92}
93
94#[proc_macro_attribute]
95pub fn compose(args: TokenStream, input: TokenStream) -> TokenStream {
96    if !args.is_empty() {
97        return syn::Error::new(
98            proc_macro2::Span::call_site(),
99            "compose takes no arguments; use #[compose(flatten)] on source placeholders",
100        )
101        .to_compile_error()
102        .into();
103    }
104    match syn::parse::<syn::ItemEnum>(input).and_then(composition::expand) {
105        Ok(t) => resolve_runtime(t).into(),
106        Err(e) => e.to_compile_error().into(),
107    }
108}
109
110#[doc(hidden)]
111#[proc_macro]
112pub fn __compose_rejection(input: TokenStream) -> TokenStream {
113    match composition::callback(input.into()) {
114        Ok(t) => resolve_runtime(t).into(),
115        Err(e) => e.to_compile_error().into(),
116    }
117}
118
119#[proc_macro_attribute]
120pub fn instrument(args: TokenStream, input: TokenStream) -> TokenStream {
121    match instrument::expand(args.into(), input.into()) {
122        Ok(t) => resolve_runtime(t).into(),
123        Err(e) => e.to_compile_error().into(),
124    }
125}