1#![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#[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}