aimx/values/errata.rs
1//! Expression and evaluation error representation.
2//!
3//! This module defines the `Errata` enum that represents errors encountered during
4//! expression parsing and evaluation in the AIMX expression engine. `Errata` captures
5//! error information at different levels of detail, from simple error messages to
6//! full diagnostic information including the problematic formula and location.
7//!
8//! The `Errata` type is used throughout the AIMX expression engine to provide
9//! detailed error information that helps users understand what went wrong and
10//! where the error occurred in their expressions.
11//!
12//! # Examples
13//!
14//! Creating different levels of error information:
15//!
16//! ```
17//! use aimx::values::Errata;
18//!
19//! // Basic error with just a reason
20//! let basic_error = Errata::One { reason: "Syntax Error".to_string() };
21//!
22//! // Error with formula context
23//! let formula_error = Errata::Two {
24//! reason: "Division by zero".to_string(),
25//! formula: "10 / 0".to_string(),
26//! };
27//!
28//! // Full diagnostic error
29//! let diagnostic_error = Errata::Three {
30//! reason: "Unexpected token".to_string(),
31//! formula: "1 + * 2".to_string(),
32//! location: "* 2".to_string(),
33//! };
34//! ```
35
36use crate::{
37 ContextLike,
38 Expression,
39 ExpressionLike,
40 Value,
41 Writer,
42};
43use std::fmt;
44use anyhow::Result;
45
46/// Represents expression and evaluation errors with varying levels of detail.
47///
48/// The `Errata` enum captures error information at different diagnostic levels:
49/// - `One`: Basic error reason only
50/// - `Two`: Error reason and the problematic formula or expression
51/// - `Three`: Full diagnostic information including reason, formula, and location
52///
53/// This enum is used throughout the AIMX expression engine to provide
54/// detailed error information that helps users understand what went wrong
55/// and where the error occurred in their expressions.
56///
57/// # Examples
58///
59/// Creating different levels of error information:
60///
61/// ```rust
62/// use aimx::values::Errata;
63///
64/// // Basic error with just a reason
65/// let basic_error = Errata::One { reason: "Syntax Error".to_string() };
66///
67/// // Error with formula context
68/// let formula_error = Errata::Two {
69/// reason: "Division by zero".to_string(),
70/// formula: "10 / 0".to_string(),
71/// };
72///
73/// // Full diagnostic error
74/// let diagnostic_error = Errata::Three {
75/// reason: "Unexpected token".to_string(),
76/// formula: "1 + * 2".to_string(),
77/// location: "* 2".to_string(),
78/// };
79/// ```
80///
81/// # Display Format
82///
83/// When displayed, `Errata` produces formatted output showing:
84/// - The error reason
85/// - The problematic formula or expression
86/// - Location indicators (arrows pointing to the problematic area)
87///
88/// Example output for a diagnostic error:
89/// ```text
90/// Unexpected token
91/// 1 + * 2
92/// ^
93/// ```
94#[derive(Debug, Clone, PartialEq)]
95pub enum Errata {
96 /// Basic error with only a reason
97 One{reason: String},
98 /// Error with reason and formula context
99 Two{reason: String, formula: String},
100 /// Full diagnostic error with reason, formula, and location
101 Three{reason: String, formula: String, location: String},
102}
103
104impl Errata {
105 /// Creates a new `Value::Errata` with only a reason.
106 ///
107 /// This is the simplest form of error representation, containing only
108 /// the error message without additional context.
109 ///
110 /// # Arguments
111 ///
112 /// * `reason` - The error message or description
113 ///
114 /// # Returns
115 ///
116 /// Returns a `Value::Errata` containing the basic error information.
117 ///
118 /// # Examples
119 ///
120 /// ```rust
121 /// use aimx::{Value, values::Errata};
122 ///
123 /// let error_value = Errata::new_reason("Syntax Error".to_string());
124 /// assert!(matches!(error_value, Value::Errata(_)));
125 /// ```
126 pub fn new_reason(reason: String) -> Value {
127 Value::Errata(Errata::One{reason})
128 }
129
130 /// Creates a new `Value::Errata` with a reason and formula context.
131 ///
132 /// This provides more context by including the problematic formula
133 /// along with the error message.
134 ///
135 /// # Arguments
136 ///
137 /// * `reason` - The error message or description
138 /// * `formula` - The problematic formula or expression
139 ///
140 /// # Returns
141 ///
142 /// Returns a `Value::Errata` containing the error with formula context.
143 ///
144 /// # Examples
145 ///
146 /// ```rust
147 /// use aimx::{Value, values::Errata};
148 ///
149 /// let error_value = Errata::new_reason_formula(
150 /// "Division by zero".to_string(),
151 /// "10 / 0".to_string()
152 /// );
153 /// assert!(matches!(error_value, Value::Errata(_)));
154 /// ```
155 pub fn new_reason_formula(reason: String, formula: String) -> Value {
156 Value::Errata(Errata::Two{reason, formula})
157 }
158
159 /// Creates a new `Value::Errata` with full diagnostic information.
160 ///
161 /// This provides the most detailed error information including the reason,
162 /// the problematic formula, and the specific location within the formula.
163 ///
164 /// # Arguments
165 ///
166 /// * `reason` - The error message or description
167 /// * `formula` - The problematic formula or expression
168 /// * `location` - The specific location within the formula where the error occurred
169 ///
170 /// # Returns
171 ///
172 /// Returns a `Value::Errata` containing full diagnostic error information.
173 ///
174 /// # Examples
175 ///
176 /// ```rust
177 /// use aimx::{Value, values::Errata};
178 ///
179 /// let error_value = Errata::new_reason_formula_location(
180 /// "Unexpected token".to_string(),
181 /// "1 + * 2".to_string(),
182 /// "* 2".to_string()
183 /// );
184 /// assert!(matches!(error_value, Value::Errata(_)));
185 /// ```
186 pub fn new_reason_formula_location(reason: String, formula: String, location: String) -> Value {
187 Value::Errata(Errata::Three{reason, formula, location})
188 }
189
190 /// Creates a new `Expression::Errata` with a reason and expression context.
191 ///
192 /// This is similar to `new_reason_formula` but creates an `Expression` variant
193 /// instead of a `Value` variant.
194 ///
195 /// # Arguments
196 ///
197 /// * `reason` - The error message or description
198 /// * `expression` - The problematic expression
199 ///
200 /// # Returns
201 ///
202 /// Returns an `Expression::Errata` containing the error with expression context.
203 ///
204 /// # Examples
205 ///
206 /// ```rust
207 /// use aimx::{Expression, values::Errata};
208 ///
209 /// let error_expression = Errata::new_reason_expression(
210 /// "Undefined variable".to_string(),
211 /// "undefined_var".to_string()
212 /// );
213 /// assert!(matches!(error_expression, Expression::Errata(_)));
214 /// ```
215 pub fn new_reason_expression(reason: String, expression: String) -> Expression {
216 Expression::Errata(Errata::Two{reason, formula: expression})
217 }
218
219 /// Creates a new `Expression::Errata` with full diagnostic information.
220 ///
221 /// This provides the most detailed error information for expression errors,
222 /// including the reason, the problematic expression, and the specific location.
223 ///
224 /// # Arguments
225 ///
226 /// * `reason` - The error message or description
227 /// * `expression` - The problematic expression
228 /// * `location` - The specific location within the expression where the error occurred
229 ///
230 /// # Returns
231 ///
232 /// Returns an `Expression::Errata` containing full diagnostic error information.
233 ///
234 /// # Examples
235 ///
236 /// ```rust
237 /// use aimx::{Expression, values::Errata};
238 ///
239 /// let error_expression = Errata::new_reason_expression_location(
240 /// "Syntax error".to_string(),
241 /// "1 + ".to_string(),
242 /// "+ ".to_string()
243 /// );
244 /// assert!(matches!(error_expression, Expression::Errata(_)));
245 /// ```
246 pub fn new_reason_expression_location(reason: String, expression: String, location: String) -> Expression {
247 Expression::Errata(Errata::Three{reason, formula: expression, location})
248 }
249
250 /// Returns the error reason string.
251 ///
252 /// This method extracts the error message from any `Errata` variant.
253 ///
254 /// # Returns
255 ///
256 /// Returns a string slice containing the error reason.
257 ///
258 /// # Examples
259 ///
260 /// ```rust
261 /// use aimx::values::Errata;
262 ///
263 /// let errata = Errata::One { reason: "Syntax Error".to_string() };
264 /// assert_eq!(errata.reason(), "Syntax Error");
265 /// ```
266 pub fn reason(&self) -> &str {
267 match self {
268 Errata::Three{reason, formula: _, location: _} => reason,
269 Errata::Two{reason, formula: _} => reason,
270 Errata::One{reason} => reason,
271 }
272 }
273
274 /// Returns the problematic formula or expression.
275 ///
276 /// For `Errata::Two` and `Errata::Three` variants, returns the formula
277 /// that caused the error. For `Errata::One`, returns an empty string.
278 ///
279 /// # Returns
280 ///
281 /// Returns a string slice containing the problematic formula.
282 ///
283 /// # Examples
284 ///
285 /// ```rust
286 /// use aimx::values::Errata;
287 ///
288 /// let errata = Errata::Two {
289 /// reason: "Division by zero".to_string(),
290 /// formula: "10 / 0".to_string(),
291 /// };
292 /// assert_eq!(errata.formula(), "10 / 0");
293 /// ```
294 pub fn formula(&self) -> &str {
295 match self {
296 Errata::Three{reason: _, formula, location: _} => formula,
297 Errata::Two{reason: _, formula} => formula,
298 Errata::One{reason: _} => &"",
299 }
300 }
301
302 /// Returns the specific location within the formula where the error occurred.
303 ///
304 /// Only `Errata::Three` variants contain location information.
305 /// For other variants, returns an empty string.
306 ///
307 /// # Returns
308 ///
309 /// Returns a string slice containing the error location.
310 ///
311 /// # Examples
312 ///
313 /// ```rust
314 /// use aimx::values::Errata;
315 ///
316 /// let errata = Errata::Three {
317 /// reason: "Unexpected token".to_string(),
318 /// formula: "1 + * 2".to_string(),
319 /// location: "* 2".to_string(),
320 /// };
321 /// assert_eq!(errata.location(), "* 2");
322 /// ```
323 pub fn location(&self) -> &str {
324 match self {
325 Errata::Three{reason: _, formula: _, location} => location,
326 _ => &"",
327 }
328 }
329}
330
331impl ExpressionLike for Errata {
332 /// Evaluates the error expression, returning the error as a value.
333 ///
334 /// Since `Errata` represents error information rather than an evaluatable
335 /// expression, this method simply returns the error wrapped in a `Value::Errata`.
336 ///
337 /// # Arguments
338 ///
339 /// * `_context` - The evaluation context (unused for errors)
340 ///
341 /// # Returns
342 ///
343 /// Returns `Ok(Value::Errata(self.clone()))` containing the error information.
344 ///
345 /// # Examples
346 ///
347 /// ```rust
348 /// use aimx::{values::Errata, ExpressionLike, Context};
349 ///
350 /// let errata = Errata::One { reason: "Test error".to_string() };
351 /// let mut context = Context::new();
352 /// let result = errata.evaluate(&mut context).unwrap();
353 /// assert!(matches!(result, aimx::Value::Errata(_)));
354 /// ```
355 fn evaluate(&self, _context: &mut dyn ContextLike) -> Result<Value> {
356 Ok(Value::Errata(self.clone()))
357 }
358
359 /// Writes the error information to the provided writer.
360 ///
361 /// This method delegates to the writer's `print_errata` method to format
362 /// the error information according to the writer's mode (string, sanitized, formula).
363 ///
364 /// # Arguments
365 ///
366 /// * `writer` - The writer to write the error information to
367 ///
368 /// # Examples
369 ///
370 /// ```rust
371 /// use aimx::{values::Errata, ExpressionLike, Writer};
372 ///
373 /// let errata = Errata::Two {
374 /// reason: "Division by zero".to_string(),
375 /// formula: "10 / 0".to_string(),
376 /// };
377 /// let mut writer = Writer::stringizer();
378 /// errata.write(&mut writer);
379 /// let output = writer.finish();
380 /// assert!(output.contains("Division by zero"));
381 /// assert!(output.contains("10 / 0"));
382 /// ```
383 fn write(&self, writer: &mut Writer) {
384 let (reason, formula, location) = match self {
385 Errata::Three{reason, formula, location}
386 => (reason, formula, location),
387 Errata::Two{reason, formula}
388 => (reason, formula, &"".to_owned()),
389 Errata::One{reason}
390 => (reason, &"".to_owned(), &"".to_owned()),
391 };
392 writer.print_errata(reason, formula, location);
393 }
394
395 /// Converts the error to a sanitized string representation.
396 ///
397 /// This method produces a string with special characters escaped,
398 /// making it safe for JSON-compatible or safely quoted output.
399 ///
400 /// # Returns
401 ///
402 /// Returns a sanitized string representation of the error.
403 ///
404 /// # Examples
405 ///
406 /// ```rust
407 /// use aimx::{values::Errata, ExpressionLike};
408 ///
409 /// let errata = Errata::One { reason: "Error <message>".to_string() };
410 /// let sanitized = errata.to_sanitized();
411 /// assert!(sanitized.contains("Error <message>"));
412 /// ```
413 fn to_sanitized(&self) -> String {
414 let mut writer = Writer::sanitizer();
415 self.write(&mut writer);
416 writer.finish()
417 }
418
419 /// Converts the error to a formula string representation.
420 ///
421 /// This method produces a string with proper quoting and escaping
422 /// for use in formulas. For `Errata`, this typically returns just
423 /// the original formula or expression that caused the error.
424 ///
425 /// # Returns
426 ///
427 /// Returns a formula string representation of the error.
428 ///
429 /// # Examples
430 ///
431 /// ```rust
432 /// use aimx::{values::Errata, ExpressionLike};
433 ///
434 /// let errata = Errata::Two {
435 /// reason: "Division by zero".to_string(),
436 /// formula: "10 / 0".to_string(),
437 /// };
438 /// let formula = errata.to_formula();
439 /// assert_eq!(formula, "10 / 0");
440 /// ```
441 fn to_formula(&self) -> String {
442 let mut writer = Writer::formulizer();
443 self.write(&mut writer);
444 writer.finish()
445 }
446}
447
448impl fmt::Display for Errata {
449 /// Formats the error for display using the default string representation.
450 ///
451 /// This implementation uses the stringizer writer mode to produce a
452 /// human-readable representation of the error, including the reason,
453 /// formula, and location indicators.
454 ///
455 /// # Arguments
456 ///
457 /// * `f` - The formatter to write to
458 ///
459 /// # Returns
460 ///
461 /// A `fmt::Result` indicating success or failure of the formatting operation.
462 ///
463 /// # Examples
464 ///
465 /// ```rust
466 /// use aimx::values::Errata;
467 ///
468 /// let errata = Errata::Three {
469 /// reason: "Unexpected token".to_string(),
470 /// formula: "1 + * 2".to_string(),
471 /// location: "* 2".to_string(),
472 /// };
473 /// let display_string = format!("{}", errata);
474 /// assert!(display_string.contains("Unexpected token"));
475 /// assert!(display_string.contains("1 + * 2"));
476 /// assert!(display_string.contains("^"));
477 /// ```
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 let mut writer = Writer::stringizer();
480 self.write(&mut writer);
481 write!(f, "{}", writer.finish())
482 }
483}