saphir_macro 2.2.0

Macro generation for http server framework
Documentation
use http::Method;
use proc_macro2::{Ident, TokenStream};
use quote::{quote_spanned, ToTokens};
use std::str::FromStr;
use syn::{
    spanned::Spanned, Attribute, Error, Expr, FnArg, GenericArgument, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta, MetaNameValue, NestedMeta, Pat, PatIdent,
    PatType, Path, PathArguments, PathSegment, Result, ReturnType, Type, TypePath,
};

#[derive(Clone, Debug)]
pub enum MapAfterLoad {
    Json,
    Form,
}

impl MapAfterLoad {
    pub fn new(i: &Ident) -> Option<Self> {
        match i.to_string().as_str() {
            "Json" => Some(MapAfterLoad::Json),
            "Form" => Some(MapAfterLoad::Form),
            _ => None,
        }
    }
}

#[derive(Clone, Debug)]
pub enum ArgsReprType {
    SelfType,
    Request,
    Json,
    Form,
    Multipart,
    Params { is_query_param: bool, is_string: bool },
    Cookie,
    Ext,
    Extensions,
    Option(Box<ArgsReprType>),
}

impl ArgsReprType {
    pub fn new(attrs: &HandlerAttrs, name: &str, p: &PathSegment) -> Result<Self> {
        let typ_ident_str = p.ident.to_string();
        match typ_ident_str.as_str() {
            "Request" => Ok(ArgsReprType::Request),
            "CookieJar" => Ok(ArgsReprType::Cookie),
            "Json" => Ok(ArgsReprType::Json),
            "Form" => Ok(ArgsReprType::Form),
            "Multipart" => Ok(ArgsReprType::Multipart),
            "Ext" => Ok(ArgsReprType::Ext),
            "Extensions" => Ok(ArgsReprType::Extensions),
            "Option" => {
                if let PathArguments::AngleBracketed(a) = &p.arguments {
                    let a = a.args.first().ok_or_else(|| Error::new_spanned(a, "Option types need an type argument"))?;
                    if let GenericArgument::Type(Type::Path(t)) = a {
                        let p2 = t
                            .path
                            .segments
                            .first()
                            .ok_or_else(|| Error::new_spanned(a, "Option types need an type path argument"))?;
                        return Ok(ArgsReprType::Option(Box::new(ArgsReprType::new(attrs, name, p2)?)));
                    }
                }
                Err(Error::new_spanned(p, "Invalid option type"))
            }
            _params => Ok(ArgsReprType::Params {
                is_query_param: !attrs.methods_paths.iter().any(|(_, path)| path.contains(&format!("<{}>", name))),
                is_string: typ_ident_str.eq("String"),
            }),
        }
    }
}

#[derive(Clone, Debug)]
pub struct ArgsRepr {
    pub name: String,
    pub typ: Option<TypePath>,
    pub a_type: ArgsReprType,
    #[cfg(feature = "validate-requests")]
    pub validated: bool,
    #[cfg(feature = "validate-requests")]
    pub is_vec: bool,
}

impl ArgsRepr {
    pub fn new(attrs: &HandlerAttrs, a: &FnArg) -> Result<ArgsRepr> {
        match a {
            FnArg::Receiver(r) => {
                if r.mutability.is_some() {
                    return Err(Error::new_spanned(r, "Controller references are immutable static references, remove 'mut'"));
                }

                if r.reference.is_none() {
                    return Err(Error::new_spanned(r, "Controller cannot be passed as owned value, please use &self"));
                }

                Ok(ArgsRepr {
                    name: "self".to_string(),
                    typ: None,
                    a_type: ArgsReprType::SelfType,
                    #[cfg(feature = "validate-requests")]
                    validated: false,
                    #[cfg(feature = "validate-requests")]
                    is_vec: false,
                })
            }
            FnArg::Typed(t) => match t.pat.as_ref() {
                Pat::Ident(i) => Self::parse_pat_ident(attrs, t, i),
                Pat::Reference(r) => Err(Error::new_spanned(r.and_token, "Unexpected referece, help: remove '&'")),
                _ => Err(Error::new_spanned(t, "Unexpected handler argument format")),
            },
        }
    }

    fn parse_pat_ident(attrs: &HandlerAttrs, t: &PatType, i: &PatIdent) -> Result<Self> {
        let name = i.ident.to_string();

        if let Some(rf) = i.by_ref {
            return Err(Error::new_spanned(rf, "Invalid handler argument, consider removing 'ref'"));
        }
        if let Type::Path(p) = t.ty.as_ref() {
            let typ = Some(p.clone());
            let p = p
                .path
                .segments
                .first()
                .ok_or_else(|| Error::new_spanned(p, "Invalid handler argument, argument should have an ident"))?;
            let a_type = ArgsReprType::new(attrs, &name, p)?;

            #[cfg(feature = "validate-requests")]
            {
                let validated = !attrs.validator_exclusions.contains(&name);
                let mut is_vec = false;

                let typ_ident_str = p.ident.to_string();
                let mut validated_type = None;

                match typ_ident_str.as_str() {
                    "Json" => {
                        if let PathArguments::AngleBracketed(a) = &p.arguments {
                            let a = a.args.first().ok_or_else(|| Error::new_spanned(a, "Json types need an type argument"))?;
                            if let GenericArgument::Type(Type::Path(t)) = a {
                                validated_type = t.path.segments.first().map(|p2| p2.ident.to_string());
                            }
                        }
                    }
                    "Form" => {
                        if let PathArguments::AngleBracketed(a) = &p.arguments {
                            let a = a.args.first().ok_or_else(|| Error::new_spanned(a, "Form types need an type argument"))?;
                            if let GenericArgument::Type(Type::Path(t)) = a {
                                validated_type = t.path.segments.first().map(|p2| p2.ident.to_string());
                            }
                        }
                    }
                    _ => (),
                };

                if let Some("Vec") = validated_type.as_deref() {
                    is_vec = true;
                }

                return Ok(ArgsRepr {
                    name,
                    typ,
                    a_type,
                    validated,
                    is_vec,
                });
            }

            #[cfg(not(feature = "validate-requests"))]
            {
                return Ok(ArgsRepr { name, typ, a_type });
            }
        } else if let Type::Reference(r) = t.ty.as_ref() {
            return Err(Error::new_spanned(r.and_token, "Unexpected reference, help: remove '&'"));
        }

        Err(Error::new_spanned(i, "Invalid handler argument, argument should be TypePath"))
    }

    pub fn is_string(&self) -> bool {
        match self.a_type {
            ArgsReprType::Params { is_string, .. } => is_string,
            _ => false,
        }
    }
}

#[derive(Clone, Debug)]
pub struct HandlerWrapperOpt {
    pub sync_handler: bool,
    pub need_body_load: bool,
    pub request_unused: bool,
    pub parse_cookies: bool,
    pub parse_query: bool,
    pub init_multipart: bool,
    pub map_multipart: bool,
    pub take_body_as: Option<TypePath>,
    pub map_after_load: Option<MapAfterLoad>,
    pub fn_arguments: Vec<ArgsRepr>,
}

impl HandlerWrapperOpt {
    pub fn new(attrs: &HandlerAttrs, m: &ImplItemMethod) -> Result<Self> {
        let mut sync_handler = false;
        let mut need_body_load = false;
        let mut request_unused = true;
        let mut parse_query = false;
        let mut parse_cookies = attrs.cookie;
        let mut take_body_as = None;
        let mut map_after_load = None;
        let mut init_multipart = false;
        let mut map_multipart = false;

        if m.sig.asyncness.is_none() {
            sync_handler = true
        }

        let fn_arguments = m.sig.inputs.iter().map(|fn_a| ArgsRepr::new(attrs, fn_a)).collect::<Result<Vec<ArgsRepr>>>()?;

        fn_arguments.iter().for_each(|a_repr| match &a_repr.a_type {
            ArgsReprType::Cookie => parse_cookies = true,
            ArgsReprType::Params { is_query_param: true, .. } => parse_query = true,
            ArgsReprType::Multipart => init_multipart = true,
            ArgsReprType::Option(inner) => {
                if let ArgsReprType::Params { is_query_param: true, .. } = inner.as_ref() {
                    parse_query = true;
                }
            }
            _ => {}
        });

        let req_param: Option<&ArgsRepr> = fn_arguments
            .iter()
            .find(|a_repr| if let ArgsReprType::Request = a_repr.a_type { true } else { false });

        if let Some(req_param) = req_param {
            request_unused = false;
            if let Some(PathArguments::AngleBracketed(a)) = req_param.typ.as_ref().and_then(|t| t.path.segments.first()).map(|s| &s.arguments) {
                if let Some(GenericArgument::Type(Type::Path(request_body_type))) = a.args.first() {
                    let request_body_type = request_body_type.path.segments.first();
                    let a = match request_body_type
                        .as_ref()
                        .map(|b| (b.ident.to_string(), &b.arguments))
                        .as_ref()
                        .map(|(id, ar)| (id.as_str(), ar))
                    {
                        // Body<_>
                        Some(("Body", pat_arg)) => {
                            if let PathArguments::AngleBracketed(a2) = pat_arg {
                                Some(a2)
                            } else {
                                None
                            }
                        }
                        // Type<_> of Type
                        Some((_, PathArguments::AngleBracketed(_))) => {
                            need_body_load = true;
                            Some(a)
                        }
                        Some((id, PathArguments::None)) => {
                            if id == "Multipart" {
                                init_multipart = true;
                                map_multipart = true;
                            } else {
                                need_body_load = true;
                            }
                            Some(a)
                        }

                        _ => None,
                    };

                    take_body_as = take_body_as.take().or_else(|| {
                        a.and_then(|a| {
                            a.args.first().and_then(|arg| {
                                if let GenericArgument::Type(Type::Path(typ)) = arg {
                                    let ident = typ.path.segments.first().map(|t| &t.ident);
                                    if let Some(ident) = ident {
                                        if ident != "Bytes" {
                                            map_after_load = MapAfterLoad::new(ident);
                                            return Some(typ.clone());
                                        }
                                    }
                                }
                                None
                            })
                        })
                    });
                }
            }
        }

        Ok(HandlerWrapperOpt {
            sync_handler,
            need_body_load,
            request_unused,
            parse_cookies,
            parse_query,
            init_multipart,
            map_multipart,
            take_body_as,
            map_after_load,
            fn_arguments,
        })
    }

    pub fn needs_wrapper_fn(&self) -> bool {
        self.sync_handler
            || self.need_body_load
            || self.request_unused
            || self.take_body_as.is_some()
            || self.parse_query
            || self.parse_cookies
            || self.fn_arguments.len() > 2
    }
}

#[derive(Clone)]
pub struct GuardDef {
    pub guard_type: Path,
    pub init_data: Option<Lit>,
    pub init_expr: Option<Expr>,
    pub init_fn: Option<Path>,
}

impl ToTokens for GuardDef {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let span = tokens.span();
        let guard_type = &self.guard_type;
        if let Some(init_fn) = self.init_fn.as_ref() {
            init_fn.to_tokens(tokens);
        } else {
            quote_spanned!(span=> #guard_type::new).to_tokens(tokens);
        }

        let paren = syn::token::Paren { span };

        paren.surround(tokens, |t| {
            if self.init_fn.is_some() {
                quote_spanned!(span=> self).to_tokens(t);
                if self.init_expr.is_some() || self.init_data.is_some() {
                    syn::token::Comma { spans: [span] }.to_tokens(t);
                }
            }

            if let Some(init_expr) = self.init_expr.as_ref() {
                syn::token::Brace { span }.surround(t, |t2| {
                    init_expr.to_tokens(t2);
                });
            } else if let Some(init_data) = self.init_data.as_ref() {
                init_data.to_tokens(t);
            }
        });
    }
}

#[derive(Clone)]
pub struct HandlerAttrs {
    pub methods_paths: Vec<(Method, String)>,
    pub guards: Vec<GuardDef>,
    pub cookie: bool,
    #[cfg(feature = "validate-requests")]
    pub validator_exclusions: Vec<String>,
}

#[derive(Clone)]
pub struct HandlerRepr {
    pub attrs: HandlerAttrs,
    pub original_method: ImplItemMethod,
    pub return_type: Box<Type>,
    pub wrapper_options: HandlerWrapperOpt,
}

impl HandlerRepr {
    pub fn new(mut m: ImplItemMethod) -> Result<Self> {
        let attrs = HandlerAttrs::new(std::mem::take(&mut m.attrs), &m)?;
        let wrapper_options = HandlerWrapperOpt::new(&attrs, &m)?;
        let return_type = if let ReturnType::Type(_0, typ) = &m.sig.output {
            typ.clone()
        } else {
            return Err(Error::new_spanned(m.sig, "Invalid handler return type"));
        };
        Ok(HandlerRepr {
            attrs,
            original_method: m,
            return_type,
            wrapper_options,
        })
    }
}

impl HandlerAttrs {
    fn empty_with_capacity(capacity: usize) -> Self {
        Self {
            methods_paths: Vec::with_capacity(capacity),
            guards: Vec::with_capacity(capacity),
            cookie: false,
            #[cfg(feature = "validate-requests")]
            validator_exclusions: Vec::new(),
        }
    }

    pub fn new(attrs: Vec<Attribute>, method: &ImplItemMethod) -> Result<Self> {
        let mut handler = HandlerAttrs::empty_with_capacity(attrs.len());

        // FIXME: This contains a lot of duplication and could be simplified
        for attr in attrs {
            let ident = match attr.path.get_ident() {
                Some(i) if i == "cookie" => {
                    handler.cookie = true;
                    continue;
                }
                Some(i) => i,
                None => continue,
            };
            let meta = attr.parse_meta()?;
            match meta {
                Meta::List(attribute) => {
                    if ident == "guard" {
                        let mut guard_type_path = None;
                        let mut init_fn = None;
                        let mut init_data = None;
                        let mut init_expr = None;

                        for guard_meta in attribute.nested {
                            if let NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) = guard_meta {
                                let path = path
                                    .segments
                                    .first()
                                    .ok_or_else(|| Error::new_spanned(&path, "Missing parameters in guard attributes"))?;
                                match path.ident.to_string().as_str() {
                                    "init_fn" => {
                                        if let Lit::Str(lit_str) = lit {
                                            init_fn = Some(
                                                syn::parse_str::<Path>(lit_str.value().as_str())
                                                    .map_err(|_e| Error::new_spanned(path, "Expected path to a guard function"))?,
                                            );
                                        } else {
                                            return Err(Error::new_spanned(lit, "Expected path to a guard function"));
                                        }
                                    }

                                    "init_data" => {
                                        init_data = Some(lit);
                                    }

                                    "init_expr" => {
                                        if let Lit::Str(lit_str) = lit {
                                            init_expr = Some(
                                                syn::parse_str::<Expr>(lit_str.value().as_str())
                                                    .map_err(|_e| Error::new_spanned(path, "Expected a valid rust expression"))?,
                                            );
                                        } else {
                                            return Err(Error::new_spanned(lit, "Expected a string of a valid rust expression"));
                                        }
                                    }

                                    _ => {
                                        return Err(Error::new_spanned(path, "Unauthorized param in guard macro"));
                                    }
                                }
                            } else if let NestedMeta::Meta(Meta::Path(p)) = guard_meta {
                                guard_type_path = Some(p);
                            }
                        }

                        let guard = GuardDef {
                            guard_type: guard_type_path.ok_or_else(|| Error::new_spanned(ident, "Missing guard"))?,
                            init_data,
                            init_fn,
                            init_expr,
                        };

                        handler.guards.push(guard);
                    } else if ident == "openapi" {
                        if attribute.nested.is_empty() {
                            return Err(Error::new_spanned(ident, "openapi attribute cannot be empty"));
                        }
                        for openapi_attributes in &attribute.nested {
                            match openapi_attributes {
                                NestedMeta::Meta(Meta::List(openapi_attribute)) => {
                                    let i = openapi_attribute.path.get_ident().map(|i| i.to_string());
                                    match i.as_deref() {
                                        Some("return") => {
                                            let mut nb_code = 0;
                                            let mut nb_type = 0;
                                            let mut nb_mime = 0;
                                            let mut nb_name = 0;
                                            if openapi_attribute.nested.is_empty() {
                                                return Err(Error::new_spanned(openapi_attribute, "openapi return attribute cannot be empty"));
                                            }
                                            for openapi_return_attribute in &openapi_attribute.nested {
                                                match openapi_return_attribute {
                                                    NestedMeta::Meta(m) => {
                                                        match m {
                                                            Meta::NameValue(nv) => {
                                                                let r = nv.path.get_ident().map(|i| i.to_string());
                                                                match r.as_deref() {
                                                                    Some("code") => {
                                                                        if let Lit::Int(i) = &nv.lit {
                                                                            let c: u16 =
                                                                                i.base10_parse().map_err(|_| Error::new_spanned(i, "Invalid status code"))?;
                                                                            if !(100..600).contains(&c) {
                                                                                return Err(Error::new_spanned(i, "Invalid status code"));
                                                                            }
                                                                            nb_code += 1;
                                                                        }
                                                                    }
                                                                    Some("type") => {
                                                                        if let Lit::Str(_) = &nv.lit {
                                                                            nb_type += 1;
                                                                        // TODO:
                                                                        // Validate
                                                                        // raw object
                                                                        // syntax
                                                                        } else {
                                                                            return Err(Error::new_spanned(
                                                                                m,
                                                                                "Invalid type : expected a type name/path/raw object wrapped in double-quotes",
                                                                            ));
                                                                        }
                                                                    }
                                                                    Some("mime") => {
                                                                        if let Lit::Str(_) = &nv.lit {
                                                                            nb_mime += 1;
                                                                        } else {
                                                                            return Err(Error::new_spanned(m, "Expected a mimetype string"));
                                                                        }
                                                                    }
                                                                    Some("name") => {
                                                                        if let Lit::Str(_) = &nv.lit {
                                                                            nb_name += 1;
                                                                        } else {
                                                                            return Err(Error::new_spanned(m, "Expected a string"));
                                                                        }
                                                                    }
                                                                    _ => return Err(Error::new_spanned(&nv.path, "Invalid openapi return attribute")),
                                                                }
                                                            }
                                                            _ => return Err(Error::new_spanned(m, "Invalid openapi return attribute")),
                                                        }
                                                    }
                                                    _ => return Err(Error::new_spanned(openapi_attribute, "Invalid openapi return attribute")),
                                                }
                                            }

                                            if nb_code == 0 {
                                                return Err(Error::new_spanned(openapi_attribute, "openapi return missing `code` value"));
                                            }

                                            if nb_type == 0 {
                                                return Err(Error::new_spanned(openapi_attribute, "openapi return missing `type` value"));
                                            }

                                            if nb_mime > 1 {
                                                return Err(Error::new_spanned(
                                                    openapi_attribute,
                                                    "openapi cannot have more than 1 `mime` for the same return",
                                                ));
                                            }

                                            if nb_name > 1 {
                                                return Err(Error::new_spanned(openapi_attribute, "Cannot specify the name twice"));
                                            }

                                            if nb_code > 1 && nb_type > 1 {
                                                return Err(Error::new_spanned(
                                                    openapi_attribute,
                                                    "openapi return cannot have both multiple codes and multiple types.\
                                                        \nPlease add a return() group for each code-type pair.
                                                    ",
                                                ));
                                            }
                                        }
                                        Some("return_override") => {
                                            let mut nb_code = 0;
                                            let mut nb_type = 0;
                                            let mut nb_mime = 0;
                                            let mut nb_name = 0;
                                            if openapi_attribute.nested.is_empty() {
                                                return Err(Error::new_spanned(openapi_attribute, "openapi return attribute cannot be empty"));
                                            }
                                            for openapi_return_attribute in &openapi_attribute.nested {
                                                match openapi_return_attribute {
                                                    NestedMeta::Meta(m) => {
                                                        match m {
                                                            Meta::NameValue(nv) => {
                                                                let r = nv.path.get_ident().map(|i| i.to_string());
                                                                match r.as_deref() {
                                                                    Some("code") => {
                                                                        if let Lit::Int(i) = &nv.lit {
                                                                            let c: u16 =
                                                                                i.base10_parse().map_err(|_| Error::new_spanned(i, "Invalid status code"))?;
                                                                            if !(100..600).contains(&c) {
                                                                                return Err(Error::new_spanned(i, "Invalid status code"));
                                                                            }
                                                                            nb_code += 1;
                                                                        }
                                                                    }
                                                                    Some("type") => {
                                                                        if let Lit::Str(_) = &nv.lit {
                                                                            nb_type += 1;
                                                                        // TODO:
                                                                        // Validate
                                                                        // raw object
                                                                        // syntax
                                                                        } else {
                                                                            return Err(Error::new_spanned(
                                                                                m,
                                                                                "Invalid type : expected a type name/path/raw object wrapped in double-quotes",
                                                                            ));
                                                                        }
                                                                    }
                                                                    Some("mime") => {
                                                                        if let Lit::Str(_) = &nv.lit {
                                                                            nb_mime += 1;
                                                                        } else {
                                                                            return Err(Error::new_spanned(m, "Expected a mimetype string"));
                                                                        }
                                                                    }
                                                                    Some("name") => {
                                                                        if let Lit::Str(_) = &nv.lit {
                                                                            nb_name += 1;
                                                                        } else {
                                                                            return Err(Error::new_spanned(m, "Expected a string"));
                                                                        }
                                                                    }
                                                                    _ => return Err(Error::new_spanned(&nv.path, "Invalid openapi return attribute")),
                                                                }
                                                            }
                                                            _ => return Err(Error::new_spanned(m, "Invalid openapi return attribute")),
                                                        }
                                                    }
                                                    _ => return Err(Error::new_spanned(openapi_attribute, "Invalid openapi return attribute")),
                                                }
                                            }

                                            // TODO: confirm specified type to override is in the return signature
                                            if nb_type == 0 {
                                                return Err(Error::new_spanned(openapi_attribute, "You must specify which `type` to override"));
                                            }

                                            if nb_type > 1 {
                                                return Err(Error::new_spanned(openapi_attribute, "can only specify one `type` per return_override"));
                                            }

                                            if nb_mime > 1 {
                                                return Err(Error::new_spanned(openapi_attribute, "cannot specify multiple mimes for an override"));
                                            }

                                            if nb_name > 1 {
                                                return Err(Error::new_spanned(openapi_attribute, "Cannot specify the name twice"));
                                            }

                                            if nb_code == 0 && nb_mime == 0 {
                                                return Err(Error::new_spanned(openapi_attribute, "must override either `code`(s) or `mime`"));
                                            }
                                        }
                                        Some("param") => {
                                            if openapi_attribute.nested.is_empty() {
                                                return Err(Error::new_spanned(openapi_attribute, "openapi param attribute cannot be empty"));
                                            }
                                        }
                                        _ => return Err(Error::new_spanned(openapi_attribute, "Invalid openapi attribute")),
                                    }
                                }
                                _ => return Err(Error::new_spanned(openapi_attributes, "Invalid openapi attribute")),
                            }
                        }
                    } else if ident == "validator" {
                        #[cfg(not(feature = "validate-requests"))]
                        {
                            return Err(Error::new_spanned(ident, "This requires the \"validate-requests\" feature flag in Saphir"));
                        }

                        #[cfg(feature = "validate-requests")]
                        {
                            if attribute.nested.is_empty() {
                                return Err(Error::new_spanned(ident, "validator attribute cannot be empty"));
                            }

                            for validator_attributes in &attribute.nested {
                                match validator_attributes {
                                    NestedMeta::Meta(Meta::List(validator_attribute)) => {
                                        let i = validator_attribute.path.get_ident().map(|i| i.to_string());
                                        match i.as_deref() {
                                            Some("exclude") => {
                                                if validator_attribute.nested.is_empty() {
                                                    return Err(Error::new_spanned(validator_attribute, "validator exclude attribute cannot be empty"));
                                                }
                                                for excluded_meta in &validator_attribute.nested {
                                                    match excluded_meta {
                                                        NestedMeta::Lit(Lit::Str(excluded)) => {
                                                            handler.validator_exclusions.push(excluded.value());
                                                        }
                                                        _ => return Err(Error::new_spanned(validator_attribute, "Expected a list of quoted parameter names")),
                                                    }
                                                }
                                            }
                                            _ => return Err(Error::new_spanned(validator_attribute, "Invalid validator attribute")),
                                        }
                                    }
                                    NestedMeta::Meta(Meta::Path(p)) if p.is_ident("exclude") => {
                                        return Err(Error::new_spanned(p, "expected a list of excluded parameters"))
                                    }
                                    _ => return Err(Error::new_spanned(validator_attributes, "Invalid validator attribute")),
                                }
                            }
                        }
                    } else {
                        let method =
                            Method::from_str(ident.to_string().to_uppercase().as_str()).map_err(|_e| Error::new_spanned(ident, "Invalid HTTP method"))?;

                        if let Some(NestedMeta::Lit(Lit::Str(str))) = attribute.nested.first() {
                            let path = str.value();
                            if !path.starts_with('/') {
                                return Err(Error::new_spanned(str, "Path must start with '/'"));
                            }

                            handler.methods_paths.push((method, path));
                        } else {
                            return Err(Error::new_spanned(attribute, "Missing path for method"));
                        }
                    }
                }
                Meta::NameValue(_) => {}
                Meta::Path(p) => {
                    if let Some(ident_str) = p.get_ident().map(|p| p.to_string()) {
                        if ident_str.starts_with("cookie") {
                            handler.cookie = true;
                        }
                    }
                }
            }
        }

        if handler.methods_paths.is_empty() {
            return Err(Error::new_spanned(
                &method.sig,
                "Missing Router attribute for handler, help: adde something like `#[get(\"/\")]`",
            ));
        }

        Ok(handler)
    }
}

pub fn parse_handlers(input: ItemImpl) -> Result<Vec<HandlerRepr>> {
    input
        .items
        .into_iter()
        .filter_map(|item| match item {
            ImplItem::Method(m) => Some(HandlerRepr::new(m)),
            _ => None,
        })
        .collect()
}