use proc_macro2::TokenStream;
use quote::quote;
use syn::Error;
use crate::{
input::{ErrorData, ErrorInput, StructData, VariantData},
lint::lifetime_lint_allows
};
pub mod backtrace;
pub mod binding;
pub mod provide;
pub mod source;
use backtrace::{enum_backtrace_method, struct_backtrace_method};
use provide::{enum_provide_method, struct_provide_method};
use source::{struct_source_body, variant_source_arm};
pub fn expand(input: &ErrorInput) -> Result<TokenStream, Error> {
match &input.data {
ErrorData::Struct(data) => expand_struct(input, data),
ErrorData::Enum(variants) => expand_enum(input, variants)
}
}
fn expand_struct(input: &ErrorInput, data: &StructData) -> Result<TokenStream, Error> {
let body = struct_source_body(&data.fields, &data.display);
let backtrace_method = struct_backtrace_method(&data.fields);
let provide_method = struct_provide_method(&data.fields);
let backtrace_method = backtrace_method.unwrap_or_default();
let provide_method = provide_method.unwrap_or_default();
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let lint_allows = lifetime_lint_allows(&input.generics);
Ok(quote! {
#lint_allows
impl #impl_generics std::error::Error for #ident #ty_generics #where_clause {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
#body
}
#backtrace_method
#provide_method
}
})
}
fn expand_enum(input: &ErrorInput, variants: &[VariantData]) -> Result<TokenStream, Error> {
let mut arms = Vec::new();
for variant in variants {
arms.push(variant_source_arm(variant));
}
let backtrace_method = enum_backtrace_method(variants);
let provide_method = enum_provide_method(variants);
let backtrace_method = backtrace_method.unwrap_or_default();
let provide_method = provide_method.unwrap_or_default();
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let lint_allows = lifetime_lint_allows(&input.generics);
Ok(quote! {
#lint_allows
impl #impl_generics std::error::Error for #ident #ty_generics #where_clause {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#(#arms),*
}
}
#backtrace_method
#provide_method
}
})
}
#[cfg(test)]
mod tests {
use proc_macro2::Span;
use syn::parse_quote;
use super::*;
use crate::{
input::{DisplaySpec, Fields},
template_support::{DisplayTemplate, TemplateSegmentSpec}
};
fn make_simple_error_input(name: &str) -> ErrorInput {
ErrorInput {
ident: syn::Ident::new(name, Span::call_site()),
generics: parse_quote!(),
data: ErrorData::Struct(Box::new(StructData {
fields: Fields::Unit,
display: DisplaySpec::Template(DisplayTemplate {
segments: vec![TemplateSegmentSpec::Literal("error".to_string())]
}),
masterror: None,
format_args: Default::default(),
app_error: None
}))
}
}
#[test]
fn test_expand_struct() {
let input = make_simple_error_input("MyError");
let result = expand(&input);
assert!(result.is_ok());
let tokens = result.expect("valid tokens");
let output = tokens.to_string();
assert!(output.contains("impl"));
assert!(output.contains("std :: error :: Error"));
assert!(output.contains("MyError"));
}
#[test]
fn test_expand_enum() {
let variant = VariantData {
ident: syn::Ident::new("Variant", Span::call_site()),
fields: Fields::Unit,
display: DisplaySpec::Template(DisplayTemplate {
segments: vec![TemplateSegmentSpec::Literal("error".to_string())]
}),
format_args: Default::default(),
app_error: None,
masterror: None,
span: Span::call_site()
};
let input = ErrorInput {
ident: syn::Ident::new("MyError", Span::call_site()),
generics: parse_quote!(),
data: ErrorData::Enum(vec![variant])
};
let result = expand(&input);
assert!(result.is_ok());
let tokens = result.expect("valid tokens");
let output = tokens.to_string();
assert!(output.contains("impl"));
assert!(output.contains("match self"));
}
#[test]
fn test_expand_struct_generates_source_method() {
let input = make_simple_error_input("TestError");
let result = expand(&input);
assert!(result.is_ok());
let output = result.expect("tokens").to_string();
assert!(output.contains("fn source"));
}
}