1use std::path::PathBuf;
8use thiserror::Error;
9
10#[derive(Debug, Error)]
15pub enum PlisskenError {
16 #[error("config not found: {path}")]
21 ConfigNotFound {
22 path: PathBuf,
24 },
25
26 #[error("config parse error: {message}")]
28 ConfigParse {
29 message: String,
31 #[source]
33 source: Option<toml::de::Error>,
34 },
35
36 #[error("config validation failed: {message}")]
38 ConfigValidation {
39 message: String,
41 },
42
43 #[error("failed to parse {language} file '{path}': {message}")]
48 Parse {
49 language: String,
51 path: PathBuf,
53 line: Option<usize>,
55 message: String,
57 },
58
59 #[error("failed to read file '{path}': {message}")]
61 FileRead {
62 path: PathBuf,
64 message: String,
66 #[source]
68 source: std::io::Error,
69 },
70
71 #[error("template error: {message}")]
76 Template {
77 message: String,
79 #[source]
81 source: tera::Error,
82 },
83
84 #[error("failed to write output '{path}': {message}")]
86 OutputWrite {
87 path: PathBuf,
89 message: String,
91 #[source]
93 source: std::io::Error,
94 },
95
96 #[error("cross-reference error: {message}")]
101 CrossRef {
102 message: String,
104 },
105
106 #[error("{context}: {source}")]
111 Io {
112 context: String,
114 #[source]
116 source: std::io::Error,
117 },
118
119 #[error("module discovery failed in '{path}': {message}")]
124 Discovery {
125 path: PathBuf,
127 message: String,
129 },
130
131 #[error("failed to parse manifest '{path}': {message}")]
136 ManifestParse {
137 path: PathBuf,
139 message: String,
141 },
142}
143
144pub type Result<T> = std::result::Result<T, PlisskenError>;
146
147impl From<std::io::Error> for PlisskenError {
152 fn from(err: std::io::Error) -> Self {
153 PlisskenError::Io {
154 context: "IO operation failed".into(),
155 source: err,
156 }
157 }
158}
159
160impl From<tera::Error> for PlisskenError {
161 fn from(err: tera::Error) -> Self {
162 PlisskenError::Template {
163 message: err.to_string(),
164 source: err,
165 }
166 }
167}
168
169impl From<toml::de::Error> for PlisskenError {
170 fn from(err: toml::de::Error) -> Self {
171 PlisskenError::ConfigParse {
172 message: err.to_string(),
173 source: Some(err),
174 }
175 }
176}
177
178impl From<crate::config::ConfigError> for PlisskenError {
180 fn from(err: crate::config::ConfigError) -> Self {
181 PlisskenError::ConfigValidation {
182 message: err.to_string(),
183 }
184 }
185}
186
187impl From<crate::manifest::ManifestError> for PlisskenError {
189 fn from(err: crate::manifest::ManifestError) -> Self {
190 PlisskenError::ManifestParse {
191 path: PathBuf::new(),
192 message: err.to_string(),
193 }
194 }
195}
196
197impl PlisskenError {
202 pub fn rust_parse(path: impl Into<PathBuf>, message: impl Into<String>) -> Self {
204 PlisskenError::Parse {
205 language: "Rust".into(),
206 path: path.into(),
207 line: None,
208 message: message.into(),
209 }
210 }
211
212 pub fn rust_parse_at(
214 path: impl Into<PathBuf>,
215 line: usize,
216 message: impl Into<String>,
217 ) -> Self {
218 PlisskenError::Parse {
219 language: "Rust".into(),
220 path: path.into(),
221 line: Some(line),
222 message: message.into(),
223 }
224 }
225
226 pub fn python_parse(path: impl Into<PathBuf>, message: impl Into<String>) -> Self {
228 PlisskenError::Parse {
229 language: "Python".into(),
230 path: path.into(),
231 line: None,
232 message: message.into(),
233 }
234 }
235
236 pub fn python_parse_at(
238 path: impl Into<PathBuf>,
239 line: usize,
240 message: impl Into<String>,
241 ) -> Self {
242 PlisskenError::Parse {
243 language: "Python".into(),
244 path: path.into(),
245 line: Some(line),
246 message: message.into(),
247 }
248 }
249
250 pub fn file_read(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
252 let path = path.into();
253 PlisskenError::FileRead {
254 message: source.to_string(),
255 path,
256 source,
257 }
258 }
259
260 pub fn io(context: impl Into<String>, source: std::io::Error) -> Self {
262 PlisskenError::Io {
263 context: context.into(),
264 source,
265 }
266 }
267
268 pub fn config_not_found(path: impl Into<PathBuf>) -> Self {
270 PlisskenError::ConfigNotFound { path: path.into() }
271 }
272
273 pub fn discovery(path: impl Into<PathBuf>, message: impl Into<String>) -> Self {
275 PlisskenError::Discovery {
276 path: path.into(),
277 message: message.into(),
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn test_parse_error_display() {
288 let err = PlisskenError::rust_parse("/path/to/file.rs", "unexpected token");
289 assert!(err.to_string().contains("Rust"));
290 assert!(err.to_string().contains("/path/to/file.rs"));
291 assert!(err.to_string().contains("unexpected token"));
292 }
293
294 #[test]
295 fn test_parse_error_with_line() {
296 let err = PlisskenError::python_parse_at("/path/to/file.py", 42, "syntax error");
297 assert!(err.to_string().contains("Python"));
298 assert!(err.to_string().contains("/path/to/file.py"));
299 }
300
301 #[test]
302 fn test_config_not_found_display() {
303 let err = PlisskenError::config_not_found("/project/plissken.toml");
304 assert!(err.to_string().contains("config not found"));
305 assert!(err.to_string().contains("plissken.toml"));
306 }
307
308 #[test]
309 fn test_config_validation_error() {
310 let err = PlisskenError::ConfigValidation {
311 message: "no language configured".into(),
312 };
313 assert!(err.to_string().contains("config validation failed"));
314 assert!(err.to_string().contains("no language configured"));
315 }
316
317 #[test]
318 fn test_io_error_conversion() {
319 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
320 let err: PlisskenError = io_err.into();
321 assert!(err.to_string().contains("IO operation failed"));
322 }
323
324 #[test]
325 fn test_template_error_conversion() {
326 let tera_result = tera::Tera::one_off("{{ invalid", &tera::Context::new(), false);
328 if let Err(tera_err) = tera_result {
329 let err: PlisskenError = tera_err.into();
330 assert!(err.to_string().contains("template error"));
331 }
332 }
333
334 #[test]
335 fn test_discovery_error() {
336 let err = PlisskenError::discovery("/src/python", "permission denied");
337 assert!(err.to_string().contains("module discovery failed"));
338 assert!(err.to_string().contains("/src/python"));
339 }
340
341 #[test]
342 fn test_file_read_error() {
343 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "not found");
344 let err = PlisskenError::file_read("/path/to/file.rs", io_err);
345 assert!(err.to_string().contains("failed to read file"));
346 assert!(err.to_string().contains("/path/to/file.rs"));
347 }
348
349 #[test]
350 fn test_crossref_error() {
351 let err = PlisskenError::CrossRef {
352 message: "unresolved reference".into(),
353 };
354 assert!(err.to_string().contains("cross-reference error"));
355 }
356}