1use std::path::PathBuf;
11use thiserror::Error;
12
13#[derive(Debug, Error)]
15pub enum CliError {
16 #[error("IO error: {0}")]
18 Io(#[from] std::io::Error),
19
20 #[error("File already exists: {0}")]
22 FileExists(String),
23
24 #[error("Clap error: {0}")]
26 Clap(String),
27
28 #[error("Generation error: {0}")]
30 Generation(String),
31
32 #[error("Migration error: {0}")]
34 Migration(String),
35
36 #[error("Cache error: {0}")]
38 Cache(String),
39
40 #[error("Scheduler error: {0}")]
42 Scheduler(String),
43
44 #[error("{0}")]
46 Generic(String),
47
48 #[error("Invalid plugin name: {0} (must be lowercase letters, digits, underscores or hyphens, not starting with a digit)")]
50 InvalidPluginName(String),
51
52 #[error("Unknown template: '{requested}'. Available templates: {available:?}")]
54 UnknownTemplate {
55 requested: String,
57 available: Vec<String>,
59 },
60
61 #[error("Field parse error: {0}")]
63 FieldParseError(String),
64
65 #[error("Directory already exists: {0} (use --force to overwrite)")]
67 DirExists(PathBuf),
68
69 #[error("Template files missing: {0:?}")]
71 TemplateMissing(Vec<String>),
72
73 #[error("Template syntax error in {file}:{line}:{col}: {msg}")]
75 TemplateSyntaxError {
76 file: String,
78 line: usize,
80 col: usize,
82 msg: String,
84 },
85
86 #[error("Variable not found: '{var}' referenced in {file}:{line}")]
88 VarNotFound {
89 var: String,
91 file: String,
93 line: usize,
95 },
96
97 #[error("Compilation failed: {0:?}")]
99 CompileFailed(Vec<String>),
100
101 #[error("Foreign key not found: '{0}' is not a field in the slave table")]
103 ForeignKeyNotFound(String),
104
105 #[error("Master table and slave table must be different")]
107 MasterSlaveSame,
108
109 #[error("Marketplace error: {0}")]
111 Marketplace(String),
112
113 #[error("HTTP error: {0}")]
115 Http(String),
116
117 #[error("TOML error: {0}")]
119 Toml(String),
120
121 #[error("JSON error: {0}")]
123 Json(String),
124}
125
126impl From<clap::Error> for CliError {
127 fn from(e: clap::Error) -> Self {
128 CliError::Clap(e.to_string())
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn test_io_error_display() {
138 let err = CliError::Io(std::io::Error::new(
139 std::io::ErrorKind::NotFound,
140 "file not found",
141 ));
142 assert!(err.to_string().contains("IO error"));
143 assert!(err.to_string().contains("file not found"));
144 }
145
146 #[test]
147 fn test_file_exists_error_display() {
148 let err = CliError::FileExists("/path/to/file".to_string());
149 assert!(err.to_string().contains("File already exists"));
150 assert!(err.to_string().contains("/path/to/file"));
151 }
152
153 #[test]
154 fn test_clap_error_conversion() {
155 let clap_err = clap::Error::new(clap::error::ErrorKind::InvalidValue);
156 let cli_err: CliError = clap_err.into();
157 assert!(matches!(cli_err, CliError::Clap(_)));
158 }
159
160 #[test]
161 fn test_generation_error_display() {
162 let err = CliError::Generation("template substitution failed".to_string());
163 assert!(err.to_string().contains("Generation error"));
164 }
165
166 #[test]
167 fn test_migration_error_display() {
168 let err = CliError::Migration("database connection failed".to_string());
169 assert!(err.to_string().contains("Migration error"));
170 }
171
172 #[test]
173 fn test_cache_error_display() {
174 let err = CliError::Cache("redis connection failed".to_string());
175 assert!(err.to_string().contains("Cache error"));
176 }
177
178 #[test]
179 fn test_scheduler_error_display() {
180 let err = CliError::Scheduler("cron parse failed".to_string());
181 assert!(err.to_string().contains("Scheduler error"));
182 }
183
184 #[test]
185 fn test_invalid_plugin_name_display() {
186 let err = CliError::InvalidPluginName("my plugin".to_string());
187 assert!(err.to_string().contains("Invalid plugin name"));
188 assert!(err.to_string().contains("my plugin"));
189 }
190
191 #[test]
192 fn test_unknown_template_display() {
193 let err = CliError::UnknownTemplate {
194 requested: "nonexistent".to_string(),
195 available: vec!["crud".to_string(), "master-slave".to_string()],
196 };
197 assert!(err.to_string().contains("Unknown template"));
198 assert!(err.to_string().contains("nonexistent"));
199 assert!(err.to_string().contains("crud"));
200 }
201
202 #[test]
203 fn test_field_parse_error_display() {
204 let err = CliError::FieldParseError("unexpected ',' at position 5".to_string());
205 assert!(err.to_string().contains("Field parse error"));
206 }
207
208 #[test]
209 fn test_dir_exists_display() {
210 let err = CliError::DirExists(PathBuf::from("/path/to/plugin"));
211 assert!(err.to_string().contains("Directory already exists"));
212 assert!(err.to_string().contains("--force"));
213 }
214
215 #[test]
216 fn test_template_missing_display() {
217 let err = CliError::TemplateMissing(vec!["model.rs.tera".to_string()]);
218 assert!(err.to_string().contains("Template files missing"));
219 assert!(err.to_string().contains("model.rs.tera"));
220 }
221
222 #[test]
223 fn test_template_syntax_error_display() {
224 let err = CliError::TemplateSyntaxError {
225 file: "model.rs.tera".to_string(),
226 line: 10,
227 col: 5,
228 msg: "unexpected token".to_string(),
229 };
230 let s = err.to_string();
231 assert!(s.contains("model.rs.tera"));
232 assert!(s.contains("10"));
233 assert!(s.contains("5"));
234 assert!(s.contains("unexpected token"));
235 }
236
237 #[test]
238 fn test_var_not_found_display() {
239 let err = CliError::VarNotFound {
240 var: "plugin_name".to_string(),
241 file: "model.rs.tera".to_string(),
242 line: 3,
243 };
244 let s = err.to_string();
245 assert!(s.contains("plugin_name"));
246 assert!(s.contains("model.rs.tera"));
247 }
248
249 #[test]
250 fn test_compile_failed_display() {
251 let err =
252 CliError::CompileFailed(vec!["error[E0277]: trait bound not satisfied".to_string()]);
253 assert!(err.to_string().contains("Compilation failed"));
254 }
255
256 #[test]
257 fn test_foreign_key_not_found_display() {
258 let err = CliError::ForeignKeyNotFound("user_id".to_string());
259 assert!(err.to_string().contains("Foreign key not found"));
260 assert!(err.to_string().contains("user_id"));
261 }
262
263 #[test]
264 fn test_master_slave_same_display() {
265 let err = CliError::MasterSlaveSame;
266 assert!(err
267 .to_string()
268 .contains("Master table and slave table must be different"));
269 }
270}