vika_cli/
error.rs

1use thiserror::Error;
2
3/// Main error type for vika-cli operations.
4///
5/// This enum represents all possible errors that can occur during
6/// code generation, configuration loading, or file operations.
7///
8/// # Example
9///
10/// ```no_run
11/// use vika_cli::error::VikaError;
12///
13/// fn example() -> Result<(), VikaError> {
14///     // Some operation that might fail
15///     let result: Result<String, VikaError> = Ok("success".to_string());
16///     
17///     match result {
18///         Ok(value) => println!("Success: {:?}", value),
19///         Err(VikaError::Schema(e)) => eprintln!("Schema error: {}", e),
20///         Err(e) => eprintln!("Other error: {}", e),
21///     }
22///     
23///     Ok(())
24/// }
25/// ```
26#[derive(Debug, Error)]
27pub enum VikaError {
28    #[error("Schema error: {0}")]
29    Schema(#[from] SchemaError),
30
31    #[error("Config error: {0}")]
32    Config(#[from] ConfigError),
33
34    #[error("Network error: {0}")]
35    Network(#[from] NetworkError),
36
37    #[error("File system error: {0}")]
38    FileSystem(#[from] FileSystemError),
39
40    #[error("Generation error: {0}")]
41    Generation(#[from] GenerationError),
42
43    #[error("Validation error: {0}")]
44    Validation(#[from] ValidationError),
45}
46
47#[derive(Debug, Error)]
48pub enum SchemaError {
49    #[error("Schema not found: {name}")]
50    NotFound { name: String },
51
52    #[error("Invalid schema reference: {ref_path}")]
53    InvalidReference { ref_path: String },
54
55    #[error("Circular reference detected: {cycle:?}")]
56    CircularReference { cycle: Vec<String> },
57
58    #[error("Unsupported schema type: {schema_type}")]
59    UnsupportedType { schema_type: String },
60
61    #[error("Parameter not found: {name}")]
62    ParameterNotFound { name: String },
63
64    #[error("Request body not found: {name}")]
65    RequestBodyNotFound { name: String },
66
67    #[error("Response not found: {name}")]
68    ResponseNotFound { name: String },
69
70    #[error("Unsupported reference path: {ref_path}")]
71    UnsupportedReferencePath { ref_path: String },
72}
73
74#[derive(Debug, Error)]
75pub enum ConfigError {
76    #[error("Config file not found: {path}")]
77    NotFound { path: String },
78
79    #[error("Invalid config: {message}")]
80    Invalid { message: String },
81
82    #[error("Failed to read config: {0}")]
83    ReadError(#[from] std::io::Error),
84
85    #[error("Failed to parse config: {0}")]
86    ParseError(#[from] serde_json::Error),
87
88    #[error("Required field missing: {field}")]
89    MissingField { field: String },
90
91    #[error("Invalid output directory: {path}")]
92    InvalidOutputDirectory { path: String },
93
94    #[error("No specs are defined. At least one spec must be configured in the 'specs' array.")]
95    NoSpecDefined,
96
97    #[error("Duplicate spec name found: {name}")]
98    DuplicateSpecName { name: String },
99
100    #[error("Invalid spec name: {name}. Spec names must be non-empty and contain only alphanumeric characters, hyphens, and underscores.")]
101    InvalidSpecName { name: String },
102}
103
104#[derive(Debug, Error)]
105pub enum NetworkError {
106    #[error("Failed to fetch spec from {url}: {source}")]
107    FetchFailed {
108        url: String,
109        #[source]
110        source: reqwest::Error,
111    },
112
113    #[error("Failed to read response from {url}: {source}")]
114    ReadResponseFailed {
115        url: String,
116        #[source]
117        source: reqwest::Error,
118    },
119
120    #[error("Invalid URL: {url}")]
121    InvalidUrl { url: String },
122}
123
124#[derive(Debug, Error)]
125pub enum FileSystemError {
126    #[error("Failed to create directory: {path}")]
127    CreateDirectoryFailed {
128        path: String,
129        #[source]
130        source: std::io::Error,
131    },
132
133    #[error("Failed to read file: {path}")]
134    ReadFileFailed {
135        path: String,
136        #[source]
137        source: std::io::Error,
138    },
139
140    #[error("Failed to write file: {path}")]
141    WriteFileFailed {
142        path: String,
143        #[source]
144        source: std::io::Error,
145    },
146
147    #[error("File not found: {path}")]
148    FileNotFound { path: String },
149
150    #[error("Directory not found: {path}")]
151    DirectoryNotFound { path: String },
152
153    #[error("File was modified by user: {path}\n  This file was previously generated by vika-cli but has been modified.\n  Use --force to overwrite, or --backup to create a backup first.")]
154    FileModifiedByUser { path: String },
155}
156
157#[derive(Debug, Error)]
158pub enum GenerationError {
159    #[error("No modules available after filtering")]
160    NoModulesAvailable,
161
162    #[error("Cannot use more than one hook generator at once. Please specify only one of --react-query or --swr")]
163    InvalidHookFlags,
164
165    #[error("No modules selected")]
166    NoModulesSelected,
167
168    #[error("Spec path or URL is required. Use --spec <path-or-url>")]
169    SpecPathRequired,
170
171    #[error("Spec not found: {name}. Available specs: {available:?}")]
172    SpecNotFound {
173        name: String,
174        available: Vec<String>,
175    },
176
177    #[error("Failed to generate TypeScript types: {0}")]
178    TypeScriptGenerationFailed(String),
179
180    #[error("Failed to generate Zod schemas: {0}")]
181    ZodGenerationFailed(String),
182
183    #[error("Failed to generate API client: {0}")]
184    ApiClientGenerationFailed(String),
185
186    #[error("Invalid operation: {message}")]
187    InvalidOperation { message: String },
188
189    #[error("Template error: {0}")]
190    Template(#[from] TemplateError),
191}
192
193#[derive(Debug, Error)]
194pub enum ValidationError {
195    #[error("Invalid TypeScript identifier: {name}")]
196    InvalidTypeScriptIdentifier { name: String },
197
198    #[error("Invalid module name: {name}")]
199    InvalidModuleName { name: String },
200
201    #[error("Validation failed: {message}")]
202    Failed { message: String },
203}
204
205#[derive(Debug, Error)]
206pub enum TemplateError {
207    #[error("Template not found: {name}")]
208    NotFound { name: String },
209
210    #[error("Failed to render template {name}: {message}")]
211    RenderFailed { name: String, message: String },
212
213    #[error("Invalid template syntax in {name}: {message}")]
214    InvalidSyntax { name: String, message: String },
215
216    #[error("Missing required context field {field} in template {name}")]
217    MissingContextField { name: String, field: String },
218
219    #[error("Failed to load template {name}: {source}")]
220    LoadFailed {
221        name: String,
222        #[source]
223        source: FileSystemError,
224    },
225}
226
227pub type Result<T> = std::result::Result<T, VikaError>;
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_schema_error_display() {
235        let error = SchemaError::NotFound {
236            name: "User".to_string(),
237        };
238        let error_msg = error.to_string();
239        assert!(error_msg.contains("User"));
240        assert!(error_msg.contains("not found"));
241    }
242
243    #[test]
244    fn test_config_error_display() {
245        let error = ConfigError::Invalid {
246            message: "Test error".to_string(),
247        };
248        let error_msg = error.to_string();
249        assert!(error_msg.contains("Test error"));
250    }
251
252    #[test]
253    fn test_network_error_display() {
254        let error = NetworkError::InvalidUrl {
255            url: "invalid-url".to_string(),
256        };
257        let error_msg = error.to_string();
258        assert!(error_msg.contains("invalid-url"));
259    }
260
261    #[test]
262    fn test_file_system_error_display() {
263        let error = FileSystemError::FileNotFound {
264            path: "/test/path".to_string(),
265        };
266        let error_msg = error.to_string();
267        assert!(error_msg.contains("/test/path"));
268    }
269
270    #[test]
271    fn test_generation_error_display() {
272        let error = GenerationError::NoModulesAvailable;
273        let error_msg = error.to_string();
274        assert!(error_msg.contains("No modules available"));
275    }
276
277    #[test]
278    fn test_error_conversion() {
279        let schema_error = SchemaError::NotFound {
280            name: "Test".to_string(),
281        };
282        let vika_error: VikaError = schema_error.into();
283        let error_msg = vika_error.to_string();
284        assert!(error_msg.contains("Schema error"));
285    }
286
287    #[test]
288    fn test_error_context_preservation() {
289        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found");
290        let config_error = ConfigError::ReadError(io_error);
291        let vika_error: VikaError = config_error.into();
292        let error_msg = vika_error.to_string();
293        assert!(error_msg.contains("Config error"));
294    }
295}