vidi_macros/
lib.rs

1//! Macros for Vidi Web Framework
2//!
3//! Generators for handler
4//!
5//! # handler
6//!
7//! ## Example
8//!
9//! ```
10//! # use vidi_core::{IntoResponse, Result};
11//! # use vidi_macros::handler;
12//!
13//! #[handler]
14//! fn about() -> impl IntoResponse {}
15//!
16//! #[handler]
17//! async fn index() -> impl IntoResponse {
18//!     ()
19//! }
20//!
21//! #[handler]
22//! async fn get_user() -> Result<impl IntoResponse> {
23//!     Ok(())
24//! }
25//! ```
26
27#![doc(html_logo_url = "https://viz.rs/logo.svg")]
28#![doc(html_favicon_url = "https://viz.rs/logo.svg")]
29#![doc(test(
30    no_crate_inject,
31    attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
32))]
33#![cfg_attr(docsrs, feature(doc_cfg))]
34
35use proc_macro::TokenStream;
36use quote::quote;
37use syn::{FnArg, ItemFn, Result, ReturnType};
38
39/// Transforms `extract-handler` to a Handler instance.
40#[proc_macro_attribute]
41pub fn handler(_args: TokenStream, input: TokenStream) -> TokenStream {
42    generate_handler(input).unwrap_or_else(|e| e.to_compile_error().into())
43}
44
45fn generate_handler(input: TokenStream) -> Result<TokenStream> {
46    let ast = syn::parse::<ItemFn>(input)?;
47    let vis = &ast.vis;
48    let docs = ast
49        .attrs
50        .iter()
51        .filter(|attr| attr.path().is_ident("doc"))
52        .cloned()
53        .collect::<Vec<_>>();
54    let name = ast.sig.ident.clone();
55    let asyncness = if ast.sig.asyncness.is_some() {
56        Some(quote!(.await))
57    } else {
58        None
59    };
60    let mut out = quote!(Ok(res));
61    let mut is_ok_type = false;
62    match &ast.sig.output {
63        // ()
64        ReturnType::Default => {
65            is_ok_type = true;
66        }
67        ReturnType::Type(_, ty) => match &**ty {
68            syn::Type::Path(path) => {
69                if let Some(seg) = &path.path.segments.first() {
70                    is_ok_type = true;
71                    // T
72                    // impl IntoResponse
73                    // Result<T>
74                    // Result<impl IntoResponse>
75                    if seg.ident == "Result" {
76                        out = quote!(res);
77                    }
78                }
79            }
80            syn::Type::ImplTrait(i) => {
81                if let Some(syn::TypeParamBound::Trait(d)) = &i.bounds.first() {
82                    // T
83                    // impl IntoResponse
84                    if matches!(d.path.get_ident(), Some(ident) if ident == "IntoResponse") {
85                        is_ok_type = true;
86                    }
87                }
88            }
89            syn::Type::Tuple(_) => {
90                // (T,...)
91                is_ok_type = true;
92            }
93            _ => {
94                is_ok_type = false;
95            }
96        },
97    }
98
99    if !is_ok_type {
100        out = quote!();
101    }
102
103    let extractors =
104        ast.sig
105            .inputs
106            .clone()
107            .into_iter()
108            .fold(Vec::new(), |mut extractors, input| {
109                if let FnArg::Typed(pat) = input {
110                    let ty = &pat.ty;
111                    extractors
112                        .push(quote!(<#ty as vidi_core::FromRequest>::extract(&mut req).await?));
113                }
114                extractors
115            });
116
117    let stream = quote! {
118        #(#docs)*
119        #[allow(non_camel_case_types)]
120        #[derive(Clone)]
121        #vis struct #name;
122
123        #[vidi_core::async_trait]
124        impl vidi_core::Handler<vidi_core::Request> for #name
125        {
126            type Output = vidi_core::Result<vidi_core::Response>;
127
128            #[allow(unused, unused_mut)]
129            async fn call(&self, mut req: vidi_core::Request) -> Self::Output {
130                #ast
131                let res = #name(#(#extractors),*)#asyncness;
132                #out.map(vidi_core::IntoResponse::into_response)
133            }
134        }
135    };
136
137    Ok(stream.into())
138}