use std::fmt;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("Scanner error in {scanner}: {message}")]
Scanner {
scanner: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Model error: {0}")]
Model(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Vault error: {0}")]
Vault(String),
#[error("Operation timed out after {0}ms")]
Timeout(u64),
#[error("Resource exhausted: {0}")]
ResourceExhausted(String),
#[error("Internal error: {0}")]
Internal(String),
#[error("Authentication error: {0}")]
Auth(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Not found: {0}")]
NotFound(String),
}
impl Error {
pub fn scanner<S: Into<String>, M: Into<String>>(scanner: S, message: M) -> Self {
Self::Scanner {
scanner: scanner.into(),
message: message.into(),
source: None,
}
}
pub fn scanner_with_source<S: Into<String>, M: Into<String>>(
scanner: S,
message: M,
source: Box<dyn std::error::Error + Send + Sync>,
) -> Self {
Self::Scanner {
scanner: scanner.into(),
message: message.into(),
source: Some(source),
}
}
pub fn model<S: Into<String>>(message: S) -> Self {
Self::Model(message.into())
}
pub fn config<S: Into<String>>(message: S) -> Self {
Self::Config(message.into())
}
pub fn invalid_input<S: Into<String>>(message: S) -> Self {
Self::InvalidInput(message.into())
}
pub fn vault<S: Into<String>>(message: S) -> Self {
Self::Vault(message.into())
}
pub fn timeout(duration_ms: u64) -> Self {
Self::Timeout(duration_ms)
}
pub fn resource_exhausted<S: Into<String>>(resource: S) -> Self {
Self::ResourceExhausted(resource.into())
}
pub fn internal<S: Into<String>>(message: S) -> Self {
Self::Internal(message.into())
}
pub fn auth<S: Into<String>>(message: S) -> Self {
Self::Auth(message.into())
}
pub fn unauthorized<S: Into<String>>(message: S) -> Self {
Self::Unauthorized(message.into())
}
pub fn not_found<S: Into<String>>(message: S) -> Self {
Self::NotFound(message.into())
}
pub fn is_retryable(&self) -> bool {
matches!(
self,
Error::Timeout(_) | Error::ResourceExhausted(_) | Error::Io(_)
)
}
pub fn category(&self) -> &'static str {
match self {
Error::Scanner { .. } => "scanner",
Error::Model(_) => "model",
Error::Config(_) => "config",
Error::InvalidInput(_) => "invalid_input",
Error::Io(_) => "io",
Error::Serialization(_) => "serialization",
Error::Vault(_) => "vault",
Error::Timeout(_) => "timeout",
Error::ResourceExhausted(_) => "resource_exhausted",
Error::Internal(_) => "internal",
Error::Auth(_) => "auth",
Error::Unauthorized(_) => "unauthorized",
Error::NotFound(_) => "not_found",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = Error::scanner("test_scanner", "test message");
assert!(matches!(err, Error::Scanner { .. }));
assert_eq!(err.category(), "scanner");
}
#[test]
fn test_error_retryable() {
assert!(Error::timeout(5000).is_retryable());
assert!(!Error::config("bad config").is_retryable());
}
#[test]
fn test_error_display() {
let err = Error::scanner("ban_substrings", "pattern not found");
let msg = format!("{}", err);
assert!(msg.contains("ban_substrings"));
assert!(msg.contains("pattern not found"));
}
}