aptu_core/
error.rs

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