Skip to main content

guts_compat/
error.rs

1//! Error types for the compatibility layer.
2
3use thiserror::Error;
4
5/// Result type for compatibility operations.
6pub type Result<T> = std::result::Result<T, CompatError>;
7
8/// Errors that can occur in the compatibility layer.
9#[derive(Debug, Error)]
10pub enum CompatError {
11    /// User not found.
12    #[error("user not found: {0}")]
13    UserNotFound(String),
14
15    /// Username already exists.
16    #[error("username already exists: {0}")]
17    UsernameExists(String),
18
19    /// Invalid username format.
20    #[error("invalid username: {0}")]
21    InvalidUsername(String),
22
23    /// Token not found.
24    #[error("token not found")]
25    TokenNotFound,
26
27    /// Invalid token format.
28    #[error("invalid token format")]
29    InvalidTokenFormat,
30
31    /// Token expired.
32    #[error("token expired")]
33    TokenExpired,
34
35    /// Invalid token (hash mismatch).
36    #[error("invalid token")]
37    InvalidToken,
38
39    /// Insufficient scope for operation.
40    #[error("insufficient scope: requires {0:?}")]
41    InsufficientScope(crate::token::TokenScope),
42
43    /// SSH key not found.
44    #[error("SSH key not found")]
45    SshKeyNotFound,
46
47    /// Invalid SSH key format.
48    #[error("invalid SSH key format: {0}")]
49    InvalidSshKey(String),
50
51    /// SSH key already exists (duplicate fingerprint).
52    #[error("SSH key already exists with fingerprint: {0}")]
53    SshKeyExists(String),
54
55    /// Release not found.
56    #[error("release not found: {0}")]
57    ReleaseNotFound(String),
58
59    /// Release already exists (same tag).
60    #[error("release already exists for tag: {0}")]
61    ReleaseExists(String),
62
63    /// Asset not found.
64    #[error("asset not found: {0}")]
65    AssetNotFound(String),
66
67    /// Asset already exists.
68    #[error("asset already exists: {0}")]
69    AssetExists(String),
70
71    /// Path not found in repository.
72    #[error("path not found: {0}")]
73    PathNotFound(String),
74
75    /// Invalid ref (branch, tag, or SHA).
76    #[error("invalid ref: {0}")]
77    InvalidRef(String),
78
79    /// Archive generation failed.
80    #[error("archive generation failed: {0}")]
81    ArchiveFailed(String),
82
83    /// Rate limit exceeded.
84    #[error("rate limit exceeded, resets at {0}")]
85    RateLimitExceeded(u64),
86
87    /// Storage error.
88    #[error("storage error: {0}")]
89    Storage(String),
90
91    /// Cryptographic operation failed.
92    #[error("crypto error: {0}")]
93    Crypto(String),
94}
95
96impl CompatError {
97    /// Get the HTTP status code for this error.
98    pub fn status_code(&self) -> u16 {
99        match self {
100            Self::UserNotFound(_) => 404,
101            Self::UsernameExists(_) => 409,
102            Self::InvalidUsername(_) => 422,
103            Self::TokenNotFound => 401,
104            Self::InvalidTokenFormat => 401,
105            Self::TokenExpired => 401,
106            Self::InvalidToken => 401,
107            Self::InsufficientScope(_) => 403,
108            Self::SshKeyNotFound => 404,
109            Self::InvalidSshKey(_) => 422,
110            Self::SshKeyExists(_) => 409,
111            Self::ReleaseNotFound(_) => 404,
112            Self::ReleaseExists(_) => 409,
113            Self::AssetNotFound(_) => 404,
114            Self::AssetExists(_) => 409,
115            Self::PathNotFound(_) => 404,
116            Self::InvalidRef(_) => 422,
117            Self::ArchiveFailed(_) => 500,
118            Self::RateLimitExceeded(_) => 429,
119            Self::Storage(_) => 500,
120            Self::Crypto(_) => 500,
121        }
122    }
123
124    /// Get the GitHub-compatible error message.
125    pub fn github_message(&self) -> &str {
126        match self {
127            Self::UserNotFound(_) => "Not Found",
128            Self::UsernameExists(_) => "Validation Failed",
129            Self::InvalidUsername(_) => "Validation Failed",
130            Self::TokenNotFound => "Bad credentials",
131            Self::InvalidTokenFormat => "Bad credentials",
132            Self::TokenExpired => "Bad credentials",
133            Self::InvalidToken => "Bad credentials",
134            Self::InsufficientScope(_) => "Forbidden",
135            Self::SshKeyNotFound => "Not Found",
136            Self::InvalidSshKey(_) => "Validation Failed",
137            Self::SshKeyExists(_) => "Validation Failed",
138            Self::ReleaseNotFound(_) => "Not Found",
139            Self::ReleaseExists(_) => "Validation Failed",
140            Self::AssetNotFound(_) => "Not Found",
141            Self::AssetExists(_) => "Validation Failed",
142            Self::PathNotFound(_) => "Not Found",
143            Self::InvalidRef(_) => "Validation Failed",
144            Self::ArchiveFailed(_) => "Server Error",
145            Self::RateLimitExceeded(_) => "API rate limit exceeded",
146            Self::Storage(_) => "Server Error",
147            Self::Crypto(_) => "Server Error",
148        }
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_error_status_codes() {
158        assert_eq!(CompatError::UserNotFound("test".into()).status_code(), 404);
159        assert_eq!(CompatError::TokenNotFound.status_code(), 401);
160        assert_eq!(CompatError::RateLimitExceeded(0).status_code(), 429);
161    }
162}