Skip to main content

framework_proc_web/
lib.rs

1mod arguments;
2mod common;
3mod options;
4mod response;
5
6use crate::arguments::expand_arguments;
7use crate::options::{Method, WebApiOptions};
8use crate::response::expand_response;
9use proc_macro::TokenStream;
10use proc_macro2::TokenStream as TokenStream2;
11use quote::{format_ident, quote};
12use syn::{Error, ItemFn, parse2};
13
14#[proc_macro_attribute]
15pub fn web_api(args: TokenStream, input: TokenStream) -> TokenStream {
16    expand(args.into(), input.into(), None).into()
17}
18
19#[proc_macro_attribute]
20pub fn web_api_get(args: TokenStream, input: TokenStream) -> TokenStream {
21    expand(args.into(), input.into(), Some(Method::Get)).into()
22}
23
24#[proc_macro_attribute]
25pub fn web_api_post(args: TokenStream, input: TokenStream) -> TokenStream {
26    expand(args.into(), input.into(), Some(Method::Post)).into()
27}
28
29#[proc_macro_attribute]
30pub fn web_api_put(args: TokenStream, input: TokenStream) -> TokenStream {
31    expand(args.into(), input.into(), Some(Method::Put)).into()
32}
33
34#[proc_macro_attribute]
35pub fn web_api_patch(args: TokenStream, input: TokenStream) -> TokenStream {
36    expand(args.into(), input.into(), Some(Method::Patch)).into()
37}
38
39#[proc_macro_attribute]
40pub fn web_api_delete(args: TokenStream, input: TokenStream) -> TokenStream {
41    expand(args.into(), input.into(), Some(Method::Delete)).into()
42}
43
44fn expand(args: TokenStream2, input: TokenStream2, forced_method: Option<Method>) -> TokenStream2 {
45    let options = match WebApiOptions::parse(args, forced_method) {
46        Ok(options) => options,
47        Err(error) => return error.to_compile_error(),
48    };
49    let function = match parse2::<ItemFn>(input) {
50        Ok(function) => function,
51        Err(error) => return error.to_compile_error(),
52    };
53    if function.sig.asyncness.is_none() {
54        return Error::new_spanned(function.sig.fn_token, "web_api 仅支持 async fn")
55            .to_compile_error();
56    }
57
58    let (conversions, call_arguments) = match expand_arguments(&function, options.method) {
59        Ok(result) => result,
60        Err(error) => return error.to_compile_error(),
61    };
62    let function_ident = &function.sig.ident;
63    let route_ident = format_ident!("build_{}_route", function_ident);
64    let method = options.method.tokens();
65    let method_name = options.method.as_str();
66    let path = options.path;
67    let auth = options.auth.map_or_else(
68        || quote!(::framework_web::AuthRule::login()),
69        |value| quote!(#value),
70    );
71    let response = expand_response(&function.sig.output, function_ident, &call_arguments);
72    let collect = if cfg!(feature = "collect") {
73        quote! {
74            ::framework_web::push_web_api!(#route_ident);
75        }
76    } else {
77        TokenStream2::new()
78    };
79
80    quote! {
81        #[::framework_proc_ts::ts_api(method = #method_name, path = #path)]
82        #function
83
84        pub(crate) fn #route_ident() -> ::framework_web::WebRoute {
85            ::framework_web::WebRoute {
86                method: #method,
87                path: #path.to_string(),
88                auth: #auth,
89                invoke: ::std::sync::Arc::new(|| {
90                    ::std::boxed::Box::pin(async move {
91                        let context = match ::framework_web::use_web() {
92                            Ok(context) => context,
93                            Err(error) => return ::framework_web::WebResponse::from_error(error, None),
94                        };
95                        let panic_request = context
96                            .request_arc();
97                        let invoke_request = ::std::sync::Arc::clone(&panic_request);
98                        match ::framework_web::catch_panic(async move {
99                            #(#conversions)*
100                            #response
101                        })
102                        .await
103                        {
104                            Ok(response) => response,
105                            Err(error) => ::framework_web::WebResponse::from_error(
106                                error,
107                                Some(panic_request.as_ref()),
108                            ),
109                        }
110                    })
111                }),
112            }
113        }
114
115        #collect
116    }
117}