Skip to main content

toon/
error.rs

1use std::path::PathBuf;
2use thiserror::Error;
3
4/// Comprehensive error types for TOON encoding and decoding operations.
5#[derive(Debug, Error)]
6pub enum ToonError {
7    /// Parse error with line number context
8    #[error("Line {line}: {message}")]
9    Parse { line: usize, message: String },
10
11    /// Validation error (strict mode violations)
12    #[error("Validation error at line {line}: {message}")]
13    Validation { line: usize, message: String },
14
15    /// Event stream processing error
16    #[error("Event stream error: {message}")]
17    EventStream { message: String },
18
19    /// Path expansion conflict or error
20    #[error("Path expansion error for '{path}': {message}")]
21    PathExpansion { path: String, message: String },
22
23    /// I/O error with operation context
24    #[error("{operation}{}: {source}", path.as_ref().map(|p| format!(" '{}'", p.display())).unwrap_or_default())]
25    Io {
26        operation: String,
27        path: Option<PathBuf>,
28        #[source]
29        source: std::io::Error,
30    },
31
32    /// JSON serialization/deserialization error
33    #[error("JSON error: {message}")]
34    Json { message: String },
35
36    /// Generic message (for backward compatibility)
37    #[error("{message}")]
38    Message { message: String },
39}
40
41pub type Result<T> = std::result::Result<T, ToonError>;
42
43impl ToonError {
44    // =========================================================================
45    // Backward-compatible constructor (preserves existing API)
46    // =========================================================================
47
48    /// Create a generic message error (backward compatible).
49    #[must_use]
50    pub fn message(message: impl Into<String>) -> Self {
51        Self::Message {
52            message: message.into(),
53        }
54    }
55
56    // =========================================================================
57    // Parse error constructors
58    // =========================================================================
59
60    /// Create a parse error with line number.
61    #[must_use]
62    pub fn parse(line: usize, message: impl Into<String>) -> Self {
63        Self::Parse {
64            line,
65            message: message.into(),
66        }
67    }
68
69    /// Create a parse error for unterminated string.
70    #[must_use]
71    pub fn unterminated_string(line: usize) -> Self {
72        Self::parse(line, "Unterminated string: missing closing quote")
73    }
74
75    /// Create a parse error for missing colon after key.
76    #[must_use]
77    pub fn missing_colon(line: usize) -> Self {
78        Self::parse(line, "Missing colon after key")
79    }
80
81    /// Create a parse error for invalid array length.
82    #[must_use]
83    pub fn invalid_array_length(line: usize, value: &str) -> Self {
84        Self::parse(line, format!("Invalid array length: {value}"))
85    }
86
87    // =========================================================================
88    // Validation error constructors
89    // =========================================================================
90
91    /// Create a validation error with line number.
92    #[must_use]
93    pub fn validation(line: usize, message: impl Into<String>) -> Self {
94        Self::Validation {
95            line,
96            message: message.into(),
97        }
98    }
99
100    /// Create a validation error for tabs in indentation.
101    #[must_use]
102    pub fn tabs_not_allowed(line: usize) -> Self {
103        Self::validation(line, "Tabs are not allowed in indentation in strict mode")
104    }
105
106    /// Create a validation error for incorrect indentation.
107    #[must_use]
108    pub fn invalid_indentation(line: usize, expected: usize, found: usize) -> Self {
109        Self::validation(
110            line,
111            format!("Indentation must be exact multiple of {expected}, but found {found} spaces"),
112        )
113    }
114
115    // =========================================================================
116    // Event stream error constructors
117    // =========================================================================
118
119    /// Create an event stream error.
120    #[must_use]
121    pub fn event_stream(message: impl Into<String>) -> Self {
122        Self::EventStream {
123            message: message.into(),
124        }
125    }
126
127    /// Create an error for mismatched end event.
128    #[must_use]
129    pub fn mismatched_end(expected: &str, found: &str) -> Self {
130        Self::event_stream(format!(
131            "Mismatched end event: expected {expected}, found {found}"
132        ))
133    }
134
135    /// Create an error for unexpected event.
136    #[must_use]
137    pub fn unexpected_event(event: &str, context: &str) -> Self {
138        Self::event_stream(format!("Unexpected {event} event {context}"))
139    }
140
141    // =========================================================================
142    // Path expansion error constructors
143    // =========================================================================
144
145    /// Create a path expansion error.
146    #[must_use]
147    pub fn path_expansion(path: impl Into<String>, message: impl Into<String>) -> Self {
148        Self::PathExpansion {
149            path: path.into(),
150            message: message.into(),
151        }
152    }
153
154    /// Create an error for path conflict during expansion.
155    #[must_use]
156    pub fn path_conflict(path: &str, existing: &str) -> Self {
157        Self::path_expansion(path, format!("conflicts with existing key '{existing}'"))
158    }
159
160    // =========================================================================
161    // I/O error constructors
162    // =========================================================================
163
164    /// Create an I/O error with path context.
165    #[must_use]
166    pub fn io(operation: impl Into<String>, path: Option<PathBuf>, source: std::io::Error) -> Self {
167        Self::Io {
168            operation: operation.into(),
169            path,
170            source,
171        }
172    }
173
174    /// Create an error for file read failure.
175    #[must_use]
176    pub fn file_read(path: PathBuf, source: std::io::Error) -> Self {
177        Self::io("Failed to read file", Some(path), source)
178    }
179
180    /// Create an error for file write failure.
181    #[must_use]
182    pub fn file_write(path: PathBuf, source: std::io::Error) -> Self {
183        Self::io("Failed to write to file", Some(path), source)
184    }
185
186    /// Create an error for file creation failure.
187    #[must_use]
188    pub fn file_create(path: PathBuf, source: std::io::Error) -> Self {
189        Self::io("Failed to create file", Some(path), source)
190    }
191
192    /// Create an error for stdin read failure.
193    #[must_use]
194    pub fn stdin_read(source: std::io::Error) -> Self {
195        Self::io("Failed to read stdin", None, source)
196    }
197
198    /// Create an error for stdout write failure.
199    #[must_use]
200    pub fn stdout_write(source: std::io::Error) -> Self {
201        Self::io("Failed to write to stdout", None, source)
202    }
203
204    // =========================================================================
205    // JSON error constructors
206    // =========================================================================
207
208    /// Create a JSON error.
209    #[must_use]
210    pub fn json(message: impl Into<String>) -> Self {
211        Self::Json {
212            message: message.into(),
213        }
214    }
215
216    /// Create a JSON parse error.
217    #[must_use]
218    pub fn json_parse(err: &serde_json::Error) -> Self {
219        Self::json(format!("Failed to parse JSON: {err}"))
220    }
221
222    /// Create a JSON stringify error.
223    #[must_use]
224    pub fn json_stringify(err: &serde_json::Error) -> Self {
225        Self::json(format!("Failed to stringify JSON: {err}"))
226    }
227}
228
229impl From<std::io::Error> for ToonError {
230    fn from(err: std::io::Error) -> Self {
231        Self::io("I/O error", None, err)
232    }
233}
234
235impl From<serde_json::Error> for ToonError {
236    fn from(err: serde_json::Error) -> Self {
237        Self::json(err.to_string())
238    }
239}