use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("Signing error: {0}")]
Signing(String),
#[error("Key error: {0}")]
Key(String),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Canonicalization error: {0}")]
Canonicalization(String),
#[error("PEM error: {0}")]
Pem(#[from] pem::PemError),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("HTTP error: {0}")]
Http(String),
#[error("Configuration error: {0}")]
Configuration(String),
#[error(transparent)]
GitClaw(#[from] GitClawError),
}
#[derive(Error, Debug, Clone)]
pub enum GitClawError {
#[error("[{code}] {message}")]
Authentication {
code: String,
message: String,
request_id: Option<String>,
},
#[error("[{code}] {message}")]
Authorization {
code: String,
message: String,
request_id: Option<String>,
},
#[error("[{code}] {message}")]
NotFound {
code: String,
message: String,
request_id: Option<String>,
},
#[error("[{code}] {message}")]
Conflict {
code: String,
message: String,
request_id: Option<String>,
},
#[error("[{code}] {message} (retry after {retry_after}s)")]
RateLimited {
code: String,
message: String,
retry_after: u32,
request_id: Option<String>,
},
#[error("[{code}] {message}")]
Validation {
code: String,
message: String,
request_id: Option<String>,
},
#[error("[{code}] {message}")]
Server {
code: String,
message: String,
request_id: Option<String>,
},
}
impl GitClawError {
#[must_use]
pub fn code(&self) -> &str {
match self {
Self::Authentication { code, .. }
| Self::Authorization { code, .. }
| Self::NotFound { code, .. }
| Self::Conflict { code, .. }
| Self::RateLimited { code, .. }
| Self::Validation { code, .. }
| Self::Server { code, .. } => code,
}
}
#[must_use]
pub fn message(&self) -> &str {
match self {
Self::Authentication { message, .. }
| Self::Authorization { message, .. }
| Self::NotFound { message, .. }
| Self::Conflict { message, .. }
| Self::RateLimited { message, .. }
| Self::Validation { message, .. }
| Self::Server { message, .. } => message,
}
}
#[must_use]
pub fn request_id(&self) -> Option<&str> {
match self {
Self::Authentication { request_id, .. }
| Self::Authorization { request_id, .. }
| Self::NotFound { request_id, .. }
| Self::Conflict { request_id, .. }
| Self::RateLimited { request_id, .. }
| Self::Validation { request_id, .. }
| Self::Server { request_id, .. } => request_id.as_deref(),
}
}
#[must_use]
pub fn retry_after(&self) -> Option<u32> {
match self {
Self::RateLimited { retry_after, .. } => Some(*retry_after),
_ => None,
}
}
#[must_use]
pub fn is_retryable(&self) -> bool {
matches!(self, Self::RateLimited { .. } | Self::Server { .. })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gitclaw_error_code() {
let error = GitClawError::Authentication {
code: "INVALID_SIGNATURE".to_string(),
message: "Signature validation failed".to_string(),
request_id: Some("req-123".to_string()),
};
assert_eq!(error.code(), "INVALID_SIGNATURE");
assert_eq!(error.message(), "Signature validation failed");
assert_eq!(error.request_id(), Some("req-123"));
}
#[test]
fn test_rate_limited_error() {
let error = GitClawError::RateLimited {
code: "RATE_LIMITED".to_string(),
message: "Too many requests".to_string(),
retry_after: 30,
request_id: None,
};
assert_eq!(error.retry_after(), Some(30));
assert!(error.is_retryable());
}
#[test]
fn test_non_retryable_errors() {
let auth_error = GitClawError::Authentication {
code: "INVALID_SIGNATURE".to_string(),
message: "Bad signature".to_string(),
request_id: None,
};
assert!(!auth_error.is_retryable());
let not_found = GitClawError::NotFound {
code: "REPO_NOT_FOUND".to_string(),
message: "Repository not found".to_string(),
request_id: None,
};
assert!(!not_found.is_retryable());
}
#[test]
fn test_server_error_is_retryable() {
let error = GitClawError::Server {
code: "INTERNAL_ERROR".to_string(),
message: "Internal server error".to_string(),
request_id: None,
};
assert!(error.is_retryable());
}
}