aimx/values/
errata.rs

1//! Expression and evaluation error metadata.
2//!
3//! `Errata` and `CriticalError` represent failures produced by parsing and
4//! evaluation. They are embedded in [`Value`] and [`Expression`] and propagated
5//! through the engine so callers can surface diagnostic details without panics.
6
7use crate::{
8    aim::{PrintMode, Writer, WriterLike},
9    expressions::Expression,
10    values::Value,
11};
12use std::{
13    fmt,
14    sync::Arc,
15};
16
17/// Structured expression and evaluation error metadata.
18///
19/// Variants capture increasing diagnostic detail used by [`Value::Errata`] and
20/// [`Expression::Errata`] to propagate failures without panics.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum Errata {
23    /// Error with reason only
24    One { reason: Arc<str> },
25    /// Error with reason and formula context
26    Two { reason: Arc<str>, formula: Arc<str> },
27    /// Full diagnostic error with reason, formula, and location
28    Three {
29        reason: Arc<str>,
30        formula: Arc<str>,
31        location: Arc<str>,
32    },
33    /// Critical error marker used for HITL / suspension semantics
34    Critical { reason: Arc<str>, formula: Arc<str> },
35}
36
37/// Critical error used to signal HITL/suspension semantics.
38///
39/// Used where a hard stop is required; typically mapped into `Errata::Critical`.
40#[derive(Debug)]
41pub struct CriticalError {
42    pub reason: Arc<str>,
43}
44
45impl std::fmt::Display for CriticalError {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        write!(f, "{}", self.reason)
48    }
49}
50
51impl std::error::Error for CriticalError {}
52
53impl CriticalError {
54    pub fn new(reason: Arc<str>) -> Self {
55        Self { reason }
56    }
57}
58
59impl Errata {
60    /// Creates a `Value::Errata` with only a reason.
61    pub fn new_reason(reason: Arc<str>) -> Value {
62        Value::Errata(Errata::One { reason })
63    }
64
65    /// Creates a `Value::Errata` with a reason and formula context.
66    pub fn new_reason_formula(reason: Arc<str>, formula: Arc<str>) -> Value {
67        Value::Errata(Errata::Two { reason, formula })
68    }
69
70    /// Creates a `Value::Errata` with full diagnostic information (reason, formula, location).
71    pub fn new_reason_formula_location(reason: Arc<str>, formula: Arc<str>, location: Arc<str>) -> Value {
72        Value::Errata(Errata::Three {
73            reason,
74            formula,
75            location,
76        })
77    }
78
79    /// Creates an `Expression::Errata` with a reason and expression context.
80    pub fn new_expression(reason: Arc<str>, expression: Arc<str>) -> Expression {
81        Expression::Errata(Errata::Two {
82            reason,
83            formula: expression,
84        })
85    }
86
87    /// Creates an `Expression::Errata` with full diagnostic information.
88    pub fn new_expression_location(
89        reason: Arc<str>,
90        expression: Arc<str>,
91        location: Arc<str>,
92    ) -> Expression {
93        Expression::Errata(Errata::Three {
94            reason,
95            formula: expression,
96            location,
97        })
98    }
99
100    /// Returns the error reason string.
101    pub fn reason(&self) -> Arc<str> {
102        match self {
103            Errata::Three {
104                reason,
105                formula: _,
106                location: _,
107            } => reason.clone(),
108            Errata::Two { reason, formula: _ } => reason.clone(),
109            Errata::One { reason } => reason.clone(),
110            Errata::Critical { reason, formula: _ } => reason.clone(),
111        }
112    }
113
114    /// Returns the problematic formula or expression, or an empty string if unavailable.
115    pub fn formula(&self) -> Arc<str> {
116        match self {
117            Errata::Three {
118                reason: _,
119                formula,
120                location: _,
121            } => formula.clone(),
122            Errata::Two { reason: _, formula } => formula.clone(),
123            Errata::One { reason: _ } => Value::empty_str(),
124            Errata::Critical { reason: _, formula } => formula.clone(),
125        }
126    }
127
128    /// Returns the specific error location within the expression, or empty if unavailable.
129    pub fn location(&self) -> Arc<str> {
130        match self {
131            Errata::Three {
132                reason: _,
133                formula: _,
134                location,
135            } => location.clone(),
136            _ => Value::empty_str(),
137        }
138    }
139
140    /// Writes this error using `Writer::print_errata`.
141    pub fn print(&self, writer: &mut Writer) {
142        let (reason, formula, location) = match self {
143            Errata::Three {
144                reason,
145                formula,
146                location,
147            } => (reason.clone(), formula.clone(), location.clone()),
148            Errata::Two { reason, formula } => (reason.clone(), formula.clone(), Value::empty_str()),
149            Errata::One { reason } => (reason.clone(), Value::empty_str(), Value::empty_str()),
150            Errata::Critical { reason, formula } => (reason.clone(), formula.clone(), Value::empty_str()),
151        };
152        match writer.print_mode() {
153            // Output only the formula or reason
154            PrintMode::QuoteEscape => {
155                if !formula.is_empty() {
156                    writer.print(&formula);
157                } else {
158                    writer.print(&reason);
159                }
160            }
161            _ => {
162                if !reason.is_empty() {
163                    writer.print(&reason);
164                    writer.write_eol();
165                }
166                writer.print(&formula);
167                writer.write_eol();
168                if !location.is_empty() && let Some(offset) = formula.find(&*location) {
169                    for _ in 0..offset {
170                        writer.write_char(' ');
171                    }
172                    writer.write_char('^');
173                    writer.write_eol();
174                }
175            }
176        }
177    }
178
179    /// Return the formula-string representation (round-trippable by the parser).
180    pub fn to_formula(&self) -> String {
181        let mut writer = Writer::formulizer();
182        self.print(&mut writer);
183        writer.finish()
184    }
185}
186
187impl WriterLike for Errata {
188    fn write(&self, writer: &mut Writer) {
189        self.print(writer);
190    }
191}
192
193impl fmt::Display for Errata {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        write!(f, "{}", self.to_stringized())
196    }
197}