framework-proc-web 26.8.151

lingting framework
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::Parser;
use syn::punctuated::Punctuated;
use syn::{Error, Expr, ExprLit, Lit, Meta, Token};

#[derive(Clone, Copy)]
pub enum Method {
    Get,
    Post,
    Put,
    Patch,
    Delete,
}

impl Method {
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Get => "GET",
            Self::Post => "POST",
            Self::Put => "PUT",
            Self::Patch => "PATCH",
            Self::Delete => "DELETE",
        }
    }

    fn parse(value: &Expr) -> syn::Result<Self> {
        let Expr::Path(path) = value else {
            return Err(Error::new_spanned(value, "method 必须是 HTTP 方法标识符"));
        };
        let Some(segment) = path.path.segments.last() else {
            return Err(Error::new_spanned(value, "method 不能为空"));
        };
        let method = segment.ident.to_string().to_ascii_lowercase();
        match method.as_str() {
            "get" => Ok(Self::Get),
            "post" => Ok(Self::Post),
            "put" => Ok(Self::Put),
            "patch" => Ok(Self::Patch),
            "delete" => Ok(Self::Delete),
            _ => Err(Error::new_spanned(value, "web_api 不支持该 HTTP 方法")),
        }
    }

    pub fn tokens(self) -> TokenStream {
        match self {
            Self::Get => quote!(::framework_web::WebMethod::Get),
            Self::Post => quote!(::framework_web::WebMethod::Post),
            Self::Put => quote!(::framework_web::WebMethod::Put),
            Self::Patch => quote!(::framework_web::WebMethod::Patch),
            Self::Delete => quote!(::framework_web::WebMethod::Delete),
        }
    }
}

pub struct WebApiOptions {
    pub method: Method,
    pub path: syn::LitStr,
    pub auth: Option<Expr>,
}

impl WebApiOptions {
    pub fn parse(args: TokenStream, forced_method: Option<Method>) -> syn::Result<Self> {
        let metas = Punctuated::<Meta, Token![,]>::parse_terminated.parse2(args)?;
        let mut method = forced_method;
        let mut path = None;
        let mut auth = None;

        for meta in metas {
            let Meta::NameValue(value) = meta else {
                return Err(Error::new_spanned(
                    meta,
                    "web_api 参数必须使用 name = value",
                ));
            };
            let Some(name) = value.path.get_ident() else {
                return Err(Error::new_spanned(value.path, "web_api 参数名必须是标识符"));
            };
            let name_value = name.to_string();
            match name_value.as_str() {
                "method" if forced_method.is_none() => method = Some(Method::parse(&value.value)?),
                "method" => {
                    return Err(Error::new_spanned(
                        value,
                        "HTTP 方法简化宏不能再次指定 method",
                    ));
                }
                "path" => path = Some(parse_path(&value.value)?),
                "auth" => auth = Some(value.value),
                _ => return Err(Error::new_spanned(name, "web_api 不支持该参数")),
            }
        }

        Ok(Self {
            method: method
                .ok_or_else(|| Error::new(proc_macro2::Span::call_site(), "缺少 method"))?,
            path: path.ok_or_else(|| Error::new(proc_macro2::Span::call_site(), "缺少 path"))?,
            auth,
        })
    }
}

fn parse_path(value: &Expr) -> syn::Result<syn::LitStr> {
    let Expr::Lit(ExprLit {
        lit: Lit::Str(path),
        ..
    }) = value
    else {
        return Err(Error::new_spanned(value, "path 必须是字符串"));
    };
    let normalized = path.value().trim_matches('/').to_string();
    if normalized.is_empty() {
        return Err(Error::new_spanned(path, "path 不能为空"));
    }
    Ok(syn::LitStr::new(&normalized, path.span()))
}