1use devise::ext::SpanDiagnosticExt;
2use devise::{MetaItem, Spanned, Result, FromMeta, Diagnostic};
3use proc_macro2::TokenStream;
45use crate::{http, http_codegen};
67/// This structure represents the parsed `catch` attribute and associated items.
8pub struct Attribute {
9/// The status associated with the code in the `#[catch(code)]` attribute.
10pub status: Option<http::Status>,
11/// The function that was decorated with the `catch` attribute.
12pub function: syn::ItemFn,
13}
1415/// We generate a full parser for the meta-item for great error messages.
16#[derive(FromMeta)]
17struct Meta {
18#[meta(naked)]
19code: Code,
20}
2122/// `Some` if there's a code, `None` if it's `default`.
23#[derive(Debug)]
24struct Code(Option<http::Status>);
2526impl FromMeta for Code {
27fn from_meta(meta: &MetaItem) -> Result<Self> {
28if usize::from_meta(meta).is_ok() {
29let status = http_codegen::Status::from_meta(meta)?;
30Ok(Code(Some(status.0)))
31 } else if let MetaItem::Path(path) = meta {
32if path.is_ident("default") {
33Ok(Code(None))
34 } else {
35Err(meta.span().error("expected `default`"))
36 }
37 } else {
38let msg = format!("expected integer or `default`, found {}", meta.description());
39Err(meta.span().error(msg))
40 }
41 }
42}
4344impl Attribute {
45pub fn parse(args: TokenStream, input: proc_macro::TokenStream) -> Result<Self> {
46let function: syn::ItemFn = syn::parse(input)
47 .map_err(Diagnostic::from)
48 .map_err(|diag| diag.help("`#[catch]` can only be used on functions"))?;
4950let attr: MetaItem = syn::parse2(quote!(catch(#args)))?;
51let status = Meta::from_meta(&attr)
52 .map(|meta| meta.code.0)
53 .map_err(|diag| diag.help("`#[catch]` expects a status code int or `default`: \
54 `#[catch(404)]` or `#[catch(default)]`"))?;
5556Ok(Attribute { status, function })
57 }
58}