1mod codegen;
2mod parse;
3mod route;
4mod service;
5mod template;
6
7use proc_macro::TokenStream;
8use proc_macro2::{Span, TokenStream as TokenStream2};
9use quote::quote;
10use syn::{Item, ItemMod, parse_macro_input};
11
12use route::expand_route_from_fn;
13use service::ServiceArgs;
14
15#[proc_macro_attribute]
16pub fn service(args: TokenStream, input: TokenStream) -> TokenStream {
17 expand_service(args, input)
18 .unwrap_or_else(syn::Error::into_compile_error)
19 .into()
20}
21
22fn expand_service(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream2> {
25 let args = syn::parse::<ServiceArgs>(args)?;
26 let ServiceArgs {
27 name,
28 prefix,
29 policy,
30 } = args;
31
32 let mut module = syn::parse::<ItemMod>(input)?;
33 let apigate_path = apigate_crate_path()?;
34
35 let Some((_, items)) = module.content.as_mut() else {
36 return Err(syn::Error::new_spanned(
37 &module,
38 "#[apigate::service] requires an inline module body: `mod x { ... }`",
39 ));
40 };
41
42 let mut route_defs = Vec::new();
43 let mut generated_items = Vec::new();
44
45 for item in items.iter_mut() {
46 if let Item::Fn(f) = item {
47 if let Some(extracted) = expand_route_from_fn(&apigate_path, f)? {
48 route_defs.push(extracted.route_def);
49 generated_items.extend(extracted.generated_items);
50 }
51 }
52 }
53
54 let routes_ident = syn::Ident::new("__APIGATE_ROUTES", Span::call_site());
57
58 let service_policy = match &policy {
59 None => quote!(None),
60 Some(p) => quote!(Some(#p)),
61 };
62
63 items.extend(generated_items);
64 items.push(syn::parse_quote! {
65 #[doc(hidden)]
66 pub const #routes_ident: &'static [#apigate_path::RouteDef] = &[
67 #(#route_defs),*
68 ];
69 });
70
71 items.push(syn::parse_quote! {
72 pub fn routes() -> #apigate_path::Routes {
73 #apigate_path::Routes {
74 service: #name,
75 prefix: #prefix,
76 policy: #service_policy,
77 routes: #routes_ident,
78 }
79 }
80 });
81
82 Ok(quote!(#module))
83}
84
85#[proc_macro_attribute]
86pub fn hook(_args: TokenStream, input: TokenStream) -> TokenStream {
87 input
89}
90
91#[proc_macro_attribute]
92pub fn map(_args: TokenStream, input: TokenStream) -> TokenStream {
93 input
94}
95
96fn apigate_crate_path() -> Result<TokenStream2, syn::Error> {
98 use proc_macro_crate::{FoundCrate, crate_name};
99
100 match crate_name("apigate") {
101 Ok(FoundCrate::Itself) => Ok(quote!(::apigate)),
102 Ok(FoundCrate::Name(n)) => {
103 let ident = syn::Ident::new(&n, Span::call_site());
104 Ok(quote!(::#ident))
105 }
106 Err(_) => Ok(quote!(::apigate)),
107 }
108}
109
110macro_rules! route_stub {
111 ($name:ident) => {
112 #[proc_macro_attribute]
113 pub fn $name(_args: TokenStream, input: TokenStream) -> TokenStream {
114 let item = parse_macro_input!(input as syn::Item);
115 syn::Error::new_spanned(
116 item,
117 concat!(
118 "`#[apigate::",
119 stringify!($name),
120 "]` must be used inside a `#[apigate::service] mod ... {}` module"
121 ),
122 )
123 .to_compile_error()
124 .into()
125 }
126 };
127}
128
129route_stub!(get);
130route_stub!(post);
131route_stub!(put);
132route_stub!(delete);
133route_stub!(patch);
134route_stub!(head);
135route_stub!(options);