sword-macros 0.3.0

Structured web framework built on top of tokio ecosystem, providing powerful features for building robust web applications.
Documentation
use syn::{Attribute, Error, Ident, LitStr, Token, meta::ParseNestedMeta, spanned::Spanned};

use crate::errors::MessageValue;

#[derive(Debug, Clone, Default)]
pub struct GrpcErrorConfig {
    pub transparent: bool,
    pub code: Option<String>,
    pub message: Option<MessageValue>,
    pub tracing_level: Option<String>,
}

pub fn parse_enum_grpc_error_config(attrs: &[Attribute]) -> syn::Result<GrpcErrorConfig> {
    let mut config = GrpcErrorConfig::default();

    for attr in attrs
        .iter()
        .filter(|attr| attr.path().is_ident("grpc_error"))
    {
        config.parse_grpc_attr(attr, "grpc_error")?;
    }

    config.validate_container()?;
    Ok(config)
}

pub fn parse_variant_grpc_error_config(
    ident: &Ident,
    attrs: &[Attribute],
) -> syn::Result<GrpcErrorConfig> {
    let mut config = GrpcErrorConfig::default();

    for attr in attrs.iter().filter(|attr| attr.path().is_ident("grpc")) {
        config.parse_grpc_attr(attr, "grpc")?;
    }

    for attr in attrs.iter().filter(|attr| attr.path().is_ident("tracing")) {
        config.parse_tracing_attr(attr)?;
    }

    config.validate_variant(ident)?;
    Ok(config)
}

impl GrpcErrorConfig {
    pub fn merged(self, defaults: &GrpcErrorConfig) -> Self {
        Self {
            transparent: self.transparent || defaults.transparent,
            code: self.code.or_else(|| defaults.code.clone()),
            message: self.message.or_else(|| defaults.message.clone()),
            tracing_level: self
                .tracing_level
                .or_else(|| defaults.tracing_level.clone()),
        }
    }

    pub fn default_code(&self) -> &str {
        self.code.as_deref().unwrap_or("unknown")
    }

    fn validate_container(&self) -> syn::Result<()> {
        crate::errors::validate_transparent_container(self.transparent, "grpc")
    }

    fn validate_variant(&self, ident: &Ident) -> syn::Result<()> {
        let has_conflict =
            self.code.is_some() || self.message.is_some() || self.tracing_level.is_some();

        crate::errors::validate_transparent_variant(
            self.transparent,
            has_conflict,
            ident,
            "`code`, `message`, or `tracing`",
        )
    }

    fn parse_grpc_attr(&mut self, attr: &Attribute, attr_name: &str) -> syn::Result<()> {
        attr.parse_nested_meta(|meta| {
            let ident = meta
                .path
                .get_ident()
                .ok_or_else(|| Error::new(meta.path.span(), "expected identifier"))?;

            match ident.to_string().as_str() {
                "transparent" => {
                    if self.transparent {
                        return Err(Error::new(
                            ident.span(),
                            "duplicate `transparent` attribute",
                        ));
                    }

                    self.transparent = true;
                    Ok(())
                }
                "code" => self.set_code(ident, &meta),
                "message" => self.set_message(ident, &meta),
                "tracing" => self.set_tracing_level(ident, &meta),
                other => Err(Error::new(
                    ident.span(),
                    format!("unknown attribute `{other}` in #[{attr_name}(...)]"),
                )),
            }
        })
    }

    fn parse_tracing_attr(&mut self, attr: &Attribute) -> syn::Result<()> {
        crate::errors::parse_tracing_attr(&mut self.tracing_level, attr)
    }

    fn set_code(&mut self, ident: &Ident, meta: &ParseNestedMeta) -> syn::Result<()> {
        if self.code.is_some() {
            return Err(Error::new(ident.span(), "duplicate `code` attribute"));
        }

        self.code = Some(parse_code_value(ident, meta)?);
        Ok(())
    }

    fn set_message(&mut self, ident: &Ident, meta: &ParseNestedMeta) -> syn::Result<()> {
        if self.message.is_some() {
            return Err(Error::new(ident.span(), "duplicate `message` attribute"));
        }

        self.message = Some(MessageValue::parse(ident, meta)?);
        Ok(())
    }

    fn set_tracing_level(&mut self, ident: &Ident, meta: &ParseNestedMeta) -> syn::Result<()> {
        crate::errors::set_tracing_level(&mut self.tracing_level, ident, meta)
    }
}

fn parse_code_value(ident: &Ident, meta: &ParseNestedMeta) -> syn::Result<String> {
    if !meta.input.peek(Token![=]) {
        return Err(Error::new(ident.span(), "expected '=' after 'code'"));
    }

    meta.input.parse::<Token![=]>()?;
    Ok(meta.input.parse::<LitStr>()?.value())
}