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