Skip to main content

json_tools_rs/
error.rs

1use crate::json_parser;
2
3/// Comprehensive error type for all JSON Tools operations with detailed information and suggestions
4///
5/// Each error variant includes:
6/// - Machine-readable error code (E001-E008) for programmatic handling
7/// - Human-readable message
8/// - Actionable suggestion
9/// - Source error (where applicable)
10#[derive(Debug)]
11#[non_exhaustive]
12pub enum JsonToolsError {
13    /// Error parsing JSON input with detailed context and suggestions
14    JsonParseError {
15        message: String,
16        suggestion: String,
17        source: json_parser::JsonError,
18    },
19
20    /// Error compiling or using regex patterns with helpful suggestions
21    RegexError {
22        message: String,
23        suggestion: String,
24        source: regex::Error,
25    },
26
27    /// Invalid replacement pattern configuration with detailed guidance
28    InvalidReplacementPattern { message: String, suggestion: String },
29
30    /// Invalid JSON structure for the requested operation
31    InvalidJsonStructure { message: String, suggestion: String },
32
33    /// Configuration error when operation mode is not set
34    ConfigurationError { message: String, suggestion: String },
35
36    /// Error processing batch item with detailed context
37    BatchProcessingError {
38        index: usize,
39        message: String,
40        suggestion: String,
41        source: Box<JsonToolsError>,
42    },
43
44    /// Input validation error with helpful guidance
45    InputValidationError { message: String, suggestion: String },
46
47    /// Serialization error when converting results back to JSON
48    SerializationError {
49        message: String,
50        suggestion: String,
51        source: json_parser::JsonError,
52    },
53}
54
55impl std::fmt::Display for JsonToolsError {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self {
58            Self::JsonParseError {
59                message,
60                suggestion,
61                ..
62            } => {
63                write!(
64                    f,
65                    "[E001] JSON parsing failed: {message}\n\u{1f4a1} Suggestion: {suggestion}"
66                )
67            }
68            Self::RegexError {
69                message,
70                suggestion,
71                ..
72            } => {
73                write!(
74                    f,
75                    "[E002] Regex pattern error: {message}\n\u{1f4a1} Suggestion: {suggestion}"
76                )
77            }
78            Self::InvalidReplacementPattern {
79                message,
80                suggestion,
81            } => {
82                write!(f, "[E003] Invalid replacement pattern: {message}\n\u{1f4a1} Suggestion: {suggestion}")
83            }
84            Self::InvalidJsonStructure {
85                message,
86                suggestion,
87            } => {
88                write!(
89                    f,
90                    "[E004] Invalid JSON structure: {message}\n\u{1f4a1} Suggestion: {suggestion}"
91                )
92            }
93            Self::ConfigurationError {
94                message,
95                suggestion,
96            } => {
97                write!(f, "[E005] Operation mode not configured: {message}\n\u{1f4a1} Suggestion: {suggestion}")
98            }
99            Self::BatchProcessingError {
100                index,
101                message,
102                suggestion,
103                ..
104            } => {
105                write!(f, "[E006] Batch processing failed at index {index}: {message}\n\u{1f4a1} Suggestion: {suggestion}")
106            }
107            Self::InputValidationError {
108                message,
109                suggestion,
110            } => {
111                write!(
112                    f,
113                    "[E007] Input validation failed: {message}\n\u{1f4a1} Suggestion: {suggestion}"
114                )
115            }
116            Self::SerializationError {
117                message,
118                suggestion,
119                ..
120            } => {
121                write!(f, "[E008] JSON serialization failed: {message}\n\u{1f4a1} Suggestion: {suggestion}")
122            }
123        }
124    }
125}
126
127impl std::error::Error for JsonToolsError {
128    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
129        match self {
130            Self::JsonParseError { source, .. } => Some(source),
131            Self::RegexError { source, .. } => Some(source),
132            Self::BatchProcessingError { source, .. } => Some(source.as_ref()),
133            Self::SerializationError { source, .. } => Some(source),
134            _ => None,
135        }
136    }
137}
138
139impl JsonToolsError {
140    /// Get machine-readable error code for programmatic handling
141    ///
142    /// # Examples
143    /// ```
144    /// use json_tools_rs::{JSONTools, JsonToolsError};
145    ///
146    /// let result = JSONTools::new().flatten().execute("invalid json");
147    /// if let Err(e) = result {
148    ///     match e.error_code() {
149    ///         "E001" => println!("JSON parsing error"),
150    ///         "E005" => println!("Configuration error"),
151    ///         _ => println!("Other error"),
152    ///     }
153    /// }
154    /// ```
155    pub fn error_code(&self) -> &'static str {
156        match self {
157            JsonToolsError::JsonParseError { .. } => "E001",
158            JsonToolsError::RegexError { .. } => "E002",
159            JsonToolsError::InvalidReplacementPattern { .. } => "E003",
160            JsonToolsError::InvalidJsonStructure { .. } => "E004",
161            JsonToolsError::ConfigurationError { .. } => "E005",
162            JsonToolsError::BatchProcessingError { .. } => "E006",
163            JsonToolsError::InputValidationError { .. } => "E007",
164            JsonToolsError::SerializationError { .. } => "E008",
165        }
166    }
167
168    /// Create a JSON parse error with helpful suggestions
169    #[cold] // Error paths are rarely taken; keep out of hot code
170    #[inline(never)]
171    pub fn json_parse_error(source: json_parser::JsonError) -> Self {
172        let suggestion = "Verify your JSON syntax using a JSON validator. Common issues include: missing quotes around keys or values, trailing commas, unescaped characters, incomplete JSON (missing closing braces or brackets), or invalid escape sequences.";
173
174        JsonToolsError::JsonParseError {
175            message: source.to_string(),
176            suggestion: suggestion.into(),
177            source,
178        }
179    }
180
181    /// Create a regex error with helpful suggestions
182    #[cold] // Error paths are rarely taken; keep out of hot code
183    #[inline(never)]
184    pub fn regex_error(source: regex::Error) -> Self {
185        let suggestion = match source {
186            regex::Error::Syntax(_) =>
187                "Check your regex pattern syntax. Use online regex testers to validate your pattern. Remember to escape special characters like '.', '*', '+', '?', etc.",
188            regex::Error::CompiledTooBig(_) =>
189                "Your regex pattern is too complex. Try simplifying it or breaking it into multiple smaller patterns.",
190            _ => "Verify your regex pattern is valid. Use tools like regex101.com to test and debug your pattern.",
191        };
192
193        JsonToolsError::RegexError {
194            message: source.to_string(),
195            suggestion: suggestion.into(),
196            source,
197        }
198    }
199
200    /// Create an invalid replacement pattern error
201    #[cold] // Error paths are rarely taken; keep out of hot code
202    #[inline(never)]
203    pub fn invalid_replacement_pattern(message: impl Into<String>) -> Self {
204        let msg = message.into();
205        let suggestion = if msg.contains("pairs") {
206            "Replacement patterns must be provided in pairs (pattern, replacement). Ensure you have an even number of arguments."
207        } else if msg.contains("regex") {
208            "Patterns are matched literally (exact substring) by default. Wrap a pattern in r'...' to use it as a regex instead, e.g. r'^user_' to match keys starting with 'user_'."
209        } else {
210            "Check your replacement pattern configuration. Patterns should be in the format: pattern1, replacement1, pattern2, replacement2, etc."
211        };
212
213        JsonToolsError::InvalidReplacementPattern {
214            message: msg,
215            suggestion: suggestion.into(),
216        }
217    }
218
219    /// Create an invalid JSON structure error
220    #[cold] // Error paths are rarely taken; keep out of hot code
221    #[inline(never)]
222    pub fn invalid_json_structure(message: impl Into<String>) -> Self {
223        let msg = message.into();
224        let suggestion = if msg.contains("unflatten") {
225            "For unflattening, ensure your JSON is a flat object with dot-separated keys like {'user.name': 'John', 'user.age': 30}."
226        } else if msg.contains("object") {
227            "The operation requires a JSON object ({}), but received a different type. Check that your input is a valid JSON object."
228        } else {
229            "Verify that your JSON structure is compatible with the requested operation. Flattening works on nested objects/arrays, unflattening works on flat objects."
230        };
231
232        JsonToolsError::InvalidJsonStructure {
233            message: msg,
234            suggestion: suggestion.into(),
235        }
236    }
237
238    /// Create a configuration error
239    #[cold] // Error paths are rarely taken; keep out of hot code
240    #[inline(never)]
241    pub fn configuration_error(message: impl Into<String>) -> Self {
242        JsonToolsError::ConfigurationError {
243            message: message.into(),
244            suggestion: "Call .flatten() or .unflatten() on your JSONTools instance before calling .execute() to set the operation mode.".into(),
245        }
246    }
247
248    /// Create a batch processing error
249    #[cold] // Error paths are rarely taken; keep out of hot code
250    #[inline(never)]
251    pub fn batch_processing_error(index: usize, source: JsonToolsError) -> Self {
252        JsonToolsError::BatchProcessingError {
253            index,
254            message: format!("Failed to process item at index {}", index),
255            suggestion: "Check the JSON at the specified index. All items in a batch must be valid JSON strings or objects.".to_string(),
256            source: Box::new(source),
257        }
258    }
259
260    /// Create an input validation error
261    #[cold] // Error paths are rarely taken; keep out of hot code
262    #[inline(never)]
263    pub fn input_validation_error(message: impl Into<String>) -> Self {
264        let msg = message.into();
265        let suggestion = if msg.contains("type") {
266            "Ensure your input is a valid JSON string, Python dict, or list of JSON strings/dicts."
267        } else if msg.contains("empty") {
268            "Provide non-empty input for processing."
269        } else {
270            "Check that your input format matches the expected type for the operation."
271        };
272
273        JsonToolsError::InputValidationError {
274            message: msg,
275            suggestion: suggestion.to_string(),
276        }
277    }
278
279    /// Create a serialization error
280    #[cold] // Error paths are rarely taken; keep out of hot code
281    #[inline(never)]
282    pub fn serialization_error(source: json_parser::JsonError) -> Self {
283        JsonToolsError::SerializationError {
284            message: source.to_string(),
285            suggestion: "This is likely an internal error. The processed data couldn't be serialized back to JSON. Please report this issue.".to_string(),
286            source,
287        }
288    }
289}
290
291// Automatic conversion from json_parser::JsonError
292impl From<json_parser::JsonError> for JsonToolsError {
293    fn from(error: json_parser::JsonError) -> Self {
294        JsonToolsError::json_parse_error(error)
295    }
296}
297
298// Automatic conversion from regex::Error
299impl From<regex::Error> for JsonToolsError {
300    fn from(error: regex::Error) -> Self {
301        JsonToolsError::regex_error(error)
302    }
303}
304
305// Automatic conversion from pyo3::PyErr (needed for closures mixing PyResult and JsonToolsError)
306#[cfg(feature = "python")]
307impl From<pyo3::PyErr> for JsonToolsError {
308    fn from(err: pyo3::PyErr) -> Self {
309        JsonToolsError::configuration_error(format!("Python error: {err}"))
310    }
311}
312
313// Automatic conversion from jni::errors::Error (needed for closures mixing JNI Results and JsonToolsError)
314#[cfg(feature = "jvm")]
315impl From<jni::errors::Error> for JsonToolsError {
316    fn from(err: jni::errors::Error) -> Self {
317        JsonToolsError::configuration_error(format!("JNI error: {err}"))
318    }
319}