ruled_router_derive/
lib.rs1use proc_macro::TokenStream;
7use proc_macro2::Span;
8use syn::{parse_macro_input, Attribute, Data, DeriveInput, Expr, Fields, Lit, LitStr, Meta};
9
10mod query;
11mod querystring;
12mod route;
13mod router_match;
14
15use query::expand_query_derive;
16use querystring::expand_querystring_derive;
17use route::expand_route_derive;
18use router_match::expand_router_match_derive;
19
20#[proc_macro_attribute]
22pub fn sub_router(_args: TokenStream, input: TokenStream) -> TokenStream {
23 input
24}
25
26#[proc_macro_derive(RouterData, attributes(router, query, sub_router))]
53pub fn derive_router(input: TokenStream) -> TokenStream {
54 let input = parse_macro_input!(input as DeriveInput);
55 expand_route_derive(input).unwrap_or_else(syn::Error::into_compile_error).into()
56}
57
58#[proc_macro_derive(QueryDerive, attributes(query))]
73pub fn derive_query(input: TokenStream) -> TokenStream {
74 let input = parse_macro_input!(input as DeriveInput);
75 expand_query_derive(input).unwrap_or_else(syn::Error::into_compile_error).into()
76}
77
78#[proc_macro_derive(QueryString)]
95pub fn derive_querystring(input: TokenStream) -> TokenStream {
96 let input = parse_macro_input!(input as DeriveInput);
97 expand_querystring_derive(input)
98 .unwrap_or_else(syn::Error::into_compile_error)
99 .into()
100}
101
102#[proc_macro_derive(RouterMatch)]
135pub fn derive_router_match(input: TokenStream) -> TokenStream {
136 let input = parse_macro_input!(input as DeriveInput);
137 expand_router_match_derive(input)
138 .unwrap_or_else(syn::Error::into_compile_error)
139 .into()
140}
141
142fn extract_route_config(input: &DeriveInput) -> syn::Result<(String, Option<String>)> {
144 for attr in &input.attrs {
145 if attr.path().is_ident("router") {
146 if let Meta::List(meta_list) = &attr.meta {
147 let mut pattern = None;
148 let mut query_type = None;
149
150 let parser = meta_list.parse_args_with(syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated)?;
152
153 for meta in parser {
154 if let Meta::NameValue(name_value) = meta {
155 if name_value.path.is_ident("pattern") {
156 if let syn::Expr::Lit(expr_lit) = &name_value.value {
157 if let Lit::Str(lit_str) = &expr_lit.lit {
158 pattern = Some(lit_str.value());
159 }
160 }
161 } else if name_value.path.is_ident("query") {
162 if let syn::Expr::Lit(expr_lit) = &name_value.value {
163 if let Lit::Str(lit_str) = &expr_lit.lit {
164 query_type = Some(lit_str.value());
165 }
166 }
167 }
168 }
169 }
170
171 if let Some(pattern) = pattern {
172 return Ok((pattern, query_type));
173 }
174 }
175 }
176 }
177 Err(syn::Error::new_spanned(input, "Missing #[router(pattern = \"...\")]"))
178}
179
180pub(crate) fn extract_doc_comment(attrs: &[Attribute]) -> Option<String> {
181 let mut docs = Vec::new();
182
183 for attr in attrs {
184 if attr.path().is_ident("doc") {
185 match &attr.meta {
186 Meta::NameValue(name_value) => {
187 if let Expr::Lit(expr_lit) = &name_value.value {
188 if let Lit::Str(lit_str) = &expr_lit.lit {
189 docs.push(lit_str.value());
190 }
191 }
192 }
193 _ => {
194 if let Ok(lit_str) = attr.parse_args::<LitStr>() {
195 docs.push(lit_str.value());
196 }
197 }
198 }
199 }
200 }
201
202 if docs.is_empty() {
203 return None;
204 }
205
206 let combined = docs.join("\n");
207 let trimmed = combined.trim();
208 if trimmed.is_empty() {
209 None
210 } else {
211 Some(trimmed.to_string())
212 }
213}
214
215pub(crate) fn doc_comment_tokens(doc: Option<String>) -> proc_macro2::TokenStream {
216 match doc {
217 Some(text) => {
218 let lit = LitStr::new(&text, Span::call_site());
219 quote::quote! { Some(#lit) }
220 }
221 None => quote::quote! { None },
222 }
223}
224
225fn extract_struct_fields(data: &Data) -> syn::Result<Vec<(syn::Ident, syn::Type)>> {
227 match data {
228 Data::Struct(data_struct) => match &data_struct.fields {
229 Fields::Named(fields_named) => {
230 let mut field_info = Vec::new();
231 for field in &fields_named.named {
232 if let Some(ident) = &field.ident {
233 field_info.push((ident.clone(), field.ty.clone()));
234 }
235 }
236 Ok(field_info)
237 }
238 _ => Err(syn::Error::new_spanned(&data_struct.fields, "Only named fields are supported")),
239 },
240 _ => Err(syn::Error::new(proc_macro2::Span::call_site(), "Only structs are supported")),
241 }
242}