Skip to main content

git_same/errors/
provider.rs

1//! Provider-specific error types for Git hosting services.
2//!
3//! These errors represent failures that occur when interacting with
4//! provider APIs like GitHub, GitLab, or Bitbucket.
5
6use thiserror::Error;
7
8/// Errors that occur when interacting with a Git hosting provider's API.
9#[derive(Error, Debug)]
10pub enum ProviderError {
11    /// Authentication failed - invalid or expired token.
12    #[error("Authentication failed: {0}")]
13    Authentication(String),
14
15    /// Network-level error - connection failed, timeout, etc.
16    #[error("Network error: {0}")]
17    Network(String),
18
19    /// API returned an error response.
20    #[error("API error (HTTP {status}): {message}")]
21    Api {
22        /// HTTP status code
23        status: u16,
24        /// Error message from the API
25        message: String,
26    },
27
28    /// Rate limit exceeded.
29    #[error("Rate limited. Resets at {reset_time}")]
30    RateLimited {
31        /// When the rate limit resets (ISO 8601 format)
32        reset_time: String,
33    },
34
35    /// Failed to parse API response.
36    #[error("Failed to parse response: {0}")]
37    Parse(String),
38
39    /// Configuration error for the provider.
40    #[error("Configuration error: {0}")]
41    Configuration(String),
42
43    /// Feature not yet implemented.
44    #[error("Not implemented: {0}")]
45    NotImplemented(String),
46
47    /// Resource not found (404).
48    #[error("Not found: {0}")]
49    NotFound(String),
50
51    /// Permission denied (403 without rate limit).
52    #[error("Permission denied: {0}")]
53    PermissionDenied(String),
54}
55
56impl ProviderError {
57    /// Returns `true` if this error is potentially recoverable with a retry.
58    ///
59    /// Retryable errors include:
60    /// - Network errors (transient connectivity issues)
61    /// - Rate limiting (will succeed after waiting)
62    /// - HTTP 429 responses
63    /// - Server errors (5xx status codes)
64    pub fn is_retryable(&self) -> bool {
65        matches!(
66            self,
67            ProviderError::Network(_)
68                | ProviderError::RateLimited { .. }
69                | ProviderError::Api { status: 429, .. }
70                | ProviderError::Api {
71                    status: 500..=599,
72                    ..
73                }
74        )
75    }
76
77    /// Returns a user-friendly suggestion for how to resolve this error.
78    pub fn suggested_action(&self) -> &'static str {
79        match self {
80            ProviderError::Authentication(_) => {
81                "Re-authenticate with your Git provider or verify your access token/credentials"
82            }
83            ProviderError::RateLimited { .. } => {
84                "Wait for the rate limit to reset, or use a different authentication token"
85            }
86            ProviderError::Network(_) => "Check your internet connection and try again",
87            ProviderError::Api { status: 403, .. } => {
88                "Check that your token has the required permissions for this operation"
89            }
90            ProviderError::Api { status: 404, .. } | ProviderError::NotFound(_) => {
91                "The resource may have been deleted or you may have lost access"
92            }
93            ProviderError::PermissionDenied(_) => {
94                "Check that your token has the required permissions for this operation"
95            }
96            ProviderError::Configuration(_) => "Check your gisa.config.toml configuration file",
97            ProviderError::NotImplemented(_) => {
98                "This feature is not yet available. Check for updates"
99            }
100            _ => "Please check the error message and try again",
101        }
102    }
103
104    /// Creates an API error from an HTTP status code and message.
105    pub fn from_status(status: u16, message: impl Into<String>) -> Self {
106        let message = message.into();
107        match status {
108            401 => ProviderError::Authentication(message),
109            403 => ProviderError::PermissionDenied(message),
110            404 => ProviderError::NotFound(message),
111            _ => ProviderError::Api { status, message },
112        }
113    }
114}
115
116#[cfg(test)]
117#[path = "provider_tests.rs"]
118mod tests;