1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
mod error;
mod route;
mod service;
mod state;

use proc_macro::TokenStream;
use syn::{spanned::Spanned, Error, ImplItem, ImplItemFn};

#[proc_macro_derive(State, attributes(borrow))]
pub fn state_impl(item: TokenStream) -> TokenStream {
    let item = syn::parse_macro_input!(item);
    state::state(item).unwrap_or_else(|e| e.to_compile_error().into())
}

/// attribute macro for `xitca-web` application.
///
/// # Pattern
/// ```plain
/// #[route("path", method = <method>[, attributes])]
/// ```
///
/// # Attributes
/// - `"path"`: string literal represent path register to http router.
///   `"/foo"` for example.  
/// - `method = <method>`: function path of http method register to http router.
///   `method = get` for example.
/// - `enclosed = <type>`: typed middleware applied to route.
/// - `enclosed_fn = <async function>`: async function as middleware applied to route
///
/// # Example
/// ```rust(no_run)
/// # use xitca_web::{codegen::route, handler::handler_service, service::Service, App, WebContext};
/// #[route("/", method = get, enclosed_fn = middleware_fn)]
/// async fn index() -> &'static str {
///     ""
/// }
///
/// async fn middleware_fn<S, C, B, Res, Err>(service: &S, ctx: WebContext<'_, C, B>) -> Result<Res, Err>
/// where
///     S: for<'r> Service<WebContext<'r, C, B>, Response = Res, Error = Err>
/// {
///     service.call(ctx).await
/// }
///
/// App::new()
///     // add generated index typed route to application.
///     .at_typed(index)
/// #   .at("/nah", handler_service(nah));
///
/// # async fn nah(_: &WebContext<'_>) -> &'static str {
/// #   // needed to infer the body type of request
/// #   ""
/// # }
/// ```
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
    let attr = syn::parse_macro_input!(attr);
    let item = syn::parse_macro_input!(item);
    route::route(attr, item).unwrap_or_else(|e| e.to_compile_error().into())
}

#[proc_macro_attribute]
pub fn error_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
    let item = syn::parse_macro_input!(item);
    error::error(attr, item).unwrap_or_else(|e| e.to_compile_error().into())
}

#[proc_macro_attribute]
pub fn middleware_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
    let item = syn::parse_macro_input!(item);
    service::middleware(attr, item).unwrap_or_else(|e| e.to_compile_error().into())
}

#[proc_macro_attribute]
pub fn service_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
    let item = syn::parse_macro_input!(item);
    service::service(attr, item).unwrap_or_else(|e| e.to_compile_error().into())
}

fn find_async_method<'a>(items: &'a [ImplItem], ident_str: &str) -> Option<Result<&'a ImplItemFn, Error>> {
    for item in items.iter() {
        if let ImplItem::Fn(func) = item {
            if func.sig.ident.to_string().as_str() == ident_str {
                if func.sig.asyncness.is_none() {
                    return Some(Err(Error::new(
                        func.span(),
                        format!("{ident_str} method must be async fn"),
                    )));
                }

                return Some(Ok(func));
            }
        }
    }

    None
}