use std::sync::LazyLock;
use darling::{FromDeriveInput, ast::Data};
use heck::ToShoutySnakeCase;
use macro_field_utils::{VariantsCollector, VariantsHelper};
use proc_macro_error2::{abort_call_site, abort_if_dirty, emit_error};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use regex::Regex;
use syn::DeriveInput;
use crate::input::*;
static VARIABLES_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\{([^}]+)\}").unwrap());
pub(crate) fn r#impl(input: DeriveInput) -> TokenStream {
let crate_expr = quote!(error_info);
let opts = match ErrorInfoOpts::from_derive_input(&input) {
Ok(o) => o,
Err(e) => {
return e.write_errors();
}
};
let Data::Enum(variants) = opts.data else {
abort_call_site!("Only enums are supported.")
};
for v in variants.iter() {
if v.message.is_empty() {
emit_error!(v.message.span(), "The message can't be empty");
}
let variables = VARIABLES_REGEX
.captures_iter(&v.message)
.map(|c| c.get(1).unwrap().as_str())
.collect::<Vec<_>>();
for variable in variables.iter() {
if !v
.fields
.iter()
.any(|f| f.ident.as_ref().expect("enum_named") == variable)
{
emit_error!(v.message.span(), "Missing a field for the variable '{}'", variable);
}
}
for field in v.fields.iter().map(|f| f.ident.as_ref().expect("enum_named")) {
if !variables.iter().any(|variable| field == variable) {
emit_error!(field, "The field is not being used");
}
}
}
abort_if_dirty();
let enum_ident = opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let match_status = VariantsHelper::new(&variants)
.left_collector(VariantsCollector::variant_fields_collector(quote!(Self)))
.right_collector(|v, _fields| {
let status = &v.status;
quote!(#status)
})
.collect();
let match_code = VariantsHelper::new(&variants)
.left_collector(VariantsCollector::variant_fields_collector(quote!(Self)))
.right_collector(|v, _fields| {
let code = &v.ident.to_string().to_shouty_snake_case();
quote!(#code)
})
.collect();
let match_raw_message = VariantsHelper::new(&variants)
.left_collector(VariantsCollector::variant_fields_collector(quote!(Self)))
.right_collector(|v, _fields| {
let raw_message = v.message.as_ref();
quote!(#raw_message)
})
.collect();
let match_fields = VariantsHelper::new(&variants)
.left_collector(VariantsCollector::variant_fields_collector(quote!(Self)))
.right_collector(|_v, fields| {
if fields.is_empty() {
quote!(Default::default())
} else {
let fields_expr = fields.into_vec().into_iter().map(|f| {
let field_ident = f.ident.as_ref().expect("enum_named");
let field_name = f.ident.as_ref().expect("enum_named").to_string();
quote!((#field_name.into(), #field_ident.to_string()))
});
quote!(::std::collections::HashMap::from([#( #fields_expr ),*]))
}
})
.collect();
#[cfg(not(feature = "summary"))]
let summary_expr = TokenStream::default();
#[cfg(feature = "summary")]
let summary_expr = {
let summary_vec_expr = variants.iter().map(|v| {
let status = &v.status;
let code = &v.ident.to_string().to_shouty_snake_case();
let raw_message = v.message.as_ref();
quote!(
#crate_expr::ErrorInfoSummary {
status: #status,
code: #code,
raw_message: #raw_message,
}
)
});
let enum_ident_snake = enum_ident.to_string().to_shouty_snake_case();
let enum_ident_snake = format_ident!("{enum_ident_snake}");
quote!(
#[automatically_derived]
impl #impl_generics #enum_ident #ty_generics #where_clause {
fn summaries() -> Vec<#crate_expr::ErrorInfoSummary> {
vec![
#( #summary_vec_expr ),*
]
}
}
#[::linkme::distributed_slice(#crate_expr::ERROR_INFO_SUMMARY)]
static #enum_ident_snake: fn() -> Vec<#crate_expr::ErrorInfoSummary> = #enum_ident::summaries;
)
};
quote!(
#[automatically_derived]
#[allow(non_shorthand_field_patterns)]
impl #impl_generics #crate_expr::ErrorInfo for #enum_ident #ty_generics #where_clause {
fn status(&self) -> ::http::StatusCode {
match self #match_status
}
fn code(&self) -> &'static str {
match self #match_code
}
fn raw_message(&self) -> &'static str {
match self #match_raw_message
}
fn fields(&self) -> std::collections::HashMap<String, String> {
match self #match_fields
}
}
#summary_expr
)
}