use std::fmt;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Git(String),
Config(String),
InvalidPath(String),
Io(std::io::Error),
Runtime(String),
Pattern(String),
Http(String),
Workflow(String),
WorkflowTimeout(String),
RateLimitExceeded(String),
Recovery(String),
Yaml(String),
EventParse(String),
ShallowExhausted(String),
Other(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Git(msg) => write!(f, "Git error: {}", msg),
Error::Config(msg) => write!(f, "Configuration error: {}", msg),
Error::InvalidPath(path) => write!(f, "Invalid path: {}", path),
Error::Io(err) => write!(f, "I/O error: {}", err),
Error::Runtime(msg) => write!(f, "Runtime error: {}", msg),
Error::Pattern(msg) => write!(f, "Pattern error: {}", msg),
Error::Http(msg) => write!(f, "HTTP error: {}", msg),
Error::Workflow(msg) => write!(f, "Workflow error: {}", msg),
Error::WorkflowTimeout(msg) => write!(f, "Workflow timeout: {}", msg),
Error::RateLimitExceeded(msg) => write!(f, "Rate limit exceeded: {}", msg),
Error::Recovery(msg) => write!(f, "Recovery error: {}", msg),
Error::Yaml(msg) => write!(f, "YAML error: {}", msg),
Error::EventParse(msg) => write!(f, "Event parse error: {}", msg),
Error::ShallowExhausted(msg) => write!(f, "Shallow clone exhausted: {}", msg),
Error::Other(msg) => write!(f, "Error: {}", msg),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(err) => Some(err),
_ => None,
}
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Io(err)
}
}
impl From<git2::Error> for Error {
fn from(err: git2::Error) -> Self {
Error::Git(err.to_string())
}
}
impl From<globset::Error> for Error {
fn from(err: globset::Error) -> Self {
Error::Pattern(err.to_string())
}
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
Error::Http(err.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Other(format!("JSON error: {}", err))
}
}
impl From<serde_norway::Error> for Error {
fn from(err: serde_norway::Error) -> Self {
Error::Yaml(err.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ErrorKind {
Git,
InvalidPath,
Config,
Io,
Runtime,
Pattern,
Http,
Workflow,
WorkflowTimeout,
RateLimitExceeded,
Recovery,
Yaml,
EventParse,
ShallowExhausted,
Other,
}
impl Error {
#[inline]
pub const fn kind(&self) -> ErrorKind {
match self {
Error::Git(_) => ErrorKind::Git,
Error::InvalidPath(_) => ErrorKind::InvalidPath,
Error::Config(_) => ErrorKind::Config,
Error::Io(_) => ErrorKind::Io,
Error::Runtime(_) => ErrorKind::Runtime,
Error::Pattern(_) => ErrorKind::Pattern,
Error::Http(_) => ErrorKind::Http,
Error::Workflow(_) => ErrorKind::Workflow,
Error::WorkflowTimeout(_) => ErrorKind::WorkflowTimeout,
Error::RateLimitExceeded(_) => ErrorKind::RateLimitExceeded,
Error::Recovery(_) => ErrorKind::Recovery,
Error::Yaml(_) => ErrorKind::Yaml,
Error::EventParse(_) => ErrorKind::EventParse,
Error::ShallowExhausted(_) => ErrorKind::ShallowExhausted,
Error::Other(_) => ErrorKind::Other,
}
}
#[inline]
pub fn message(&self) -> &str {
match self {
Error::Git(msg)
| Error::Config(msg)
| Error::InvalidPath(msg)
| Error::Runtime(msg)
| Error::Pattern(msg)
| Error::Http(msg)
| Error::Workflow(msg)
| Error::WorkflowTimeout(msg)
| Error::RateLimitExceeded(msg)
| Error::Recovery(msg)
| Error::Yaml(msg)
| Error::EventParse(msg)
| Error::ShallowExhausted(msg)
| Error::Other(msg) => msg,
Error::Io(_) => "I/O error",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_kind_is_copy() {
let err = Error::Git("test".to_string());
let k = err.kind();
let k2 = k; assert_eq!(k, k2);
}
#[test]
fn test_error_kind_zero_alloc() {
assert_eq!(std::mem::size_of::<ErrorKind>(), 1);
}
#[test]
fn test_error_message_borrows() {
let err = Error::Config("bad config".to_string());
let msg: &str = err.message();
assert_eq!(msg, "bad config");
}
#[test]
fn test_all_error_variants_have_kind() {
let cases: Vec<(Error, ErrorKind)> = vec![
(Error::Git("g".into()), ErrorKind::Git),
(Error::InvalidPath("p".into()), ErrorKind::InvalidPath),
(Error::Config("c".into()), ErrorKind::Config),
(Error::Io(std::io::Error::other("io")), ErrorKind::Io),
(Error::Runtime("r".into()), ErrorKind::Runtime),
(Error::Pattern("pat".into()), ErrorKind::Pattern),
(Error::Http("h".into()), ErrorKind::Http),
(Error::Workflow("w".into()), ErrorKind::Workflow),
(
Error::WorkflowTimeout("wt".into()),
ErrorKind::WorkflowTimeout,
),
(
Error::RateLimitExceeded("rl".into()),
ErrorKind::RateLimitExceeded,
),
(Error::Recovery("rec".into()), ErrorKind::Recovery),
(Error::Yaml("y".into()), ErrorKind::Yaml),
(Error::EventParse("ep".into()), ErrorKind::EventParse),
(
Error::ShallowExhausted("se".into()),
ErrorKind::ShallowExhausted,
),
(Error::Other("o".into()), ErrorKind::Other),
];
for (err, expected_kind) in cases {
assert_eq!(err.kind(), expected_kind, "Mismatch for {:?}", err);
}
}
#[test]
fn test_error_kind_repr_u8() {
assert_eq!(std::mem::size_of::<ErrorKind>(), 1);
}
#[test]
fn test_error_messages_never_contain_token_patterns() {
let token_patterns = ["ghp_", "gho_", "ghs_", "github_pat_", "Bearer "];
let errors: Vec<Error> = vec![
Error::Git("git error".into()),
Error::Config("config error".into()),
Error::Http("http error".into()),
Error::Workflow("workflow error".into()),
Error::Runtime("runtime error".into()),
Error::RateLimitExceeded("rate limit exceeded".into()),
];
for err in &errors {
let msg = err.message();
let display = format!("{}", err);
let debug = format!("{:?}", err);
for pattern in &token_patterns {
assert!(
!msg.contains(pattern),
"Error message contains token pattern '{}': {}",
pattern,
msg
);
assert!(
!display.contains(pattern),
"Error Display contains token pattern '{}': {}",
pattern,
display
);
assert!(
!debug.contains(pattern),
"Error Debug contains token pattern '{}': {}",
pattern,
debug
);
}
}
}
}