a3s-boot-macros 0.1.2

Attribute macros for a3s-boot
Documentation
use quote::quote;
use syn::{Attribute, LitStr};

use crate::openapi::{
    ApiExtensionArgs, ApiExtraModelArgs, AttrKind as OpenApiAttrKind, RouteSpec as RouteOpenApiSpec,
};

pub(in crate::controller) fn take_controller_openapi_attrs(
    attrs: &[Attribute],
) -> (Vec<Attribute>, ControllerOpenApiAttrs, Vec<syn::Error>) {
    let mut clean_attrs = Vec::new();
    let mut openapi = ControllerOpenApiAttrs::default();
    let mut errors = Vec::new();

    for attr in attrs {
        let Some(kind) = OpenApiAttrKind::from_attribute(attr) else {
            clean_attrs.push(attr.clone());
            continue;
        };

        match kind {
            OpenApiAttrKind::Tag => match attr.parse_args::<LitStr>() {
                Ok(tag) => openapi.tags.push(tag),
                Err(error) => errors.push(error),
            },
            OpenApiAttrKind::ApiExtraModel => match attr.parse_args::<ApiExtraModelArgs>() {
                Ok(extra_model) => openapi.extra_models.push(extra_model),
                Err(error) => errors.push(error),
            },
            OpenApiAttrKind::ApiExtension => match attr.parse_args::<ApiExtensionArgs>() {
                Ok(extension) => openapi.extensions.push(extension),
                Err(error) => errors.push(error),
            },
            OpenApiAttrKind::HideFromOpenApi => {
                if let Err(error) = crate::expect_no_extractor_args(attr, "hide_from_openapi") {
                    errors.push(error);
                } else {
                    openapi.hidden = true;
                }
            }
            _ => errors.push(syn::Error::new_spanned(
                attr,
                "only #[tag(\"name\")], #[api_extra_model(...)], #[api_extension(...)], and #[hide_from_openapi] are supported on #[controller] impl blocks",
            )),
        }
    }

    (clean_attrs, openapi, errors)
}

pub(in crate::controller) fn take_route_openapi_attrs(
    attrs: &[Attribute],
) -> (Vec<Attribute>, Vec<RouteOpenApiSpec>, Vec<syn::Error>) {
    let mut clean_attrs = Vec::new();
    let mut specs = Vec::new();
    let mut errors = Vec::new();

    for attr in attrs {
        let Some(kind) = OpenApiAttrKind::from_attribute(attr) else {
            clean_attrs.push(attr.clone());
            continue;
        };

        match kind.parse_route_spec(attr) {
            Ok(spec) => specs.push(spec),
            Err(error) => errors.push(error),
        }
    }

    (clean_attrs, specs, errors)
}

#[derive(Default)]
pub(in crate::controller) struct ControllerOpenApiAttrs {
    tags: Vec<LitStr>,
    extra_models: Vec<ApiExtraModelArgs>,
    extensions: Vec<ApiExtensionArgs>,
    hidden: bool,
}

impl ControllerOpenApiAttrs {
    pub(in crate::controller) fn tokens(&self) -> Vec<proc_macro2::TokenStream> {
        let mut tokens = self
            .tags
            .iter()
            .map(|tag| quote!(with_tag(#tag)))
            .chain(self.extra_models.iter().map(ApiExtraModelArgs::tokens))
            .chain(self.extensions.iter().map(ApiExtensionArgs::tokens))
            .collect::<Vec<_>>();
        if self.hidden {
            tokens.push(quote!(hide_from_openapi()));
        }
        tokens
    }
}