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}