1#![allow(clippy::struct_excessive_bools)]
42#![allow(clippy::needless_pass_by_value)]
43
44pub mod cli;
45pub mod core;
46
47#[cfg(feature = "ai")]
48pub mod ai;
49
50#[cfg(feature = "wiki")]
51pub mod wiki;
52
53#[cfg(feature = "export")]
54pub mod export;
55
56use thiserror::Error;
57
58pub type Result<T> = std::result::Result<T, Error>;
60
61#[derive(Error, Debug)]
63pub enum Error {
64 #[error("git error: {message}")]
66 Git {
67 message: String,
69 command: Vec<String>,
71 exit_code: i32,
73 stderr: String,
75 },
76
77 #[error("git executable not found - please install git")]
79 GitNotFound,
80
81 #[error("not a git repository{}", path.as_ref().map(|p| format!(": {p}")).unwrap_or_default())]
83 NotARepository {
84 path: Option<String>,
86 },
87
88 #[error("git-adr not initialized - run 'git adr init' first")]
90 NotInitialized,
91
92 #[error("ADR not found: {id}")]
94 AdrNotFound {
95 id: String,
97 },
98
99 #[error("invalid ADR format: {message}")]
101 InvalidAdr {
102 message: String,
104 },
105
106 #[error("YAML error: {0}")]
108 Yaml(#[from] serde_yaml::Error),
109
110 #[error("JSON error: {0}")]
112 Json(#[from] serde_json::Error),
113
114 #[error("IO error: {0}")]
116 Io(#[from] std::io::Error),
117
118 #[error("template error: {0}")]
120 Template(#[from] tera::Error),
121
122 #[error("configuration error: {message}")]
124 Config {
125 message: String,
127 },
128
129 #[error("validation error: {message}")]
131 Validation {
132 message: String,
134 },
135
136 #[error("content too large: {size} bytes (max: {max} bytes)")]
138 ContentTooLarge {
139 size: usize,
141 max: usize,
143 },
144
145 #[error("feature not available: {feature} - install with 'cargo install git-adr --features {feature}'")]
147 FeatureNotAvailable {
148 feature: String,
150 },
151
152 #[error("invalid status '{status}', valid values are: {}", valid.join(", "))]
154 InvalidStatus {
155 status: String,
157 valid: Vec<String>,
159 },
160
161 #[error("parse error: {message}")]
163 ParseError {
164 message: String,
166 },
167
168 #[error("template error: {message}")]
170 TemplateError {
171 message: String,
173 },
174
175 #[error("template not found: {name}")]
177 TemplateNotFound {
178 name: String,
180 },
181
182 #[cfg(feature = "ai")]
184 #[error("AI not configured: {message}")]
185 AiNotConfigured {
186 message: String,
188 },
189
190 #[cfg(feature = "ai")]
192 #[error("invalid AI provider: {provider}")]
193 InvalidProvider {
194 provider: String,
196 },
197
198 #[cfg(feature = "wiki")]
200 #[error("wiki error: {message}")]
201 WikiError {
202 message: String,
204 },
205
206 #[cfg(feature = "export")]
208 #[error("export error: {message}")]
209 ExportError {
210 message: String,
212 },
213
214 #[error("invalid format: {format}")]
216 InvalidFormat {
217 format: String,
219 },
220
221 #[error("IO error: {message}")]
223 IoError {
224 message: String,
226 },
227
228 #[error("{0}")]
230 Other(String),
231}
232
233impl Error {
234 #[must_use]
236 pub fn git(
237 message: impl Into<String>,
238 command: Vec<String>,
239 exit_code: i32,
240 stderr: impl Into<String>,
241 ) -> Self {
242 Self::Git {
243 message: message.into(),
244 command,
245 exit_code,
246 stderr: stderr.into(),
247 }
248 }
249
250 #[must_use]
252 pub fn adr_not_found(id: impl Into<String>) -> Self {
253 Self::AdrNotFound { id: id.into() }
254 }
255
256 #[must_use]
258 pub fn invalid_adr(message: impl Into<String>) -> Self {
259 Self::InvalidAdr {
260 message: message.into(),
261 }
262 }
263
264 #[must_use]
266 pub fn config(message: impl Into<String>) -> Self {
267 Self::Config {
268 message: message.into(),
269 }
270 }
271
272 #[must_use]
274 pub fn validation(message: impl Into<String>) -> Self {
275 Self::Validation {
276 message: message.into(),
277 }
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
286 fn test_error_git() {
287 let err = Error::git(
288 "command failed",
289 vec!["git".to_string(), "status".to_string()],
290 1,
291 "error output",
292 );
293 assert!(matches!(err, Error::Git { .. }));
294 assert!(format!("{err}").contains("git error"));
295 }
296
297 #[test]
298 fn test_error_adr_not_found() {
299 let err = Error::adr_not_found("ADR-0001");
300 assert!(format!("{err}").contains("ADR not found"));
301 }
302
303 #[test]
304 fn test_error_invalid_adr() {
305 let err = Error::invalid_adr("invalid content");
306 assert!(format!("{err}").contains("invalid ADR format"));
307 }
308
309 #[test]
310 fn test_error_config() {
311 let err = Error::config("config error");
312 assert!(format!("{err}").contains("configuration error"));
313 }
314
315 #[test]
316 fn test_error_validation() {
317 let err = Error::validation("validation failed");
318 assert!(format!("{err}").contains("validation error"));
319 }
320
321 #[test]
322 fn test_error_not_initialized() {
323 let err = Error::NotInitialized;
324 assert!(format!("{err}").contains("not initialized"));
325 }
326
327 #[test]
328 fn test_error_git_not_found() {
329 let err = Error::GitNotFound;
330 assert!(format!("{err}").contains("git executable not found"));
331 }
332
333 #[test]
334 fn test_error_not_a_repository() {
335 let err = Error::NotARepository {
336 path: Some("/tmp/test".to_string()),
337 };
338 assert!(format!("{err}").contains("not a git repository"));
339 assert!(format!("{err}").contains("/tmp/test"));
340 }
341
342 #[test]
343 fn test_error_not_a_repository_no_path() {
344 let err = Error::NotARepository { path: None };
345 let msg = format!("{err}");
346 assert!(msg.contains("not a git repository"));
347 }
348
349 #[test]
350 fn test_error_content_too_large() {
351 let err = Error::ContentTooLarge {
352 size: 1024,
353 max: 512,
354 };
355 assert!(format!("{err}").contains("content too large"));
356 }
357
358 #[test]
359 fn test_error_feature_not_available() {
360 let err = Error::FeatureNotAvailable {
361 feature: "ai".to_string(),
362 };
363 assert!(format!("{err}").contains("feature not available"));
364 }
365
366 #[test]
367 fn test_error_invalid_status() {
368 let err = Error::InvalidStatus {
369 status: "invalid".to_string(),
370 valid: vec!["proposed".to_string(), "accepted".to_string()],
371 };
372 assert!(format!("{err}").contains("invalid status"));
373 }
374
375 #[test]
376 fn test_error_parse_error() {
377 let err = Error::ParseError {
378 message: "parse failed".to_string(),
379 };
380 assert!(format!("{err}").contains("parse error"));
381 }
382
383 #[test]
384 fn test_error_template_not_found() {
385 let err = Error::TemplateNotFound {
386 name: "custom".to_string(),
387 };
388 assert!(format!("{err}").contains("template not found"));
389 }
390
391 #[test]
392 fn test_error_other() {
393 let err = Error::Other("generic error".to_string());
394 assert!(format!("{err}").contains("generic error"));
395 }
396}