#![deny(unsafe_code)]
#![warn(clippy::all)]
#![warn(missing_docs)]
#![warn(clippy::cargo)]
#![warn(clippy::pedantic)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(clippy::doc_markdown)]
#![allow(clippy::map_unwrap_or)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::unnecessary_wraps)]
#![allow(clippy::unnecessary_map_or)]
#![allow(clippy::ignored_unit_patterns)]
#![allow(clippy::uninlined_format_args)]
use darling::ast::Style;
use darling::{FromDeriveInput, FromField, FromVariant};
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use regex::Regex;
use std::collections::HashMap;
use std::sync::LazyLock; use syn::{
parse_macro_input, spanned::Spanned, Attribute, Data, DeriveInput, Error, Generics, Ident,
Result, Type, Visibility,
};
const ATTRIBUTE_SHORTCUTS: &[(&str, &str)] = &[
(
"y_net",
r#"yoshi(kind = "Network", display = "Network error: {message}")"#,
),
(
"y_timeout",
r#"yoshi(kind = "Timeout", display = "Operation timed out: {operation}")"#,
),
(
"y_io",
r#"yoshi(kind = "Io", display = "IO error: {source}")"#,
),
(
"y_file",
r#"yoshi(kind = "Io", display = "File error: {source}")"#,
),
(
"y_val",
r#"yoshi(kind = "Validation", display = "Validation error: {field}")"#,
),
(
"y_parse",
r#"yoshi(kind = "Validation", display = "Parse error: {message}")"#,
),
(
"y_cfg",
r#"yoshi(kind = "Config", display = "Configuration error: {message}")"#,
),
(
"y_env",
r#"yoshi(kind = "Config", display = "Environment error: {message}")"#,
),
(
"y_sys",
r#"yoshi(kind = "Internal", display = "System error: {message}")"#,
),
(
"y_db",
r#"yoshi(kind = "Network", display = "Database error: {message}")"#,
),
("y_from", "yoshi(from)"),
("y_from_io", "yoshi(from, kind = \"Io\", source)"),
("y_from_net", "yoshi(from, kind = \"Network\", source)"),
("y_from_parse", "yoshi(from, kind = \"Validation\", source)"),
];
static REGEX_CACHE: LazyLock<HashMap<&'static str, Regex>> = LazyLock::new(|| {
let mut cache = HashMap::new();
cache.insert("display_placeholder", Regex::new(r"\{(\w+)\}").unwrap());
cache.insert(
"valid_identifier",
Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap(),
);
cache.insert(
"context_key",
Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap(),
);
cache.insert(
"error_code_pattern",
Regex::new(r"^[A-Z][A-Z0-9_]*$").unwrap(),
);
cache.insert("shorthand_attribute", Regex::new(r"^y_[a-z_]+$").unwrap());
cache.insert(
"error_type_detection",
Regex::new(r"(?i)(error|exception|fault|failure)").unwrap(),
);
cache.insert(
"duration_field",
Regex::new(r"(?i)(duration|timeout|elapsed|delay)").unwrap(),
);
cache
});
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(yoshi), supports(enum_any))]
struct YoshiErrorOpts {
ident: Ident,
#[allow(dead_code)]
vis: Visibility,
generics: Generics,
data: darling::ast::Data<YoshiVariantOpts, ()>,
#[darling(default)]
error_code_prefix: Option<String>,
#[darling(default = "yoshi_default_severity")]
default_severity: u8,
#[darling(default)]
performance_monitoring: bool,
#[darling(default)]
tracing_integration: bool,
#[darling(default)]
doc_prefix: Option<String>,
#[darling(default)]
precise_capturing: bool,
}
fn yoshi_default_severity() -> u8 {
50
}
#[derive(Debug, FromVariant)]
#[darling(attributes(yoshi))]
struct YoshiVariantOpts {
ident: Ident,
fields: darling::ast::Fields<YoshiFieldOpts>,
display: Option<String>,
#[darling(default)]
kind: Option<String>,
#[darling(default)]
error_code: Option<u32>,
#[darling(default)]
severity: Option<u8>,
#[darling(default)]
transient: bool,
#[darling(default)]
context: Option<String>,
#[darling(default)]
suggestion: Option<String>,
#[darling(default)]
convert_with: Option<String>,
#[darling(default)]
doc: Option<String>,
}
#[derive(Debug, FromField)]
#[darling(attributes(yoshi))]
#[allow(clippy::struct_excessive_bools)]
struct YoshiFieldOpts {
ident: Option<Ident>,
ty: Type,
#[darling(default)]
source: bool,
#[darling(default)]
context: Option<String>,
#[darling(default)]
shell: bool,
#[darling(default)]
skip: bool,
#[darling(default)]
format_with: Option<String>,
#[darling(default)]
from: bool,
#[darling(default)]
suggestion: Option<String>,
#[allow(dead_code)]
#[darling(default)]
doc: Option<String>,
}
struct ValidationContext {
errors: Vec<Error>,
warnings: Vec<String>,
performance_hints: Vec<String>,
}
impl ValidationContext {
fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
performance_hints: Vec::new(),
}
}
fn error(&mut self, span: Span, message: impl Into<String>) {
self.errors.push(Error::new(span, message.into()));
}
fn warning(&mut self, message: impl Into<String>) {
self.warnings.push(message.into());
}
fn performance_hint(&mut self, message: impl Into<String>) {
self.performance_hints.push(message.into());
}
fn finish(self) -> Result<()> {
if !self.errors.is_empty() {
let mut errors_iter = self.errors.into_iter();
let mut combined = errors_iter.next().unwrap();
for error in errors_iter {
combined.combine(error);
}
return Err(combined);
}
for warning in self.warnings {
eprintln!("warning: {warning}");
}
for hint in self.performance_hints {
eprintln!("performance hint: {hint}");
}
Ok(())
}
}
#[proc_macro_derive(YoshiError, attributes(yoshi))]
pub fn yoshi_error_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match yoshi_error_derive_impl(input) {
Ok(tokens) => tokens.into(),
Err(error) => error.to_compile_error().into(),
}
}
fn yoshi_error_derive_impl(input: DeriveInput) -> Result<TokenStream2> {
let mut input_with_expanded_attrs = input;
expand_attribute_shortcuts(&mut input_with_expanded_attrs.attrs);
if let Data::Enum(ref mut data_enum) = input_with_expanded_attrs.data {
for variant in &mut data_enum.variants {
expand_attribute_shortcuts(&mut variant.attrs);
for field in &mut variant.fields {
expand_attribute_shortcuts(&mut field.attrs);
}
}
}
let mut opts = YoshiErrorOpts::from_derive_input(&input_with_expanded_attrs)?;
let mut validation = ValidationContext::new(); apply_auto_inference(&mut opts)?;
let darling::ast::Data::Enum(variants) = &opts.data else {
return Err(Error::new(
opts.ident.span(),
"YoshiError can only be derived on enums",
));
};
validate_enum_structure(&opts, variants, &mut validation)?;
let display_impl = generate_display_impl(&opts, variants, &mut validation)?;
let error_impl = generate_error_impl(&opts, variants, &mut validation)?;
let yoshi_conversion_impl = generate_yoshi_conversion(&opts, variants, &mut validation)?;
let additional_impls = generate_additional_impls(&opts, variants, &mut validation)?;
let performance_monitoring = if opts.performance_monitoring {
generate_performance_monitoring(&opts, variants)?
} else {
quote! {}
};
let tracing_integration = if opts.tracing_integration {
generate_tracing_integration(&opts, variants)?
} else {
quote! {}
};
let precise_capturing_traits = if opts.precise_capturing {
generate_precise_capturing_traits(&opts, variants)?
} else {
quote! {}
};
let documentation_impl = generate_comprehensive_documentation(&opts, variants)?;
validation.finish()?;
Ok(quote! {
#documentation_impl
#display_impl
#error_impl
#yoshi_conversion_impl
#additional_impls
#performance_monitoring
#tracing_integration
#precise_capturing_traits
})
}
fn expand_attribute_shortcuts(attrs: &mut [Attribute]) {
for attr in attrs.iter_mut() {
if let Some(ident) = attr.path().get_ident() {
let attr_name = ident.to_string();
if let Some((_, expansion)) = ATTRIBUTE_SHORTCUTS
.iter()
.find(|(short, _)| *short == attr_name)
{
if let Ok(new_attr) = syn::parse_str::<syn::Meta>(expansion) {
attr.meta = new_attr;
}
}
}
}
}
fn apply_auto_inference(opts: &mut YoshiErrorOpts) -> Result<()> {
if let darling::ast::Data::Enum(ref mut variants) = opts.data {
for variant in variants.iter_mut() {
infer_yoshi_attributes(variant)?;
}
}
Ok(())
}
fn infer_yoshi_attributes(variant: &mut YoshiVariantOpts) -> Result<()> {
let variant_name = variant.ident.to_string().to_lowercase();
if variant.kind.is_none() {
variant.kind = Some(
match () {
_ if variant_name.contains("io") || variant_name.contains("file") => "Io",
_ if variant_name.contains("network")
|| variant_name.contains("connection")
|| variant_name.contains("http") =>
{
"Network"
}
_ if variant_name.contains("config") || variant_name.contains("settings") => {
"Config"
}
_ if variant_name.contains("validation")
|| variant_name.contains("invalid")
|| variant_name.contains("parse") =>
{
"Validation"
}
_ if variant_name.contains("timeout") => "Timeout",
_ if variant_name.contains("not_found") || variant_name.contains("missing") => {
"NotFound"
}
_ if variant_name.contains("internal")
|| variant_name.contains("bug")
|| variant_name.contains("panic") =>
{
"Internal"
}
_ if variant_name.contains("resource")
|| variant_name.contains("limit")
|| variant_name.contains("quota") =>
{
"ResourceExhausted"
}
_ => "Foreign", }
.to_string(),
);
}
if variant.severity.is_none() {
variant.severity = Some(match variant.kind.as_deref() {
Some("Internal") => 200, Some("Timeout") => 100, Some("Network") => 80, Some("Validation") => 60, Some("Config") => 70, Some("NotFound") => 50, Some("Io") => 90, Some("ResourceExhausted") => 150, _ => 75, });
} let is_single_tuple_field =
variant.fields.fields.len() == 1 && matches!(variant.fields.style, Style::Tuple);
for field in &mut variant.fields.fields {
if !field.source && is_error_type(&field.ty) {
field.source = true;
}
if field.context.is_none() {
if let Some(ref field_name) = field.ident {
let name: String = field_name.to_string().to_lowercase();
field.context = Some(
match () {
_ if name.contains("path") || name.contains("file") => "file_path",
_ if name.contains("url") || name.contains("uri") => "endpoint",
_ if name.contains("user") || name.contains("id") => "identifier",
_ if name.contains("host") || name.contains("server") => "server",
_ if name.contains("port") => "port",
_ if name.contains("database") || name.contains("db") => "database",
_ if name.contains("table") => "table",
_ if name.contains("query") => "query",
_ => return Ok(()), }
.to_string(),
);
}
}
if !field.from && is_single_tuple_field && is_error_type(&field.ty) {
field.from = true; }
if !field.from && is_single_tuple_field {
if let Some(ref field_name) = field.ident {
let name = field_name.to_string().to_lowercase();
if name.contains("error") || name.contains("cause") || name.contains("source") {
field.from = true;
}
} else {
field.from = true;
}
}
}
if variant.display.is_none() {
variant.display = Some(generate_inferred_display_format(variant));
} if !variant.transient {
variant.transient = matches!(
variant.kind.as_deref(),
Some("Network" | "Timeout" | "ResourceExhausted")
);
}
Ok(())
}
fn is_error_type(ty: &Type) -> bool {
let type_string = quote! { #ty }.to_string();
type_string.contains("std :: io :: Error")
|| type_string.contains("Box < dyn std :: error :: Error")
|| type_string.contains("reqwest :: Error")
|| type_string.contains("serde_json :: Error")
|| type_string.contains("tokio :: io :: Error")
|| type_string.contains("anyhow :: Error")
|| type_string.contains("eyre :: Report")
|| type_string.ends_with("Error")
|| type_string.ends_with("Error >")
}
fn generate_inferred_display_format(variant: &YoshiVariantOpts) -> String {
match variant.fields.style {
Style::Unit => {
format!("{}", variant.ident)
}
Style::Tuple if variant.fields.fields.len() == 1 => {
format!("{}: {{}}", variant.ident)
}
Style::Struct => {
let fields = &variant.fields.fields;
let mut format_parts = vec![format!("{}", variant.ident)];
let important_fields: Vec<_> = fields
.iter()
.filter(|f| !f.skip && f.ident.is_some())
.collect();
if important_fields.is_empty() {
return format!("{}", variant.ident);
}
for field in important_fields.iter().take(3) {
if let Some(ref field_name) = field.ident {
let name = field_name.to_string();
if field.source {
format_parts.push(format!("caused by {{{}}}", name));
} else if name.to_lowercase().contains("message") {
format_parts.push(format!("{{{}}}", name));
} else {
format_parts.push(format!("{}: {{{}}}", name, name));
}
}
}
format_parts.join(" - ")
}
Style::Tuple => {
format!(
"{}: {}",
variant.ident,
(0..variant.fields.fields.len())
.map(|i| format!("{{{}}}", i))
.collect::<Vec<_>>()
.join(", ")
)
}
}
}
fn validate_enum_structure(
opts: &YoshiErrorOpts,
variants: &[YoshiVariantOpts],
validation: &mut ValidationContext,
) -> Result<()> {
if variants.is_empty() {
validation.error(opts.ident.span(), "Error enum cannot be empty");
return Ok(());
}
if variants.len() > 50 {
validation.performance_hint(format!(
"Large error enum with {} variants may impact compilation time. Consider splitting into multiple enums or using error codes for categorization.",
variants.len()
));
}
if let Some(ref prefix) = opts.error_code_prefix {
let prefix_regex = REGEX_CACHE.get("error_code_pattern").unwrap();
if !prefix_regex.is_match(prefix) {
validation.error(
opts.ident.span(),
format!(
"Error code prefix '{}' must match pattern ^[A-Z][A-Z0-9_]*$",
prefix
),
);
}
}
for variant in variants {
validate_variant(variant, validation)?;
}
let mut error_codes = HashMap::new();
for variant in variants {
if let Some(code) = variant.error_code {
if let Some(existing) = error_codes.insert(code, &variant.ident) {
validation.error(
variant.ident.span(),
format!(
"Duplicate error code {} (already used by {})",
code, existing
),
);
}
}
}
let total_fields: usize = variants.iter().map(|v| v.fields.len()).sum();
if total_fields > 100 {
validation
.performance_hint("Consider using Box<T> for large field types to reduce enum size");
}
Ok(())
}
fn validate_variant(variant: &YoshiVariantOpts, validation: &mut ValidationContext) -> Result<()> {
if let Some(ref display_format) = variant.display {
validate_display_format(display_format, variant, validation)?;
}
if let Some(ref kind) = variant.kind {
validate_yoshi_kind_mapping(kind, variant, validation)?;
}
if let Some(severity) = variant.severity {
match severity {
0 => validation
.warning("Severity level 0 indicates no error - consider using Result<T> instead"),
1..=25 => validation.performance_hint(
"Low severity errors might benefit from Result<T, Option<Error>> pattern",
),
200..=255 => validation
.warning("Very high severity levels should be reserved for system-critical errors"),
_ => {} }
}
if variant.transient && variant.kind.as_deref() == Some("Internal") {
validation.warning(
"Internal errors are typically not transient - consider using Network or Timeout kinds",
);
}
for field in variant.fields.iter() {
validate_field(field, validation)?;
}
let source_fields: Vec<_> = variant.fields.iter().filter(|f| f.source).collect();
match source_fields.len() {
0 => {
if variant.kind.as_deref() == Some("Foreign") {
validation
.warning("Foreign error kinds typically benefit from a #[yoshi(source)] field");
}
}
1 => {
let _source_field = source_fields[0];
}
_ => {
validation.error(
variant.ident.span(),
"Only one field can be marked as #[yoshi(source)]",
);
}
}
let from_fields: Vec<_> = variant.fields.iter().filter(|f| f.from).collect();
match (variant.fields.style, from_fields.len()) {
(Style::Tuple, n) if n > 1 => {
validation.error(
variant.ident.span(),
"Only one field can be marked as #[yoshi(from)] in tuple variants - automatic From conversion requires unambiguous field selection",
);
}
(Style::Struct, n) if n > 1 => {
validation.error(
variant.ident.span(),
"Only one field can be marked as #[yoshi(from)] in struct variants - use explicit constructors for multi-field conversion",
);
}
(Style::Unit, n) if n > 0 => {
validation.error(
variant.ident.span(),
"Unit variants cannot have #[yoshi(from)] fields - no fields available for conversion",
);
}
(Style::Tuple, 1) if variant.fields.fields.len() == 1 => {
validation.performance_hint(
"Single-field tuple variants with #[yoshi(from)] enable ergonomic ? operator usage",
);
}
(Style::Struct, 1) => {
validation.warning(
"From conversion on struct variants requires explicit field initialization - consider using constructor functions",
);
}
_ => {} }
Ok(())
}
fn validate_display_format(
format_str: &str,
variant: &YoshiVariantOpts,
validation: &mut ValidationContext,
) -> Result<()> {
let placeholder_regex = REGEX_CACHE.get("display_placeholder").unwrap();
let field_names: std::collections::HashSet<_> = variant
.fields
.iter()
.filter_map(|f| f.ident.as_ref().map(ToString::to_string))
.collect();
for cap in placeholder_regex.captures_iter(format_str) {
let placeholder = &cap[1];
if placeholder != "source" && !field_names.contains(placeholder) {
validation.error(
variant.ident.span(),
format!(
"Display format references unknown field '{}'. Available fields: {:?}",
placeholder, field_names
),
);
}
}
match format_str.len() {
0..=50 => {}, 51..=200 => validation.performance_hint(format!(
"Moderately long format strings may impact formatting performance: '{}' ({} chars)",
format_str, format_str.len()
)),
_ => validation.performance_hint(format!(
"Very long format strings may significantly impact runtime performance - consider simplifying: '{}' ({} chars)",
format_str, format_str.len()
)),
}
if format_str.contains("{{") || format_str.contains("}}") {
validation
.warning("Escaped braces in format strings may indicate unintended literal braces");
}
let placeholder_count = placeholder_regex.find_iter(format_str).count();
if placeholder_count > 10 {
validation.performance_hint(
"Format strings with many placeholders may benefit from custom Display implementation",
);
}
Ok(())
}
fn validate_yoshi_kind_mapping(
kind: &str,
variant: &YoshiVariantOpts,
validation: &mut ValidationContext,
) -> Result<()> {
let valid_kinds = [
"Io",
"Network",
"Config",
"Validation",
"Internal",
"NotFound",
"Timeout",
"ResourceExhausted",
"Foreign",
"Multiple",
];
if !valid_kinds.contains(&kind) {
validation.error(
variant.ident.span(),
format!(
"Unknown YoshiKind '{}'. Valid kinds: {}",
kind,
valid_kinds.join(", ")
),
);
return Ok(());
}
match kind {
"Foreign" => {
if variant.fields.iter().any(|f| f.source) {
validation.performance_hint(
"Foreign errors with source fields enable better error chaining",
);
}
}
"Timeout" => {
let has_duration_field = variant.fields.iter().any(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("duration")
|| name.contains("timeout")
|| name.contains("elapsed")
})
});
if !has_duration_field {
validation.performance_hint(
"Timeout errors often benefit from duration fields for debugging",
);
}
}
"ResourceExhausted" => {
let has_metrics = variant.fields.iter().any(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("limit") || name.contains("current") || name.contains("usage")
})
});
if !has_metrics {
validation.performance_hint(
"ResourceExhausted errors benefit from limit/usage fields for diagnostics",
);
}
}
_ => {}
}
Ok(())
}
fn validate_field(field: &YoshiFieldOpts, validation: &mut ValidationContext) -> Result<()> {
if let Some(ref context_key) = field.context {
let valid_key_regex = REGEX_CACHE.get("context_key").unwrap();
if !valid_key_regex.is_match(context_key) {
validation.error(
field.ty.span(),
format!("Invalid context key '{}'. Must be a valid identifier matching ^[a-zA-Z_][a-zA-Z0-9_]*$", context_key)
);
}
if context_key.len() > 30 {
validation.performance_hint("Long context keys may impact metadata storage efficiency");
}
}
if field.source && field.shell {
validation.error(
field.ty.span(),
"Field cannot be both #[yoshi(source)] and #[yoshi(shell)] - choose one role per field",
);
}
if field.source && field.skip {
validation.warning(
"Source field marked as skip may hide important error information in Display output",
);
}
if field.shell && field.skip {
validation.warning("Shell field marked as skip reduces diagnostic utility");
}
if field.from && field.source {
validation.warning(
"Field marked as both #[yoshi(from)] and #[yoshi(source)] - from conversion will wrap the source error"
);
}
if field.from && field.skip {
validation.error(
field.ty.span(),
"Field cannot be both #[yoshi(from)] and #[yoshi(skip)] - from fields must be accessible for conversion"
);
}
if let Some(ref format_fn) = field.format_with {
let valid_fn_regex = REGEX_CACHE.get("valid_identifier").unwrap();
if !valid_fn_regex.is_match(format_fn) {
validation.error(
field.ty.span(),
format!(
"Invalid format_with function name '{}'. Must be a valid identifier.",
format_fn
),
);
}
}
if field.source && field.context.is_some() && field.shell {
validation.performance_hint(
"Fields with multiple roles may benefit from being split into separate fields",
);
}
if field.from {
validate_from_type_compatibility(&field.ty, validation);
}
Ok(())
}
fn validate_from_type_compatibility(ty: &Type, validation: &mut ValidationContext) {
let type_string = quote! { #ty }.to_string();
let normalized_type = type_string.replace(' ', "");
if is_error_type(ty) {
validation.performance_hint(
"Error types with #[yoshi(from)] enable excellent ? operator ergonomics",
);
return;
}
if is_primitive_or_std_type(&normalized_type) {
validation.performance_hint(
"Primitive and standard library types work well with From conversions",
);
return;
}
if is_complex_generic_type(&normalized_type) {
validation.warning(
"Complex generic types with From conversion may require additional trait bounds",
);
}
if is_large_struct_type(&normalized_type) {
validation.performance_hint(
"Large types may benefit from Box wrapping for better performance in From conversions",
);
}
if normalized_type.starts_with('&') {
validation.warning(
"Reference types in From conversions require careful lifetime management - consider owned types"
);
}
if normalized_type.contains("fn(") || normalized_type.starts_with("fn(") {
validation.performance_hint(
"Function pointer types work well with From conversions for callback patterns",
);
}
if normalized_type.starts_with("Option<") {
validation.warning(
"Option types in From conversions may create nested Option patterns - consider unwrapping"
);
}
if normalized_type.starts_with("Result<") {
validation.warning(
"Result types in From conversions create Result<Result<...>> patterns - consider error flattening"
);
}
if normalized_type.starts_with("Arc<") || normalized_type.starts_with("Rc<") {
validation.performance_hint(
"Arc/Rc types enable efficient cloning in From conversions but may indicate shared ownership needs"
);
}
if normalized_type.contains("String") || normalized_type.contains("&str") {
validation.performance_hint(
"String types benefit from Into<String> patterns for flexible From conversions",
);
}
if is_collection_type(&normalized_type) {
validation.performance_hint(
"Collection types in From conversions may benefit from iterator-based construction for performance"
);
}
if !is_known_type(&normalized_type) {
validation.performance_hint(
"Custom types with From conversion should implement appropriate trait bounds for optimal ergonomics"
);
}
}
fn is_primitive_or_std_type(type_str: &str) -> bool {
matches!(
type_str,
"bool" | "char" | "i8" | "i16" | "i32" | "i64" | "i128" | "isize" |
"u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "f32" | "f64" |
"String" | "&str" | "str" |
"std::string::String" | "std::path::PathBuf" | "std::path::Path" |
"std::ffi::OsString" | "std::ffi::CString" |
"std::net::IpAddr" | "std::net::SocketAddr" |
"std::time::Duration" | "std::time::Instant" | "std::time::SystemTime"
) || type_str.starts_with("std::") && is_std_convertible_type(type_str)
}
fn is_std_convertible_type(type_str: &str) -> bool {
type_str.contains("::Error")
|| type_str.contains("::Addr")
|| type_str.contains("::Path")
|| type_str.contains("::Duration")
|| type_str.contains("::Instant")
}
fn is_complex_generic_type(type_str: &str) -> bool {
let generic_count = type_str.matches('<').count();
let nested_generics = type_str.matches("<<").count();
generic_count > 2
|| nested_generics > 0
|| (type_str.contains('<') && type_str.contains("dyn") && type_str.contains("trait"))
}
fn is_large_struct_type(type_str: &str) -> bool {
let generic_params = type_str.matches(',').count();
generic_params > 5
|| type_str.contains("HashMap")
|| type_str.contains("BTreeMap")
|| type_str.contains("Vec<Vec<")
|| type_str.len() > 100 }
fn is_collection_type(type_str: &str) -> bool {
type_str.starts_with("Vec<")
|| type_str.starts_with("HashMap<")
|| type_str.starts_with("BTreeMap<")
|| type_str.starts_with("HashSet<")
|| type_str.starts_with("BTreeSet<")
|| type_str.starts_with("VecDeque<")
|| type_str.starts_with("LinkedList<")
|| type_str.contains("::Vec<")
|| type_str.contains("::HashMap<")
|| type_str.contains("::BTreeMap<")
}
fn is_known_type(type_str: &str) -> bool {
is_primitive_or_std_type(type_str) ||
is_error_type_string(type_str) ||
is_collection_type(type_str) ||
type_str.starts_with("Option<") ||
type_str.starts_with("Result<") ||
type_str.starts_with("Box<") ||
type_str.starts_with("Arc<") ||
type_str.starts_with("Rc<") ||
type_str.starts_with("Cow<") ||
type_str.contains("serde") ||
type_str.contains("tokio") ||
type_str.contains("reqwest") ||
type_str.contains("uuid") ||
type_str.contains("chrono") ||
type_str.contains("url") ||
type_str.contains("regex")
}
fn is_error_type_string(type_str: &str) -> bool {
type_str.ends_with("Error")
|| type_str.ends_with("Error>")
|| type_str.contains("Error+")
|| type_str.contains("::Error")
|| type_str.contains("std::io::Error")
|| type_str.contains("Box<dynerror::Error")
|| type_str.contains("anyhow::Error")
|| type_str.contains("eyre::Report")
}
fn generate_display_impl(
opts: &YoshiErrorOpts,
variants: &[YoshiVariantOpts],
validation: &mut ValidationContext,
) -> Result<TokenStream2> {
let enum_name = &opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let match_arms = variants
.iter()
.map(|variant| generate_display_arm(variant, validation))
.collect::<Result<Vec<_>>>()?;
let doc_comment = if let Some(ref prefix) = opts.doc_prefix {
format!(
"{} - Generated Display implementation with optimized formatting",
prefix
)
} else {
"Generated Display implementation with optimized formatting using Rust 1.87 enhancements"
.to_string()
};
Ok(quote! {
#[doc = #doc_comment]
impl #impl_generics ::core::fmt::Display for #enum_name #ty_generics #where_clause {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
#(#match_arms)*
}
}
}
})
}
fn generate_display_arm(
variant: &YoshiVariantOpts,
_validation: &mut ValidationContext,
) -> Result<TokenStream2> {
let variant_name = &variant.ident;
let enum_name = format_ident!("Self");
let (pattern, format_logic) = match variant.fields.style {
Style::Unit => {
let ident_string = variant.ident.to_string();
let display_text = variant.display.as_deref().unwrap_or(&ident_string);
(
quote! { #enum_name::#variant_name },
quote! { f.write_str(#display_text) },
)
}
Style::Tuple => {
let fields = &variant.fields.fields;
let field_patterns: Vec<_> = (0..fields.len())
.map(|i| format_ident!("field_{}", i))
.collect();
let pattern = quote! { #enum_name::#variant_name(#(#field_patterns),*) };
if let Some(display_format) = &variant.display {
let format_logic = generate_format_logic(display_format, &field_patterns, fields);
(pattern, format_logic)
} else {
let format_logic = if field_patterns.len() == 1 {
let field = &field_patterns[0];
quote! {
write!(f, "{}: {}", stringify!(#variant_name), #field)
}
} else {
let mut format_str = format!("{}", variant_name);
let mut args = Vec::new();
for (i, field_ident) in field_patterns.iter().enumerate() {
let field_config = &fields[i];
if !field_config.skip {
format_str = format!("{} {{{}}}", format_str, field_ident);
args.push(quote! { #field_ident });
}
}
quote! {
write!(f, #format_str, #(#args),*)
}
};
(pattern, format_logic)
}
}
Style::Struct => {
let fields = &variant.fields.fields;
let field_patterns: Vec<_> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
let pattern = quote! { #enum_name::#variant_name { #(#field_patterns),* } };
if let Some(display_format) = &variant.display {
let format_logic =
generate_format_logic_named(display_format, &field_patterns, fields);
(pattern, format_logic)
} else {
let non_skipped_fields: Vec<_> = fields
.iter()
.filter(|f| !f.skip)
.map(|f| f.ident.as_ref().unwrap())
.collect();
let format_logic = if non_skipped_fields.is_empty() {
quote! { write!(f, "{}", stringify!(#variant_name)) }
} else {
quote! {
write!(f, "{}: {{ ", stringify!(#variant_name))?;
#(
write!(f, "{}: {{:?}}, ", stringify!(#non_skipped_fields), #non_skipped_fields)?;
)*
f.write_str("}")
}
};
(pattern, format_logic)
}
}
};
Ok(quote! {
#pattern => {
#format_logic
}
})
}
fn generate_format_logic(
format_str: &str,
field_patterns: &[Ident],
fields: &[YoshiFieldOpts],
) -> TokenStream2 {
let mut format_args = Vec::new();
let placeholder_regex = REGEX_CACHE.get("display_placeholder").unwrap();
let mut current_format_str = format_str.to_string();
for cap in placeholder_regex.captures_iter(format_str) {
let placeholder = &cap[1];
if let Ok(idx) = placeholder.parse::<usize>() {
if idx < field_patterns.len() {
let field_ident = &field_patterns[idx];
let field_config = &fields[idx];
if field_config.skip {
current_format_str =
current_format_str.replace(&format!("{{{}}}", idx), "<skipped>");
} else if let Some(ref format_fn) = field_config.format_with {
let format_fn_ident = format_ident!("{}", format_fn);
format_args.push(quote! { #format_fn_ident(#field_ident) });
} else {
format_args.push(quote! { #field_ident });
}
} else {
format_args.push(quote! { "<invalid_index>" });
}
} else {
format_args.push(quote! { #placeholder });
}
}
if format_args.is_empty() && format_str.contains("{}") {
quote! {
write!(f, #format_str, #(#field_patterns),*)
}
} else {
quote! {
write!(f, #format_str, #(#format_args),*)
}
}
}
fn generate_format_logic_named(
format_str: &str,
field_patterns: &[&Ident],
fields: &[YoshiFieldOpts],
) -> TokenStream2 {
let placeholder_regex = REGEX_CACHE.get("display_placeholder").unwrap();
let mut format_args = Vec::new();
let field_configs: HashMap<&Ident, &YoshiFieldOpts> = fields
.iter()
.filter_map(|f| f.ident.as_ref().map(|ident| (ident, f)))
.collect();
for cap in placeholder_regex.captures_iter(format_str) {
let placeholder = &cap[1];
if let Some(&field_ident) = field_patterns.iter().find(|&&ident| ident == placeholder) {
if let Some(field_config) = field_configs.get(field_ident) {
if field_config.skip {
format_args.push(quote! { #field_ident = "<skipped>" });
} else if let Some(ref format_fn) = field_config.format_with {
let format_fn_ident = format_ident!("{}", format_fn);
format_args.push(quote! { #field_ident = #format_fn_ident(#field_ident) });
} else {
format_args.push(quote! { #field_ident = #field_ident });
}
} else {
format_args.push(quote! { #field_ident = #field_ident });
}
} else if placeholder == "source" {
if let Some(source_field_config) = fields.iter().find(|f| f.source) {
if let Some(source_ident) = &source_field_config.ident {
format_args.push(quote! { source = #source_ident });
} else {
format_args.push(quote! { source = "<unnamed_source>" });
}
} else {
format_args.push(quote! { source = "<no source>" });
}
} else {
format_args
.push(quote! { #placeholder = format!("<UNKNOWN_FIELD: {}>", #placeholder) });
}
}
quote! {
write!(f, #format_str, #(#format_args),*)
}
}
fn generate_error_impl(
opts: &YoshiErrorOpts,
variants: &[YoshiVariantOpts],
_validation: &mut ValidationContext,
) -> Result<TokenStream2> {
let enum_name = &opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let source_match_arms = variants.iter().map(generate_source_arm).collect::<Vec<_>>();
let doc_comment = "Generated Error trait implementation with enhanced source chaining and Rust 1.87 optimizations";
Ok(quote! {
#[doc = #doc_comment]
impl #impl_generics ::std::error::Error for #enum_name #ty_generics #where_clause {
fn source(&self) -> ::core::option::Option<&(dyn ::std::error::Error + 'static)> {
match self {
#(#source_match_arms)*
}
}
}
})
}
fn generate_source_arm(variant: &YoshiVariantOpts) -> TokenStream2 {
let variant_name = &variant.ident;
let enum_name = format_ident!("Self");
let source_field = variant.fields.fields.iter().find(|f| f.source);
match variant.fields.style {
Style::Unit => {
quote! { #enum_name::#variant_name => None, }
}
Style::Tuple => {
let fields = &variant.fields.fields;
let field_patterns: Vec<_> = fields
.iter()
.enumerate()
.map(|(i, field_opts)| {
if field_opts.source {
format_ident!("source")
} else {
format_ident!("_field_{}", i)
}
})
.collect();
if source_field.is_some() {
quote! {
#enum_name::#variant_name(#(#field_patterns),*) => Some(source),
}
} else {
quote! { #enum_name::#variant_name(#(#field_patterns),*) => None, }
}
}
Style::Struct => {
let fields = &variant.fields.fields;
if let Some(source) = source_field {
let source_ident = source.ident.as_ref().unwrap();
let other_fields: Vec<_> = fields
.iter()
.filter(|f| !f.source)
.map(|f| {
let ident = f.ident.as_ref().unwrap();
quote! { #ident: _ }
})
.collect();
quote! {
#enum_name::#variant_name { #source_ident, #(#other_fields),* } => Some(#source_ident),
}
} else {
let all_fields: Vec<_> = fields
.iter()
.map(|f| {
let ident = f.ident.as_ref().unwrap();
quote! { #ident: _ }
})
.collect();
quote! { #enum_name::#variant_name { #(#all_fields),* } => None, }
}
}
}
}
fn generate_yoshi_conversion(
opts: &YoshiErrorOpts,
variants: &[YoshiVariantOpts],
_validation: &mut ValidationContext,
) -> Result<TokenStream2> {
let enum_name = &opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let conversion_arms = variants
.iter()
.map(|variant| generate_yoshi_conversion_arm(variant, opts))
.collect::<Vec<_>>();
let doc_comment = "Generated conversion to Yoshi with intelligent kind mapping and enhanced metadata preservation";
Ok(quote! {
#[doc = #doc_comment]
impl #impl_generics ::core::convert::From<#enum_name #ty_generics> for ::yoshi_std::Yoshi #where_clause {
#[track_caller]
fn from(err: #enum_name #ty_generics) -> Self {
match err {
#(#conversion_arms)*
}
}
}
})
}
fn generate_yoshi_conversion_arm(
variant: &YoshiVariantOpts,
opts: &YoshiErrorOpts,
) -> TokenStream2 {
let variant_name = &variant.ident;
let enum_name = &opts.ident;
let yoshi_kind = if let Some(ref kind) = variant.kind {
if let Some(ref convert_fn) = variant.convert_with {
let convert_fn_ident = format_ident!("{}", convert_fn);
quote! { #convert_fn_ident(&err) }
} else {
generate_specific_yoshi_kind(kind, variant)
}
} else {
quote! {
::yoshi_std::Yoshi::foreign(err)
}
};
let pattern_fields = match variant.fields.style {
Style::Unit => quote! {},
Style::Tuple => {
let field_idents: Vec<_> = (0..variant.fields.fields.len())
.map(|i| format_ident!("field_{}", i))
.collect();
quote!(#(#field_idents),*)
}
Style::Struct => {
let field_idents: Vec<_> = variant
.fields
.fields
.iter()
.map(|f| f.ident.as_ref().unwrap())
.collect();
quote! { #(#field_idents),* }
}
};
let variant_pattern = match variant.fields.style {
Style::Unit => quote! { #enum_name::#variant_name },
Style::Tuple => quote! { #enum_name::#variant_name(#pattern_fields) },
Style::Struct => quote! { #enum_name::#variant_name { #pattern_fields } },
};
let mut yoshi_construction = quote! {
let mut yoshi_err = #yoshi_kind;
};
if let Some(ref context) = variant.context {
yoshi_construction.extend(quote! {
yoshi_err = yoshi_err.context(#context);
});
}
if let Some(ref suggestion) = variant.suggestion {
yoshi_construction.extend(quote! {
yoshi_err = yoshi_err.with_suggestion(#suggestion);
});
}
for field in &variant.fields.fields {
if let Some(ref context_key) = field.context {
if let Some(ref field_ident) = field.ident {
yoshi_construction.extend(quote! {
yoshi_err = yoshi_err.with_metadata(#context_key, format!("{}", #field_ident));
});
}
}
if field.shell {
if let Some(ref field_ident) = field.ident {
yoshi_construction.extend(quote! {
yoshi_err = yoshi_err.with_shell(#field_ident);
});
}
}
if let Some(ref suggestion) = field.suggestion {
yoshi_construction.extend(quote! {
yoshi_err = yoshi_err.with_suggestion(#suggestion);
});
}
}
if let Some(error_code) = variant.error_code {
let error_code_str = if let Some(ref prefix) = opts.error_code_prefix {
format!("{}-{:04}", prefix, error_code)
} else {
error_code.to_string()
};
yoshi_construction.extend(quote! {
yoshi_err = yoshi_err.with_metadata("error_code", #error_code_str);
});
}
yoshi_construction.extend(quote! {
yoshi_err
});
quote! {
#variant_pattern => {
#yoshi_construction
}
}
}
fn generate_specific_yoshi_kind(kind: &str, variant: &YoshiVariantOpts) -> TokenStream2 {
let source_field = variant
.fields
.fields
.iter()
.find(|f| f.source)
.and_then(|f| f.ident.as_ref());
let message_field = variant
.fields
.fields
.iter()
.find(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("message") || name.contains("msg")
})
})
.and_then(|f| f.ident.as_ref());
let variant_ident = &variant.ident;
match kind {
"Io" => {
if let Some(source_ident) = source_field {
quote! { ::yoshi_std::Yoshi::from(#source_ident) }
} else {
let msg = message_field
.map(|id| quote! { #id.to_string() })
.unwrap_or_else(|| quote! { format!("{}", stringify!(#variant_ident)) });
quote! { ::yoshi_std::Yoshi::from(#msg) }
}
}
"Network" => {
let message = message_field
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { format!("{}", stringify!(#variant_ident)).into() });
let source = source_field
.map(|id| quote! { Some(Box::new(::yoshi_std::Yoshi::from(#id))) })
.unwrap_or_else(|| quote! { None });
quote! {
::yoshi_std::Yoshi::new(::yoshi_std::YoshiKind::Network {
message: #message,
source: #source,
error_code: None,
})
}
}
"Config" => {
let message = message_field
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { format!("{}", stringify!(#variant_ident)).into() });
let source = source_field
.map(|id| quote! { Some(Box::new(::yoshi_std::Yoshi::from(#id))) })
.unwrap_or_else(|| quote! { None });
quote! {
::yoshi_std::Yoshi::new(::yoshi_std::YoshiKind::Config {
message: #message,
source: #source,
config_path: None,
})
}
}
"Validation" => {
let field_name = variant
.fields
.fields
.iter()
.find(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("field") || name.contains("name")
})
})
.and_then(|f| f.ident.as_ref())
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { "unknown".into() });
let message = message_field
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { format!("{}", stringify!(#variant_ident)).into() });
quote! {
::yoshi_std::Yoshi::new(::yoshi_std::YoshiKind::Validation {
field: #field_name,
message: #message,
expected: None,
actual: None,
})
}
}
"Internal" => {
let message = message_field
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { format!("{}", stringify!(#variant_ident)).into() });
let source = source_field
.map(|id| quote! { Some(Box::new(::yoshi_std::Yoshi::from(#id))) })
.unwrap_or_else(|| quote! { None });
quote! {
::yoshi_std::Yoshi::new(::yoshi_std::YoshiKind::Internal {
message: #message,
source: #source,
component: None,
})
}
}
"NotFound" => {
let resource_type = variant
.fields
.fields
.iter()
.find(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("resource") || name.contains("type")
})
})
.and_then(|f| f.ident.as_ref())
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { "resource".into() });
let identifier = variant
.fields
.fields
.iter()
.find(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("id") || name.contains("identifier") || name.contains("name")
})
})
.and_then(|f| f.ident.as_ref())
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { format!("{}", stringify!(#variant_ident)).into() });
quote! {
::yoshi_std::Yoshi::new(::yoshi_std::YoshiKind::NotFound {
resource_type: #resource_type,
identifier: #identifier,
search_locations: None,
})
}
}
"Timeout" => {
let operation = variant
.fields
.fields
.iter()
.find(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("operation") || name.contains("action")
})
})
.and_then(|f| f.ident.as_ref())
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { stringify!(#variant_ident).into() });
let duration = variant
.fields
.fields
.iter()
.find(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("duration")
|| name.contains("timeout")
|| name.contains("elapsed")
})
})
.and_then(|f| f.ident.as_ref())
.map(|id| quote! { #id })
.unwrap_or_else(|| quote! { ::core::time::Duration::from_secs(30) });
quote! {
::yoshi_std::Yoshi::new(::yoshi_std::YoshiKind::Timeout {
operation: #operation,
duration: #duration,
expected_max: None,
})
}
}
"ResourceExhausted" => {
let resource = variant
.fields
.fields
.iter()
.find(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("resource")
})
})
.and_then(|f| f.ident.as_ref())
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { "unknown".into() });
let limit = variant
.fields
.fields
.iter()
.find(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("limit")
})
})
.and_then(|f| f.ident.as_ref())
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { "unknown".into() });
let current = variant
.fields
.fields
.iter()
.find(|f| {
f.ident.as_ref().map_or(false, |id| {
let name = id.to_string().to_lowercase();
name.contains("current") || name.contains("usage")
})
})
.and_then(|f| f.ident.as_ref())
.map(|id| quote! { #id.to_string().into() })
.unwrap_or_else(|| quote! { "unknown".into() });
quote! {
::yoshi_std::Yoshi::new(::yoshi_std::YoshiKind::ResourceExhausted {
resource: #resource,
limit: #limit,
current: #current,
usage_percentage: None,
})
}
}
"Foreign" => {
if let Some(source_ident) = source_field {
quote! { ::yoshi_std::Yoshi::foreign(#source_ident) }
} else {
quote! { ::yoshi_std::Yoshi::from(format!("{}", stringify!(#variant_ident))) }
}
}
"Multiple" => {
quote! {
::yoshi_std::Yoshi::new(::yoshi_std::YoshiKind::Multiple {
errors: vec![::yoshi_std::Yoshi::from(format!("{}", stringify!(#variant_ident)))],
primary_index: Some(0),
})
}
}
_ => {
quote! { ::yoshi_std::Yoshi::from(format!("{}", stringify!(#variant_ident))) }
}
}
}
fn generate_additional_impls(
opts: &YoshiErrorOpts,
variants: &[YoshiVariantOpts],
validation: &mut ValidationContext,
) -> Result<TokenStream2> {
let enum_name = &opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let mut from_impls = TokenStream2::new();
for variant_opts in variants {
let variant_name = &variant_opts.ident;
match variant_opts.fields.style {
Style::Tuple => {
let fields = &variant_opts.fields.fields;
if fields.len() == 1 {
let field = &fields[0];
if field.from {
let field_ty = &field.ty;
let field_ident = format_ident!("value");
from_impls.extend(quote! {
#[doc = concat!("Automatically generated From implementation for ", stringify!(#field_ty), " -> ", stringify!(#enum_name), "::", stringify!(#variant_name))]
impl #impl_generics ::core::convert::From<#field_ty> for #enum_name #ty_generics #where_clause {
#[inline]
fn from(#field_ident: #field_ty) -> Self {
#enum_name::#variant_name(#field_ident)
}
}
});
if is_error_type(&field.ty) {
from_impls.extend(quote! {
#[doc = concat!("Enhanced conversion from ", stringify!(#field_ty), " with error context preservation")]
impl #impl_generics #enum_name #ty_generics #where_clause {
#[inline]
pub fn from_source(#field_ident: #field_ty) -> Self {
#enum_name::#variant_name(#field_ident)
}
}
});
}
}
} else if fields.iter().any(|f| f.from) {
let from_field_count = fields.iter().filter(|f| f.from).count();
if from_field_count > 0 {
validation.warning(format!(
"#[yoshi(from)] on multi-field tuple variant '{}::{}' is not supported. Consider using explicit constructor functions.",
enum_name, variant_name
));
}
}
}
Style::Struct => {
let fields = &variant_opts.fields.fields;
let from_fields: Vec<_> = fields.iter().filter(|f| f.from).collect();
match from_fields.len() {
1 => {
let from_field = from_fields[0];
let field_ty = &from_field.ty;
let field_name = from_field.ident.as_ref().unwrap();
let field_ident = format_ident!("value");
let other_fields: Vec<_> = fields
.iter()
.filter(|f| !f.from)
.map(|f| {
let name = f.ident.as_ref().unwrap();
quote! { #name: Default::default() }
})
.collect();
from_impls.extend(quote! {
#[doc = concat!("Automatically generated From implementation for ", stringify!(#field_ty), " -> ", stringify!(#enum_name), "::", stringify!(#variant_name))]
#[doc = "Other fields are initialized with Default::default()"]
impl #impl_generics ::core::convert::From<#field_ty> for #enum_name #ty_generics #where_clause
where
#(#other_fields: Default,)*
{
#[inline]
fn from(#field_ident: #field_ty) -> Self {
#enum_name::#variant_name {
#field_name: #field_ident,
#(#other_fields,)*
}
}
}
});
}
n if n > 1 => {
validation.warning(format!(
"#[yoshi(from)] on multiple fields in struct variant '{}::{}' is not supported. Use explicit constructor functions.",
enum_name, variant_name
));
}
_ => {
}
}
}
Style::Unit => {
if variant_opts.fields.fields.iter().any(|f| f.from) {
validation.error(
variant_name.span(),
"Unit variants cannot have #[yoshi(from)] fields",
);
}
}
}
}
if !from_impls.is_empty() {
from_impls.extend(generate_from_helper_methods(opts, variants));
}
Ok(from_impls)
}
fn generate_from_helper_methods(
opts: &YoshiErrorOpts,
variants: &[YoshiVariantOpts],
) -> TokenStream2 {
let enum_name = &opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let mut helper_methods = TokenStream2::new();
let variant_check_methods = variants.iter()
.filter(|variant| variant.fields.fields.iter().any(|f| f.from))
.map(|variant| {
let variant_name = &variant.ident;
let method_name = format_ident!("is_{}", variant_name.to_string().to_lowercase());
let pattern = generate_variant_pattern(variant);
quote! {
#[doc = concat!("Returns true if this error is a ", stringify!(#variant_name), " variant")]
#[inline]
pub fn #method_name(&self) -> bool {
matches!(self, #pattern)
}
}
});
let conversion_helpers = variants.iter()
.filter(|variant| variant.fields.fields.iter().any(|f| f.from))
.filter_map(|variant| {
let variant_name = &variant.ident;
let from_field = variant.fields.fields.iter().find(|f| f.from)?;
match variant.fields.style {
Style::Tuple if variant.fields.fields.len() == 1 => {
let field_ty = &from_field.ty;
let method_name = format_ident!("into_{}", variant_name.to_string().to_lowercase());
Some(quote! {
#[doc = concat!("Attempts to extract the inner ", stringify!(#field_ty), " from a ", stringify!(#variant_name), " variant")]
#[inline]
pub fn #method_name(self) -> ::core::result::Result<#field_ty, Self> {
match self {
#enum_name::#variant_name(value) => Ok(value),
other => Err(other),
}
}
})
}
Style::Struct => {
let field_name = from_field.ident.as_ref()?;
let field_ty = &from_field.ty;
let method_name = format_ident!("into_{}_field", field_name);
Some(quote! {
#[doc = concat!("Attempts to extract the ", stringify!(#field_name), " field from a ", stringify!(#variant_name), " variant")]
#[inline]
pub fn #method_name(self) -> ::core::result::Result<#field_ty, Self> {
match self {
#enum_name::#variant_name { #field_name, .. } => Ok(#field_name),
other => Err(other),
}
}
})
}
_ => None,
}
});
helper_methods.extend(quote! {
impl #impl_generics #enum_name #ty_generics #where_clause {
#(#variant_check_methods)*
#(#conversion_helpers)*
}
});
helper_methods
}
fn generate_variant_pattern(variant: &YoshiVariantOpts) -> TokenStream2 {
let variant_name = &variant.ident;
match variant.fields.style {
Style::Unit => quote! { Self::#variant_name },
Style::Tuple => quote! { Self::#variant_name(..) },
Style::Struct => quote! { Self::#variant_name { .. } },
}
}
fn generate_performance_monitoring(
opts: &YoshiErrorOpts,
variants: &[YoshiVariantOpts],
) -> Result<TokenStream2> {
let enum_name = &opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let variant_match_arms = variants.iter().map(|variant| {
let variant_name = &variant.ident;
let variant_pattern = generate_variant_pattern(variant);
let variant_str = variant_name.to_string();
quote! {
#variant_pattern => #variant_str,
}
});
let error_code_match_arms = variants.iter().map(|variant| {
let variant_pattern = generate_variant_pattern(variant);
let error_code = variant.error_code.unwrap_or(0);
quote! {
#variant_pattern => #error_code,
}
});
let severity_match_arms = variants.iter().map(|variant| {
let variant_pattern = generate_variant_pattern(variant);
let severity = variant.severity.unwrap_or(opts.default_severity);
quote! {
#variant_pattern => #severity,
}
});
let performance_metrics = quote! {
impl #impl_generics #enum_name #ty_generics #where_clause {
pub fn variant_name(&self) -> &'static str {
match self {
#(#variant_match_arms)*
}
}
pub fn error_code(&self) -> Option<u32> {
let code = match self {
#(#error_code_match_arms)*
};
if code == 0 { None } else { Some(code) }
}
pub fn severity(&self) -> Option<u8> {
Some(match self {
#(#severity_match_arms)*
})
}
#[cfg(feature = "performance-monitoring")]
pub fn performance_metrics(&self) -> PerformanceMetrics {
PerformanceMetrics {
error_type: stringify!(#enum_name),
variant_name: self.variant_name(),
creation_time: ::std::time::Instant::now(),
memory_usage: ::std::mem::size_of_val(self),
}
}
#[cfg(feature = "performance-monitoring")]
pub fn track_creation(&self) {
#[cfg(feature = "yoshi-std")]
if let Ok(metrics) = self.performance_metrics() {
eprintln!("Performance tracking: {} created at {:?}",
metrics.error_type, metrics.creation_time);
}
}
}
#[cfg(feature = "performance-monitoring")]
#[derive(Debug, Clone)]
pub struct PerformanceMetrics {
pub error_type: &'static str,
pub variant_name: &'static str,
pub creation_time: ::std::time::Instant,
pub memory_usage: usize,
}
};
Ok(performance_metrics)
}
fn generate_tracing_integration(
opts: &YoshiErrorOpts,
variants: &[YoshiVariantOpts],
) -> Result<TokenStream2> {
let enum_name = &opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let variant_match_arms = variants.iter().map(|variant| {
let variant_name = &variant.ident;
let variant_pattern = generate_variant_pattern(variant);
let variant_str = variant_name.to_string();
quote! {
#variant_pattern => #variant_str,
}
});
let tracing_impl = quote! {
impl #impl_generics #enum_name #ty_generics #where_clause {
#[cfg(feature = "tracing")]
pub fn create_span(&self) -> ::tracing::Span {
let variant_name = match self {
#(#variant_match_arms)*
};
::tracing::error_span!(
"yoshi_error",
error_type = stringify!(#enum_name),
variant = variant_name,
error_code = self.error_code().unwrap_or(0),
severity = self.severity().unwrap_or(50)
)
}
#[cfg(feature = "tracing")]
pub fn trace_error(&self) {
let _span = self.create_span().entered();
::tracing::error!(
message = %self,
error_chain = ?self.source(),
"Error occurred"
);
}
#[cfg(feature = "tracing")]
pub fn trace_with_context<F, R>(&self, f: F) -> R
where
F: FnOnce() -> R,
{
let _span = self.create_span().entered();
self.trace_error();
f()
}
}
};
Ok(tracing_impl)
}
fn generate_precise_capturing_traits(
opts: &YoshiErrorOpts,
_variants: &[YoshiVariantOpts],
) -> Result<TokenStream2> {
let enum_name = &opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let precise_capturing = quote! {
impl #impl_generics #enum_name #ty_generics #where_clause {
#[cfg(feature = "async")]
pub async fn async_convert<T>(self) -> ::core::result::Result<T, ::yoshi_std::Yoshi>
where
Self: Into<::yoshi_std::Yoshi> + Send + 'static,
T: Default + Send + 'static,
{
let yoshi_error: ::yoshi_std::Yoshi = self.into();
#[cfg(feature = "tokio")]
::tokio::task::yield_now().await;
Err(yoshi_error)
}
pub fn propagate_with_precision<E>(self) -> ::core::result::Result<(), E>
where
E: From<Self> + Send + Sync + 'static,
Self: Send + Sync + 'static,
{
Err(E::from(self))
}
}
};
Ok(precise_capturing)
}
fn generate_comprehensive_documentation(
opts: &YoshiErrorOpts,
variants: &[YoshiVariantOpts],
) -> Result<TokenStream2> {
let enum_name = &opts.ident;
let (impl_generics, ty_generics, where_clause) = opts.generics.split_for_impl();
let doc_prefix = opts.doc_prefix.as_deref().unwrap_or("Error");
let variant_match_arms = variants.iter().map(|variant| {
let variant_pattern = generate_variant_pattern(variant);
let custom_doc = variant.doc.as_deref().unwrap_or("");
let severity = variant.severity.unwrap_or(opts.default_severity);
let kind = variant.kind.as_deref().unwrap_or("General");
let doc_string = if custom_doc.is_empty() {
format!(
"Auto-generated documentation for {} variant (Severity: {}, Kind: {})",
variant.ident, severity, kind
)
} else {
format!("{} (Severity: {}, Kind: {})", custom_doc, severity, kind)
};
quote! {
#variant_pattern => #doc_string
}
});
let documentation = quote! {
impl #impl_generics #enum_name #ty_generics #where_clause {
pub fn documentation(&self) -> &'static str {
match self {
#(#variant_match_arms,)*
}
}
pub fn error_type_name() -> &'static str {
stringify!(#enum_name)
}
pub fn doc_prefix() -> &'static str {
#doc_prefix
}
}
};
Ok(documentation)
}