use proc_macro2::TokenStream;
use quote::quote;
use syn::{Expr, ExprPath, Index};
use crate::input::{ErrorInput, MasterrorSpec, VariantData};
pub fn struct_mapping_impl(input: &ErrorInput, spec: &MasterrorSpec) -> TokenStream {
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let code = &spec.code;
let category = &spec.category;
let grpc_mapping =
mapping_option_tokens(spec.map_grpc.as_ref(), code, category, MappingKind::Grpc);
let problem_mapping = mapping_option_tokens(
spec.map_problem.as_ref(),
code,
category,
MappingKind::Problem
);
quote! {
impl #impl_generics #ident #ty_generics #where_clause {
pub const HTTP_MAPPING: masterror::mapping::HttpMapping =
masterror::mapping::HttpMapping::new((#code), (#category));
pub const GRPC_MAPPING: Option<masterror::mapping::GrpcMapping> = #grpc_mapping;
pub const PROBLEM_MAPPING: Option<masterror::mapping::ProblemMapping> = #problem_mapping;
}
}
}
pub fn enum_mapping_impl(input: &ErrorInput, variants: &[VariantData]) -> TokenStream {
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let http_entries: Vec<_> = variants
.iter()
.map(|variant| {
let spec = variant.masterror.as_ref().expect("presence checked");
let code = &spec.code;
let category = &spec.category;
quote!(masterror::mapping::HttpMapping::new((#code), (#category)))
})
.collect();
let grpc_entries: Vec<_> = variants
.iter()
.filter_map(|variant| {
let spec = variant.masterror.as_ref().expect("presence checked");
let code = &spec.code;
let category = &spec.category;
spec.map_grpc.as_ref().map(
|expr| quote!(masterror::mapping::GrpcMapping::new((#code), (#category), (#expr)))
)
})
.collect();
let problem_entries: Vec<_> = variants
.iter()
.filter_map(|variant| {
let spec = variant.masterror.as_ref().expect("presence checked");
let code = &spec.code;
let category = &spec.category;
spec.map_problem.as_ref().map(|expr| {
quote!(masterror::mapping::ProblemMapping::new((#code), (#category), (#expr)))
})
})
.collect();
let http_len = Index::from(http_entries.len());
let grpc_slice = if grpc_entries.is_empty() {
quote!(&[] as &[masterror::mapping::GrpcMapping])
} else {
quote!(&[#(#grpc_entries),*])
};
let problem_slice = if problem_entries.is_empty() {
quote!(&[] as &[masterror::mapping::ProblemMapping])
} else {
quote!(&[#(#problem_entries),*])
};
quote! {
impl #impl_generics #ident #ty_generics #where_clause {
pub const HTTP_MAPPINGS: [masterror::mapping::HttpMapping; #http_len] = [#(#http_entries),*];
pub const GRPC_MAPPINGS: &'static [masterror::mapping::GrpcMapping] = #grpc_slice;
pub const PROBLEM_MAPPINGS: &'static [masterror::mapping::ProblemMapping] = #problem_slice;
}
}
}
#[derive(Clone, Copy)]
enum MappingKind {
Grpc,
Problem
}
fn mapping_option_tokens(
expr: Option<&Expr>,
code: &Expr,
category: &ExprPath,
kind: MappingKind
) -> TokenStream {
match expr {
Some(value) => match kind {
MappingKind::Grpc => {
quote!(Some(masterror::mapping::GrpcMapping::new((#code), (#category), (#value))))
}
MappingKind::Problem => {
quote!(Some(masterror::mapping::ProblemMapping::new((#code), (#category), (#value))))
}
},
None => quote!(None)
}
}
#[cfg(test)]
mod tests {
use syn::parse_quote;
use super::*;
#[test]
fn test_mapping_option_tokens_grpc_some() {
let expr: Expr = parse_quote!(tonic::Code::Internal);
let code: Expr = parse_quote!("E001");
let category: ExprPath = parse_quote!(ErrorCategory::Internal);
let result = mapping_option_tokens(Some(&expr), &code, &category, MappingKind::Grpc);
let result_str = result.to_string();
assert!(result_str.contains("GrpcMapping"));
assert!(result_str.contains("Some"));
}
#[test]
fn test_mapping_option_tokens_problem_some() {
let expr: Expr = parse_quote!("about:blank");
let code: Expr = parse_quote!("E001");
let category: ExprPath = parse_quote!(ErrorCategory::Internal);
let result = mapping_option_tokens(Some(&expr), &code, &category, MappingKind::Problem);
let result_str = result.to_string();
assert!(result_str.contains("ProblemMapping"));
assert!(result_str.contains("Some"));
}
#[test]
fn test_mapping_option_tokens_none() {
let code: Expr = parse_quote!("E001");
let category: ExprPath = parse_quote!(ErrorCategory::Internal);
let result = mapping_option_tokens(None, &code, &category, MappingKind::Grpc);
assert_eq!(result.to_string(), "None");
}
#[test]
fn test_mapping_option_tokens_problem_none() {
let code: Expr = parse_quote!("E002");
let category: ExprPath = parse_quote!(ErrorCategory::NotFound);
let result = mapping_option_tokens(None, &code, &category, MappingKind::Problem);
assert_eq!(result.to_string(), "None");
}
}