framework_proc_web/
lib.rs1mod 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}