sword-macros 0.3.0

Structured web framework built on top of tokio ecosystem, providing powerful features for building robust web applications.
Documentation
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{Fields, Ident, Type};

use super::parse::HttpErrorConfig;
use crate::errors;

pub struct HttpErrorCodegen;

impl HttpErrorCodegen {
    pub fn generate_from_arm(
        enum_name: &Ident,
        variant_name: &Ident,
        fields: &Fields,
        config: &HttpErrorConfig,
    ) -> TokenStream {
        if config.transparent {
            return quote! {
                #enum_name::#variant_name(inner) => ::sword::web::JsonResponse::from(inner),
            };
        }

        let pattern = errors::generate_pattern(enum_name, variant_name, fields);
        let tracing_stmt = Self::generate_tracing_stmt(variant_name, config, fields);
        let builder = Self::generate_json_builder(config);

        quote! {
            #pattern => {
                #tracing_stmt
                #builder
            },
        }
    }

    pub fn generate_json_builder(config: &HttpErrorConfig) -> TokenStream {
        let status_code = config.code.as_ref().unwrap().as_u16();

        let message_expr = errors::generate_message_expr(&config.message, || {
            let default_message = config.default_message();
            quote! { #default_message }
        });

        let mut builder = quote! {
            ::sword::web::JsonResponse::status(#status_code).message(#message_expr)
        };

        if let Some(field_name) = &config.error_field {
            let field_ident = Ident::new(field_name, proc_macro2::Span::call_site());
            builder = quote! { #builder.error(&#field_ident) };
        }

        if let Some(field_name) = &config.errors_field {
            let field_ident = Ident::new(field_name, proc_macro2::Span::call_site());
            builder = quote! { #builder.errors(&#field_ident) };
        }

        builder
    }

    pub fn generate_tracing_stmt(
        variant_name: &Ident,
        config: &HttpErrorConfig,
        fields: &Fields,
    ) -> TokenStream {
        let status_code = config.code.as_ref().unwrap().as_u16();

        errors::generate_tracing_stmt(
            variant_name,
            &config.tracing_level,
            fields,
            &Ident::new("status_code", Span::call_site()),
            quote! { #status_code },
            "HTTP error response",
        )
    }

    pub fn generate_variant_fn(variant_name: &Ident, fields: &Fields) -> TokenStream {
        let Fields::Named(named) = fields else {
            return quote! {};
        };

        let fn_name = variant_name;

        let params = named.named.iter().map(|field| {
            let name = &field.ident;
            let ty = &field.ty;

            if Self::is_string_type(ty) {
                quote! { #name: impl Into<String> }
            } else {
                quote! { #name: #ty }
            }
        });

        let field_assignments = named.named.iter().map(|field| {
            let name = &field.ident;
            let ty = &field.ty;

            if Self::is_string_type(ty) {
                quote! { #name: #name.into() }
            } else {
                quote! { #name }
            }
        });

        quote! {
            #[allow(non_snake_case)]
            pub fn #fn_name(#(#params),*) -> Self {
                Self::#variant_name {
                    #(#field_assignments),*
                }
            }
        }
    }

    fn is_string_type(ty: &Type) -> bool {
        if let Type::Path(type_path) = ty
            && let Some(segment) = type_path.path.segments.last()
        {
            return segment.ident == "String";
        }
        false
    }
}