1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// SPDX-License-Identifier: Apache-2.0
//! Error types for the aptu-core library.
//!
//! Uses `thiserror` for deriving `std::error::Error` implementations.
//! Application code should use `anyhow::Result` for top-level error handling.
use thiserror::Error;
/// Errors that can occur during Aptu operations.
#[derive(Error, Debug)]
pub enum AptuError {
/// GitHub API error from octocrab.
#[error("GitHub API error: {message}")]
GitHub {
/// Error message.
message: String,
},
/// AI provider error (`OpenRouter`, Ollama, etc.).
#[error("AI provider error: {message}")]
AI {
/// Error message from the AI provider.
message: String,
/// Optional HTTP status code from the provider.
status: Option<u16>,
/// Name of the AI provider (e.g., `OpenRouter`, `Ollama`).
provider: String,
},
/// User is not authenticated - needs to run `aptu auth login`.
#[error(
"Authentication required - run `aptu auth login` first, or set GITHUB_TOKEN environment variable"
)]
NotAuthenticated,
/// AI provider is not authenticated - missing API key.
#[error("AI provider '{provider}' is not authenticated - set {env_var} environment variable")]
AiProviderNotAuthenticated {
/// Name of the AI provider (e.g., `OpenRouter`, `Ollama`).
provider: String,
/// Environment variable name to set (e.g., `OPENROUTER_API_KEY`).
env_var: String,
},
/// Rate limit exceeded from an AI provider.
#[error("Rate limit exceeded on {provider}, retry after {retry_after}s")]
RateLimited {
/// Name of the provider that rate limited (e.g., `OpenRouter`).
provider: String,
/// Number of seconds to wait before retrying.
retry_after: u64,
},
/// AI response was truncated (incomplete JSON due to EOF).
#[error("Truncated response from {provider} - response ended prematurely")]
TruncatedResponse {
/// Name of the AI provider that returned truncated response.
provider: String,
},
/// Configuration file error.
#[error("Configuration error: {message}")]
Config {
/// Error message.
message: String,
},
/// Invalid JSON response from AI provider.
#[error("Invalid JSON response from AI")]
InvalidAIResponse(#[source] serde_json::Error),
/// Network/HTTP error from reqwest.
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
/// Keyring/credential storage error.
#[cfg(feature = "keyring")]
#[error("Keyring error: {0}")]
Keyring(#[from] keyring::Error),
/// Circuit breaker is open - AI provider is unavailable.
#[error("Circuit breaker is open - AI provider is temporarily unavailable")]
CircuitOpen,
/// Type mismatch: reference is a different type than expected.
#[error("#{number} is {actual}, not {expected}")]
TypeMismatch {
/// The issue/PR number.
number: u64,
/// Expected type.
expected: ResourceType,
/// Actual type.
actual: ResourceType,
},
/// Model registry error (runtime model validation).
#[error("Model registry error: {message}")]
ModelRegistry {
/// Error message.
message: String,
},
/// Model validation error - invalid model ID with suggestions.
#[error("Invalid model ID: {model_id}. Did you mean one of these?\n{suggestions}")]
ModelValidation {
/// The invalid model ID provided by the user.
model_id: String,
/// Suggested valid model IDs based on fuzzy matching.
suggestions: String,
},
/// Security scan error.
#[error("Security scan error: {message}")]
SecurityScan {
/// Error message.
message: String,
},
}
/// GitHub resource type for type mismatch errors.
#[derive(Debug, Clone, Copy)]
pub enum ResourceType {
/// GitHub issue.
Issue,
/// GitHub pull request.
PullRequest,
}
impl std::fmt::Display for ResourceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResourceType::Issue => write!(f, "issue"),
ResourceType::PullRequest => write!(f, "pull request"),
}
}
}
impl From<octocrab::Error> for AptuError {
fn from(err: octocrab::Error) -> Self {
AptuError::GitHub {
message: err.to_string(),
}
}
}
impl From<config::ConfigError> for AptuError {
fn from(err: config::ConfigError) -> Self {
AptuError::Config {
message: err.to_string(),
}
}
}