1#![allow(unused_assignments, reason = "miette::Diagnostic derive generates field assignments")]
2
3use std::path::PathBuf;
4
5use miette::Diagnostic;
6use thiserror::Error;
7
8#[derive(Debug, Error, Diagnostic)]
14pub enum CommitGenError {
15 #[error("git: {message}")]
16 #[diagnostic(code(lgit::git))]
17 GitError { message: String },
18
19 #[error("git index is locked")]
20 #[diagnostic(
21 code(lgit::git::index_locked),
22 help("Another git process may be running in this repository.\nRemove the lock file to continue:\n rm {}", lock_path.display()),
23 )]
24 GitIndexLocked { lock_path: PathBuf },
25
26 #[error("API request failed (HTTP {status}): {body}")]
27 #[diagnostic(code(lgit::api))]
28 ApiError { status: u16, body: String },
29
30 #[error("API call failed after {retries} retries")]
31 #[diagnostic(
32 code(lgit::api::retry_exhausted),
33 help(
34 "Check that your LiteLLM server is running and reachable.\nYou can increase max_retries \
35 in ~/.config/llm-git/config.toml"
36 )
37 )]
38 ApiRetryExhausted {
39 retries: u32,
40 #[source]
41 source: Box<Self>,
42 },
43
44 #[error("Validation failed: {0}")]
45 #[diagnostic(code(lgit::validation))]
46 ValidationError(String),
47
48 #[error("No changes found in {mode} mode")]
49 #[diagnostic(
50 code(lgit::git::no_changes),
51 help("Stage changes with `git add` or use --mode=unstaged to analyze working tree changes")
52 )]
53 NoChanges { mode: String },
54
55 #[error("Diff parsing failed: {0}")]
56 #[diagnostic(code(lgit::diff))]
57 #[allow(dead_code, reason = "Reserved for future diff parsing error handling")]
58 DiffParseError(String),
59
60 #[error("Invalid commit type: {0}")]
61 #[diagnostic(
62 code(lgit::types::commit_type),
63 help("Valid types: feat, fix, refactor, docs, test, chore, style, perf, build, ci, revert")
64 )]
65 InvalidCommitType(String),
66
67 #[error("Invalid scope format: {0}")]
68 #[diagnostic(
69 code(lgit::types::scope),
70 help("Scopes must be lowercase alphanumeric with at most 2 segments (e.g. api/client)")
71 )]
72 InvalidScope(String),
73
74 #[error("Summary too long: {len} chars (max {max})")]
75 #[diagnostic(code(lgit::validation::length))]
76 SummaryTooLong { len: usize, max: usize },
77
78 #[error("IO error: {0}")]
79 #[diagnostic(code(lgit::io))]
80 IoError(#[from] std::io::Error),
81
82 #[error("JSON error: {0}")]
83 #[diagnostic(code(lgit::json))]
84 JsonError(#[from] serde_json::Error),
85
86 #[error("HTTP error: {0}")]
87 #[diagnostic(code(lgit::http))]
88 HttpError(#[from] reqwest::Error),
89
90 #[error("Clipboard error: {0}")]
91 #[diagnostic(code(lgit::clipboard))]
92 ClipboardError(#[from] arboard::Error),
93
94 #[error("{0}")]
95 Other(String),
96
97 #[error("Failed to parse changelog {path}: {reason}")]
98 #[diagnostic(code(lgit::changelog::parse))]
99 ChangelogParseError { path: String, reason: String },
100
101 #[error("No [Unreleased] section found in {path}")]
102 #[diagnostic(
103 code(lgit::changelog::no_unreleased),
104 help("Add an ## [Unreleased] section to the changelog file")
105 )]
106 NoUnreleasedSection { path: String },
107}
108
109impl CommitGenError {
110 pub fn git(msg: impl Into<String>) -> Self {
113 Self::GitError { message: msg.into() }
114 }
115}
116
117pub type Result<T, E = CommitGenError> = std::result::Result<T, E>;