1mod 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
15fn 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}