1use syn::{
7 FnArg, GenericArgument, Ident, ImplItem, ImplItemFn, ItemImpl, Lit, Meta, Pat, PathArguments,
8 ReturnType, Type,
9};
10
11#[derive(Debug, Clone)]
13pub struct MethodInfo {
14 pub method: ImplItemFn,
16 pub name: Ident,
18 pub docs: Option<String>,
20 pub params: Vec<ParamInfo>,
22 pub return_info: ReturnInfo,
24 pub is_async: bool,
26}
27
28#[derive(Debug, Clone)]
30pub struct ParamInfo {
31 pub name: Ident,
33 pub ty: Type,
35 pub is_optional: bool,
37 pub is_id: bool,
39 pub wire_name: Option<String>,
41 pub location: Option<ParamLocation>,
43 pub default_value: Option<String>,
45}
46
47#[derive(Debug, Clone, PartialEq)]
49pub enum ParamLocation {
50 Query,
51 Path,
52 Body,
53 Header,
54}
55
56#[derive(Debug, Clone)]
58pub struct ReturnInfo {
59 pub ty: Option<Type>,
61 pub ok_type: Option<Type>,
63 pub err_type: Option<Type>,
65 pub some_type: Option<Type>,
67 pub is_result: bool,
69 pub is_option: bool,
71 pub is_unit: bool,
73 pub is_stream: bool,
75 pub stream_item: Option<Type>,
77}
78
79impl MethodInfo {
80 pub fn parse(method: &ImplItemFn) -> syn::Result<Option<Self>> {
84 let name = method.sig.ident.clone();
85 let is_async = method.sig.asyncness.is_some();
86
87 let has_receiver = method
89 .sig
90 .inputs
91 .iter()
92 .any(|arg| matches!(arg, FnArg::Receiver(_)));
93 if !has_receiver {
94 return Ok(None);
95 }
96
97 let docs = extract_docs(&method.attrs);
99
100 let params = parse_params(&method.sig.inputs)?;
102
103 let return_info = parse_return_type(&method.sig.output);
105
106 Ok(Some(Self {
107 method: method.clone(),
108 name,
109 docs,
110 params,
111 return_info,
112 is_async,
113 }))
114 }
115}
116
117pub fn extract_docs(attrs: &[syn::Attribute]) -> Option<String> {
119 let docs: Vec<String> = attrs
120 .iter()
121 .filter_map(|attr| {
122 if attr.path().is_ident("doc")
123 && let Meta::NameValue(meta) = &attr.meta
124 && let syn::Expr::Lit(syn::ExprLit {
125 lit: Lit::Str(s), ..
126 }) = &meta.value
127 {
128 return Some(s.value().trim().to_string());
129 }
130 None
131 })
132 .collect();
133
134 if docs.is_empty() {
135 None
136 } else {
137 Some(docs.join("\n"))
138 }
139}
140
141pub fn parse_param_attrs(
143 attrs: &[syn::Attribute],
144) -> syn::Result<(Option<String>, Option<ParamLocation>, Option<String>)> {
145 let mut wire_name = None;
146 let mut location = None;
147 let mut default_value = None;
148
149 for attr in attrs {
150 if !attr.path().is_ident("param") {
151 continue;
152 }
153
154 attr.parse_nested_meta(|meta| {
155 if meta.path.is_ident("name") {
157 let value: syn::LitStr = meta.value()?.parse()?;
158 wire_name = Some(value.value());
159 Ok(())
160 }
161 else if meta.path.is_ident("default") {
163 let value = meta.value()?;
165 let lookahead = value.lookahead1();
166 if lookahead.peek(syn::LitStr) {
167 let lit: syn::LitStr = value.parse()?;
168 default_value = Some(format!("\"{}\"", lit.value()));
169 } else if lookahead.peek(syn::LitInt) {
170 let lit: syn::LitInt = value.parse()?;
171 default_value = Some(lit.to_string());
172 } else if lookahead.peek(syn::LitBool) {
173 let lit: syn::LitBool = value.parse()?;
174 default_value = Some(lit.value.to_string());
175 } else {
176 return Err(lookahead.error());
177 }
178 Ok(())
179 }
180 else if meta.path.is_ident("query") {
182 location = Some(ParamLocation::Query);
183 Ok(())
184 } else if meta.path.is_ident("path") {
185 location = Some(ParamLocation::Path);
186 Ok(())
187 } else if meta.path.is_ident("body") {
188 location = Some(ParamLocation::Body);
189 Ok(())
190 } else if meta.path.is_ident("header") {
191 location = Some(ParamLocation::Header);
192 Ok(())
193 } else {
194 Err(meta.error(
195 "unknown attribute\n\
196 \n\
197 Valid attributes: name, default, query, path, body, header\n\
198 \n\
199 Examples:\n\
200 - #[param(name = \"q\")]\n\
201 - #[param(default = 10)]\n\
202 - #[param(query)]\n\
203 - #[param(header, name = \"X-API-Key\")]",
204 ))
205 }
206 })?;
207 }
208
209 Ok((wire_name, location, default_value))
210}
211
212pub fn parse_params(
214 inputs: &syn::punctuated::Punctuated<FnArg, syn::Token![,]>,
215) -> syn::Result<Vec<ParamInfo>> {
216 let mut params = Vec::new();
217
218 for arg in inputs {
219 match arg {
220 FnArg::Receiver(_) => continue, FnArg::Typed(pat_type) => {
222 let name = match pat_type.pat.as_ref() {
223 Pat::Ident(pat_ident) => pat_ident.ident.clone(),
224 other => {
225 return Err(syn::Error::new_spanned(
226 other,
227 "unsupported parameter pattern\n\
228 \n\
229 Server-less macros require simple parameter names.\n\
230 Use: name: String\n\
231 Not: (name, _): (String, i32) or &name: &String",
232 ));
233 }
234 };
235
236 let ty = (*pat_type.ty).clone();
237 let is_optional = is_option_type(&ty);
238 let is_id = is_id_param(&name);
239
240 let (wire_name, location, default_value) = parse_param_attrs(&pat_type.attrs)?;
242
243 params.push(ParamInfo {
244 name,
245 ty,
246 is_optional,
247 is_id,
248 wire_name,
249 location,
250 default_value,
251 });
252 }
253 }
254 }
255
256 Ok(params)
257}
258
259pub fn parse_return_type(output: &ReturnType) -> ReturnInfo {
261 match output {
262 ReturnType::Default => ReturnInfo {
263 ty: None,
264 ok_type: None,
265 err_type: None,
266 some_type: None,
267 is_result: false,
268 is_option: false,
269 is_unit: true,
270 is_stream: false,
271 stream_item: None,
272 },
273 ReturnType::Type(_, ty) => {
274 let ty = ty.as_ref().clone();
275
276 if let Some((ok, err)) = extract_result_types(&ty) {
278 return ReturnInfo {
279 ty: Some(ty),
280 ok_type: Some(ok),
281 err_type: Some(err),
282 some_type: None,
283 is_result: true,
284 is_option: false,
285 is_unit: false,
286 is_stream: false,
287 stream_item: None,
288 };
289 }
290
291 if let Some(inner) = extract_option_type(&ty) {
293 return ReturnInfo {
294 ty: Some(ty),
295 ok_type: None,
296 err_type: None,
297 some_type: Some(inner),
298 is_result: false,
299 is_option: true,
300 is_unit: false,
301 is_stream: false,
302 stream_item: None,
303 };
304 }
305
306 if let Some(item) = extract_stream_item(&ty) {
308 return ReturnInfo {
309 ty: Some(ty),
310 ok_type: None,
311 err_type: None,
312 some_type: None,
313 is_result: false,
314 is_option: false,
315 is_unit: false,
316 is_stream: true,
317 stream_item: Some(item),
318 };
319 }
320
321 if is_unit_type(&ty) {
323 return ReturnInfo {
324 ty: Some(ty),
325 ok_type: None,
326 err_type: None,
327 some_type: None,
328 is_result: false,
329 is_option: false,
330 is_unit: true,
331 is_stream: false,
332 stream_item: None,
333 };
334 }
335
336 ReturnInfo {
338 ty: Some(ty),
339 ok_type: None,
340 err_type: None,
341 some_type: None,
342 is_result: false,
343 is_option: false,
344 is_unit: false,
345 is_stream: false,
346 stream_item: None,
347 }
348 }
349 }
350}
351
352pub fn extract_option_type(ty: &Type) -> Option<Type> {
354 if let Type::Path(type_path) = ty
355 && let Some(segment) = type_path.path.segments.last()
356 && segment.ident == "Option"
357 && let PathArguments::AngleBracketed(args) = &segment.arguments
358 && let Some(GenericArgument::Type(inner)) = args.args.first()
359 {
360 return Some(inner.clone());
361 }
362 None
363}
364
365pub fn is_option_type(ty: &Type) -> bool {
367 extract_option_type(ty).is_some()
368}
369
370pub fn extract_result_types(ty: &Type) -> Option<(Type, Type)> {
372 if let Type::Path(type_path) = ty
373 && let Some(segment) = type_path.path.segments.last()
374 && segment.ident == "Result"
375 && let PathArguments::AngleBracketed(args) = &segment.arguments
376 {
377 let mut iter = args.args.iter();
378 if let (Some(GenericArgument::Type(ok)), Some(GenericArgument::Type(err))) =
379 (iter.next(), iter.next())
380 {
381 return Some((ok.clone(), err.clone()));
382 }
383 }
384 None
385}
386
387pub fn extract_stream_item(ty: &Type) -> Option<Type> {
389 if let Type::ImplTrait(impl_trait) = ty {
390 for bound in &impl_trait.bounds {
391 if let syn::TypeParamBound::Trait(trait_bound) = bound
392 && let Some(segment) = trait_bound.path.segments.last()
393 && segment.ident == "Stream"
394 && let PathArguments::AngleBracketed(args) = &segment.arguments
395 {
396 for arg in &args.args {
397 if let GenericArgument::AssocType(assoc) = arg
398 && assoc.ident == "Item"
399 {
400 return Some(assoc.ty.clone());
401 }
402 }
403 }
404 }
405 }
406 None
407}
408
409pub fn is_unit_type(ty: &Type) -> bool {
411 if let Type::Tuple(tuple) = ty {
412 return tuple.elems.is_empty();
413 }
414 false
415}
416
417pub fn is_id_param(name: &Ident) -> bool {
419 let name_str = name.to_string();
420 name_str == "id" || name_str.ends_with("_id")
421}
422
423pub fn extract_methods(impl_block: &ItemImpl) -> syn::Result<Vec<MethodInfo>> {
429 let mut methods = Vec::new();
430
431 for item in &impl_block.items {
432 if let ImplItem::Fn(method) = item {
433 if method.sig.ident.to_string().starts_with('_') {
435 continue;
436 }
437 if let Some(info) = MethodInfo::parse(method)? {
439 methods.push(info);
440 }
441 }
442 }
443
444 Ok(methods)
445}
446
447pub fn get_impl_name(impl_block: &ItemImpl) -> syn::Result<Ident> {
449 if let Type::Path(type_path) = impl_block.self_ty.as_ref()
450 && let Some(segment) = type_path.path.segments.last()
451 {
452 return Ok(segment.ident.clone());
453 }
454 Err(syn::Error::new_spanned(
455 &impl_block.self_ty,
456 "Expected a simple type name",
457 ))
458}