1use std::path::PathBuf;
2use thiserror::Error;
3
4#[derive(Debug, Error)]
6pub enum ToonError {
7 #[error("Line {line}: {message}")]
9 Parse { line: usize, message: String },
10
11 #[error("Validation error at line {line}: {message}")]
13 Validation { line: usize, message: String },
14
15 #[error("Event stream error: {message}")]
17 EventStream { message: String },
18
19 #[error("Path expansion error for '{path}': {message}")]
21 PathExpansion { path: String, message: String },
22
23 #[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 #[error("JSON error: {message}")]
34 Json { message: String },
35
36 #[error("{message}")]
38 Message { message: String },
39}
40
41pub type Result<T> = std::result::Result<T, ToonError>;
42
43impl ToonError {
44 #[must_use]
50 pub fn message(message: impl Into<String>) -> Self {
51 Self::Message {
52 message: message.into(),
53 }
54 }
55
56 #[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 #[must_use]
71 pub fn unterminated_string(line: usize) -> Self {
72 Self::parse(line, "Unterminated string: missing closing quote")
73 }
74
75 #[must_use]
77 pub fn missing_colon(line: usize) -> Self {
78 Self::parse(line, "Missing colon after key")
79 }
80
81 #[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 #[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 #[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 #[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 #[must_use]
121 pub fn event_stream(message: impl Into<String>) -> Self {
122 Self::EventStream {
123 message: message.into(),
124 }
125 }
126
127 #[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 #[must_use]
137 pub fn unexpected_event(event: &str, context: &str) -> Self {
138 Self::event_stream(format!("Unexpected {event} event {context}"))
139 }
140
141 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
194 pub fn stdin_read(source: std::io::Error) -> Self {
195 Self::io("Failed to read stdin", None, source)
196 }
197
198 #[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 #[must_use]
210 pub fn json(message: impl Into<String>) -> Self {
211 Self::Json {
212 message: message.into(),
213 }
214 }
215
216 #[must_use]
218 pub fn json_parse(err: &serde_json::Error) -> Self {
219 Self::json(format!("Failed to parse JSON: {err}"))
220 }
221
222 #[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}