1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use crate::analysis::expect_analysis::ExpectMode;
use crate::field::FieldProcessingContext;
use crate::field::info::RustFieldInfo;
/// Error handling mode that consolidates separate strategies for each error handling approach.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ErrorMode {
/// No special error handling - may panic on missing values
None,
/// Use .expect() with descriptive panic message
Panic,
/// Generate error handling code that returns Result
Error,
/// Use default value - None means Default::default(), Some(fn) means custom function
Default(Option<String>),
}
impl ErrorMode {
/// Create ErrorMode from existing field analysis
pub fn from_field_context(ctx: &FieldProcessingContext, rust: &RustFieldInfo) -> Self {
// Priority 1: Default takes precedence over everything (including error_type)
if rust.has_default || ctx.default_fn.is_some() {
return Self::Default(ctx.default_fn.clone());
}
// Priority 2: When error_type is specified, it overrides panic behavior
if ctx.struct_level_error_type.is_some() {
// Check if we have any error function available
if ctx.has_error_fn() {
return Self::Error; // Force error mode even if expect(panic) was specified
} else {
// Error case - error_type specified but no error function
panic!(
"Field '{}': when struct-level 'error_type' is specified, \
either provide field-level 'error_fn' or struct-level 'error_fn' as fallback. \
Example: #[protto(error_type = MyError, error_fn = \"MyError::missing_field\")]",
rust.field_name
);
}
}
// Priority 3: Explicit expect modes (only when no error_type override)
if rust.expect_mode == ExpectMode::Panic {
return Self::Panic;
} else if rust.expect_mode == ExpectMode::Error {
return Self::Error;
}
// Priority 4: Handle remaining fallback patterns
if Self::custom_functions_need_default_panic(rust) {
Self::Panic
} else {
Self::None
}
}
/// Determine if custom functions should get panic behavior by default
/// This maintains compatibility with the old system's behavior
fn custom_functions_need_default_panic(rust: &RustFieldInfo) -> bool {
// Custom bidirectional functions on complex types got panic behavior in old system
rust.from_proto_fn.is_some()
&& rust.to_proto_fn.is_some() // Bidirectional
&& !rust.is_primitive // Complex type
&& !rust.is_option // Not already optional
}
// /// Generate the appropriate error expression based on the error mode and context
// pub fn generate_error_expression(
// &self,
// _field_name: &syn::Ident,
// proto_field_name: &str,
// ctx: &FieldProcessingContext,
// ) -> proc_macro2::TokenStream {
// match self {
// ErrorMode::Error => {
// if let Some(field_error_fn) = &ctx.field_level_error_fn() {
// // Priority 1: Field-level error_fn
// let error_fn: syn::Path = syn::parse_str(field_error_fn).unwrap();
// quote! { #error_fn(stringify!(#proto_field_name)) }
// } else if let Some(struct_error_fn) = ctx.struct_level_error_fn {
// // Priority 2: Struct-level error_fn (when error_type is specified)
// let error_fn: syn::Path = syn::parse_str(struct_error_fn).unwrap();
// quote! { #error_fn(stringify!(#proto_field_name)) }
// } else {
// // Priority 3: Default error generation (only when no error_type)
// let default_error_name = &ctx.default_error_ident();
// quote! {
// #default_error_name::MissingField(stringify!(#proto_field_name).to_string())
// }
// }
// }
// ErrorMode::Panic => {
// quote! { panic!("Missing required field: {}", stringify!(#proto_field_name)) }
// }
// ErrorMode::Default(default_fn) => {
// if let Some(default_fn_name) = default_fn {
// let default_fn: syn::Path = syn::parse_str(default_fn_name).unwrap();
// quote! { #default_fn() }
// } else {
// quote! { Default::default() }
// }
// }
// ErrorMode::None => {
// // This shouldn't happen for operations that need error expressions
// // But provide a fallback
// quote! { Default::default() }
// }
// }
// }
/// Check if this mode requires error handling infrastructure
#[cfg(test)]
pub fn requires_error_handling(&self) -> bool {
matches!(self, Self::Error)
}
/// Check if this mode uses default values
#[cfg(test)]
pub fn uses_default(&self) -> bool {
matches!(self, Self::Default(_))
}
/// Check if this mode will panic on missing values
#[cfg(test)]
pub fn will_panic(&self) -> bool {
matches!(self, Self::Panic | Self::None)
}
/// Get the default function name if specified
#[cfg(test)]
pub fn default_function(&self) -> Option<&str> {
match self {
Self::Default(Some(fn_name)) => Some(fn_name),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_mode_detection() {
assert!(ErrorMode::Error.requires_error_handling());
assert!(!ErrorMode::None.requires_error_handling());
assert!(ErrorMode::Default(None).uses_default());
assert!(ErrorMode::Default(Some("my_fn".to_string())).uses_default());
assert!(!ErrorMode::Panic.uses_default());
assert!(ErrorMode::Panic.will_panic());
assert!(ErrorMode::None.will_panic());
assert!(!ErrorMode::Error.will_panic());
}
#[test]
fn test_default_function_extraction() {
let mode_with_fn = ErrorMode::Default(Some("custom_default".to_string()));
assert_eq!(mode_with_fn.default_function(), Some("custom_default"));
let mode_without_fn = ErrorMode::Default(None);
assert_eq!(mode_without_fn.default_function(), None);
let panic_mode = ErrorMode::Panic;
assert_eq!(panic_mode.default_function(), None);
}
}