Skip to main content

git_same/errors/
app.rs

1//! Application-level error types.
2//!
3//! These errors represent top-level failures in the gisa application,
4//! aggregating errors from providers, git operations, and configuration.
5
6use super::{GitError, ProviderError};
7use thiserror::Error;
8
9/// Top-level application errors.
10///
11/// This enum aggregates all error types that can occur in the application,
12/// providing a unified error type for the CLI interface.
13#[derive(Error, Debug)]
14pub enum AppError {
15    /// Configuration file error.
16    #[error("Configuration error: {0}")]
17    Config(String),
18
19    /// Authentication failed across all methods.
20    #[error("Authentication failed: {0}")]
21    Auth(String),
22
23    /// Error from a Git hosting provider.
24    #[error("Provider error: {0}")]
25    Provider(
26        #[from]
27        #[source]
28        ProviderError,
29    ),
30
31    /// Error during a git operation.
32    #[error("Git error: {0}")]
33    Git(
34        #[from]
35        #[source]
36        GitError,
37    ),
38
39    /// File system I/O error.
40    #[error("IO error: {0}")]
41    Io(
42        #[from]
43        #[source]
44        std::io::Error,
45    ),
46
47    /// Path-related error (invalid path, not found, etc.).
48    #[error("Path error: {0}")]
49    Path(String),
50
51    /// User cancelled the operation.
52    #[error("Operation cancelled by user")]
53    Cancelled,
54
55    /// Operation interrupted by signal.
56    #[error("Operation interrupted")]
57    Interrupted,
58
59    /// Generic error with context.
60    #[error("{0}")]
61    Other(
62        #[from]
63        #[source]
64        anyhow::Error,
65    ),
66}
67
68impl AppError {
69    /// Creates a configuration error.
70    pub fn config(message: impl Into<String>) -> Self {
71        AppError::Config(message.into())
72    }
73
74    /// Creates an authentication error.
75    pub fn auth(message: impl Into<String>) -> Self {
76        AppError::Auth(message.into())
77    }
78
79    /// Creates a path error.
80    pub fn path(message: impl Into<String>) -> Self {
81        AppError::Path(message.into())
82    }
83
84    /// Returns `true` if this error is recoverable with a retry.
85    pub fn is_retryable(&self) -> bool {
86        match self {
87            AppError::Provider(e) => e.is_retryable(),
88            AppError::Git(e) => e.is_retryable(),
89            AppError::Io(e) => {
90                // Some I/O errors are retryable
91                matches!(
92                    e.kind(),
93                    std::io::ErrorKind::TimedOut
94                        | std::io::ErrorKind::Interrupted
95                        | std::io::ErrorKind::WouldBlock
96                )
97            }
98            _ => false,
99        }
100    }
101
102    /// Returns a user-friendly exit code for this error.
103    pub fn exit_code(&self) -> i32 {
104        match self {
105            AppError::Config(_) => 2,
106            AppError::Auth(_) => 3,
107            AppError::Provider(_) => 4,
108            AppError::Git(_) => 5,
109            AppError::Io(_) => 6,
110            AppError::Path(_) => 7,
111            AppError::Cancelled => 130, // Standard for SIGINT
112            AppError::Interrupted => 130,
113            AppError::Other(_) => 1,
114        }
115    }
116
117    /// Returns a suggested action to resolve this error.
118    pub fn suggested_action(&self) -> &str {
119        match self {
120            AppError::Config(_) => {
121                "Check your config file for syntax errors, or run 'gisa init' to create one"
122            }
123            AppError::Auth(_) => "Run 'gh auth login' to authenticate with GitHub CLI",
124            AppError::Provider(e) => e.suggested_action(),
125            AppError::Git(e) => e.suggested_action(),
126            AppError::Io(_) => "Check file permissions and disk space",
127            AppError::Path(_) => "Check that the path exists and is accessible",
128            AppError::Cancelled | AppError::Interrupted => "Re-run the command to continue",
129            AppError::Other(_) => "Check the error message and try again",
130        }
131    }
132}
133
134/// A convenience type alias for Results in this application.
135pub type Result<T> = std::result::Result<T, AppError>;
136
137#[cfg(test)]
138#[path = "app_tests.rs"]
139mod tests;