Skip to main content

uxum_macros/
lib.rs

1//! # uxum-macros
2//!
3//! Procedural macros for use with UXUM framework.
4
5#![forbid(unsafe_code)]
6#![deny(elided_lifetimes_in_paths, unreachable_pub)]
7
8mod case;
9mod handler;
10mod util;
11
12use darling::{ast::NestedMeta, FromMeta};
13use proc_macro::TokenStream;
14use proc_macro_error::{abort, abort_call_site, proc_macro_error};
15use quote::{format_ident, quote};
16use syn::{parse_macro_input, ItemFn};
17
18use crate::{
19    case::{ToCamelCase, ToSnakeCase},
20    handler::{
21        body::detect_request_body,
22        data::{HandlerData, HandlerMethod},
23        state::detect_state,
24    },
25};
26
27/// Attribute macro for declaring service endpoints.
28#[proc_macro_error]
29#[proc_macro_attribute]
30pub fn handler(args: TokenStream, input: TokenStream) -> TokenStream {
31    let attr_args = match NestedMeta::parse_meta_list(args.into()) {
32        Ok(meta) => meta,
33        Err(err) => abort_call_site!("Unable to parse attributes: {}", err),
34    };
35    let input = parse_macro_input!(input as ItemFn);
36    let fn_ident = &input.sig.ident;
37    let handler_ident = format_ident!("{}HandlerMeta", fn_ident.to_camel_case());
38    let mod_ident = format_ident!("_uxum_private_hdl_{}", fn_ident.to_snake_case());
39
40    let data = match HandlerData::from_list(&attr_args) {
41        Ok(val) => val,
42        Err(err) => abort!(
43            input.sig.ident,
44            "Unable to parse handler attributes: {}",
45            err
46        ),
47    };
48
49    let handler_name = data.name.unwrap_or_else(|| input.sig.ident.to_string());
50    let handler_path = data.path.unwrap_or_else(|| format!("/{handler_name}"));
51    let request_body = detect_request_body(&input);
52    let handler_method = match data.method {
53        Some(method) => method,
54        None => {
55            if request_body.is_some() {
56                HandlerMethod::Post
57            } else {
58                HandlerMethod::Get
59            }
60        }
61    };
62    let no_auth = data.no_auth;
63    let permissions = match no_auth {
64        true => Vec::new(),
65        false => data.permissions,
66    };
67    let handler_spec = data.spec.generate_schema(
68        &handler_name,
69        &handler_path,
70        &handler_method,
71        &input,
72        &request_body,
73    );
74
75    let state = detect_state(&input);
76    let into_service = match state {
77        Some(s) => quote! { super::#fn_ident.with_state(::uxum::state::get::<#s>()) },
78        None => quote! { super::#fn_ident.into_service() },
79    };
80
81    quote! {
82        #[::uxum::reexport::tracing::instrument(name = "handler", skip_all, fields(name = #handler_name))]
83        #input
84
85        #[doc(hidden)]
86        #[allow(missing_docs)]
87        mod #mod_ident {
88            use ::std::convert::Infallible;
89
90            use ::uxum::{
91                reexport::{
92                    axum::{
93                        body::Body,
94                        handler::{Handler, HandlerWithoutStateExt},
95                    },
96                    http,
97                    hyper::{Request, Response},
98                    inventory,
99                    okapi,
100                    openapi3,
101                    schemars,
102                    tower::util::BoxCloneSyncService,
103                },
104                HandlerExt,
105            };
106
107            use super::*;
108
109            struct #handler_ident;
110
111            #[automatically_derived]
112            impl HandlerExt for #handler_ident {
113                #[inline]
114                #[must_use]
115                fn name(&self) -> &'static str {
116                    #handler_name
117                }
118
119                #[inline]
120                #[must_use]
121                fn path(&self) -> &'static str {
122                    #handler_path
123                }
124
125                #[inline]
126                #[must_use]
127                fn spec_path(&self) -> &'static str {
128                    #handler_path
129                }
130
131                #[inline]
132                #[must_use]
133                fn method(&self) -> http::Method {
134                    #handler_method
135                }
136
137                #[inline]
138                #[must_use]
139                fn permissions(&self) -> &'static [&'static str] {
140                    &[#(#permissions),*]
141                }
142
143                #[inline]
144                #[must_use]
145                fn no_auth(&self) -> bool {
146                    #no_auth
147                }
148
149                #[inline]
150                #[must_use]
151                fn service(&self) -> BoxCloneSyncService<Request<Body>, Response<Body>, Infallible> {
152                    BoxCloneSyncService::new(#into_service)
153                }
154
155                #[inline]
156                #[must_use]
157                fn openapi_spec(&self, gen: &mut schemars::gen::SchemaGenerator) -> openapi3::Operation {
158                    #handler_spec
159                }
160            }
161
162            inventory::submit! { &#handler_ident as &dyn HandlerExt }
163        }
164    }.into()
165}