1use thiserror::Error;
9
10#[derive(Error, Debug)]
12pub enum AptuError {
13 #[error("GitHub API error: {message}")]
15 GitHub {
16 message: String,
18 },
19
20 #[error("AI provider error: {message}")]
22 AI {
23 message: String,
25 status: Option<u16>,
27 provider: String,
29 },
30
31 #[error(
33 "Authentication required - run `aptu auth login` first, or set GITHUB_TOKEN environment variable"
34 )]
35 NotAuthenticated,
36
37 #[error("AI provider '{provider}' is not authenticated - set {env_var} environment variable")]
39 AiProviderNotAuthenticated {
40 provider: String,
42 env_var: String,
44 },
45
46 #[error("Rate limit exceeded on {provider}, retry after {retry_after}s")]
48 RateLimited {
49 provider: String,
51 retry_after: u64,
53 },
54
55 #[error("Truncated response from {provider} - response ended prematurely")]
57 TruncatedResponse {
58 provider: String,
60 },
61
62 #[error("Configuration error: {message}")]
64 Config {
65 message: String,
67 },
68
69 #[error("Invalid JSON response from AI")]
71 InvalidAIResponse(#[source] serde_json::Error),
72
73 #[cfg(not(target_arch = "wasm32"))]
75 #[error("Network error: {0}")]
76 Network(#[from] reqwest::Error),
77
78 #[cfg(feature = "keyring")]
80 #[error("Keyring error: {0}")]
81 Keyring(#[from] keyring_core::error::Error),
82
83 #[error("Circuit breaker is open - AI provider is temporarily unavailable")]
85 CircuitOpen,
86
87 #[error("#{number} is {actual}, not {expected}")]
89 TypeMismatch {
90 number: u64,
92 expected: ResourceType,
94 actual: ResourceType,
96 },
97
98 #[error("Model registry error: {message}")]
100 ModelRegistry {
101 message: String,
103 },
104
105 #[error("Invalid model ID: {model_id}. Did you mean one of these?\n{suggestions}")]
107 ModelValidation {
108 model_id: String,
110 suggestions: String,
112 },
113
114 #[error("Security scan error: {message}")]
116 SecurityScan {
117 message: String,
119 },
120
121 #[error(
123 "input field `{field}` exceeds limit: {actual_bytes} bytes (limit: {limit_bytes} bytes){hint}"
124 )]
125 InputExceedsLimit {
126 field: String,
128 actual_bytes: usize,
130 limit_bytes: usize,
132 hint: String,
134 },
135}
136
137#[derive(Debug, Clone, Copy)]
139pub enum ResourceType {
140 Issue,
142 PullRequest,
144}
145
146impl std::fmt::Display for ResourceType {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 match self {
149 ResourceType::Issue => write!(f, "issue"),
150 ResourceType::PullRequest => write!(f, "pull request"),
151 }
152 }
153}
154
155#[cfg(not(target_arch = "wasm32"))]
156impl From<octocrab::Error> for AptuError {
157 fn from(err: octocrab::Error) -> Self {
158 AptuError::GitHub {
159 message: err.to_string(),
160 }
161 }
162}
163
164impl From<config::ConfigError> for AptuError {
165 fn from(err: config::ConfigError) -> Self {
166 AptuError::Config {
167 message: err.to_string(),
168 }
169 }
170}