pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Validation error: {message}")]
Validation {
message: String,
},
#[error("Routing error: {message}")]
Routing {
message: String,
},
#[error("Proxy error: {message}")]
Proxy {
message: String,
},
#[error("Latency simulation error: {message}")]
Latency {
message: String,
},
#[error("Configuration error: {message}")]
Config {
message: String,
},
#[error("Protocol not found: {message}")]
ProtocolNotFound {
message: String,
},
#[error("Protocol disabled: {message}")]
ProtocolDisabled {
message: String,
},
#[error("Protocol handler in use: {message}")]
ProtocolHandlerInUse {
message: String,
},
#[error("Protocol validation error: {message}")]
ProtocolValidationError {
protocol: String,
message: String,
},
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("YAML error: {0}")]
Yaml(#[from] serde_yaml::Error),
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("URL parse error: {0}")]
UrlParse(#[from] url::ParseError),
#[error("Regex error: {0}")]
Regex(#[from] regex::Error),
#[error("Route not found: {method} {path}")]
RouteNotFound {
method: String,
path: String,
},
#[error("Schema validation failed at '{path}': expected {expected}, got {actual}")]
SchemaValidationFailed {
path: String,
expected: String,
actual: String,
},
#[error("Configuration error: {message}")]
ConfigWithSource {
message: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("{entity} not found: {id}")]
NotFound {
entity: String,
id: String,
},
#[error("Feature disabled: {feature}")]
FeatureDisabled {
feature: String,
},
#[error("Already initialized: {component}")]
AlreadyInitialized {
component: String,
},
#[error("Invalid state: {message}")]
InvalidState {
message: String,
},
#[error("SIEM transport error: {message}")]
SiemTransport {
message: String,
},
#[error("I/O error ({context}): {message}")]
IoWithContext {
context: String,
message: String,
},
#[error("Internal error: {message}")]
Internal {
message: String,
},
#[error("Generic error: {0}")]
Generic(String),
#[error("Encryption error: {0}")]
Encryption(#[from] crate::encryption::EncryptionError),
#[cfg(feature = "scripting")]
#[error("JavaScript error: {0}")]
JavaScript(#[from] rquickjs::Error),
}
impl From<String> for Error {
fn from(message: String) -> Self {
Self::Generic(message)
}
}
impl Error {
pub fn validation<S: Into<String>>(message: S) -> Self {
Self::Validation {
message: message.into(),
}
}
pub fn routing<S: Into<String>>(message: S) -> Self {
Self::Routing {
message: message.into(),
}
}
pub fn proxy<S: Into<String>>(message: S) -> Self {
Self::Proxy {
message: message.into(),
}
}
pub fn latency<S: Into<String>>(message: S) -> Self {
Self::Latency {
message: message.into(),
}
}
pub fn config<S: Into<String>>(message: S) -> Self {
Self::Config {
message: message.into(),
}
}
pub fn protocol_not_found<S: Into<String>>(message: S) -> Self {
Self::ProtocolNotFound {
message: message.into(),
}
}
pub fn protocol_disabled<S: Into<String>>(message: S) -> Self {
Self::ProtocolDisabled {
message: message.into(),
}
}
pub fn protocol_handler_in_use<S: Into<String>>(message: S) -> Self {
Self::ProtocolHandlerInUse {
message: message.into(),
}
}
pub fn protocol_validation_error<S: Into<String>>(protocol: S, message: S) -> Self {
Self::ProtocolValidationError {
protocol: protocol.into(),
message: message.into(),
}
}
pub fn route_not_found<S: Into<String>>(method: S, path: S) -> Self {
Self::RouteNotFound {
method: method.into(),
path: path.into(),
}
}
pub fn schema_validation_failed<S: Into<String>>(path: S, expected: S, actual: S) -> Self {
Self::SchemaValidationFailed {
path: path.into(),
expected: expected.into(),
actual: actual.into(),
}
}
pub fn config_with_source<S: Into<String>>(
message: S,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::ConfigWithSource {
message: message.into(),
source: Box::new(source),
}
}
pub fn not_found<S1: Into<String>, S2: Into<String>>(entity: S1, id: S2) -> Self {
Self::NotFound {
entity: entity.into(),
id: id.into(),
}
}
pub fn feature_disabled<S: Into<String>>(feature: S) -> Self {
Self::FeatureDisabled {
feature: feature.into(),
}
}
pub fn already_initialized<S: Into<String>>(component: S) -> Self {
Self::AlreadyInitialized {
component: component.into(),
}
}
pub fn invalid_state<S: Into<String>>(message: S) -> Self {
Self::InvalidState {
message: message.into(),
}
}
pub fn siem_transport<S: Into<String>>(message: S) -> Self {
Self::SiemTransport {
message: message.into(),
}
}
pub fn io_with_context<S1: Into<String>, S2: Into<String>>(context: S1, message: S2) -> Self {
Self::IoWithContext {
context: context.into(),
message: message.into(),
}
}
pub fn internal<S: Into<String>>(message: S) -> Self {
Self::Internal {
message: message.into(),
}
}
#[deprecated(note = "Use a specific error variant instead of Generic")]
pub fn generic<S: Into<String>>(message: S) -> Self {
Self::Generic(message.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validation_error() {
let err = Error::validation("test validation");
assert!(err.to_string().contains("Validation error"));
assert!(err.to_string().contains("test validation"));
}
#[test]
fn test_routing_error() {
let err = Error::routing("test routing");
assert!(err.to_string().contains("Routing error"));
assert!(err.to_string().contains("test routing"));
}
#[test]
fn test_proxy_error() {
let err = Error::proxy("test proxy");
assert!(err.to_string().contains("Proxy error"));
assert!(err.to_string().contains("test proxy"));
}
#[test]
fn test_latency_error() {
let err = Error::latency("test latency");
assert!(err.to_string().contains("Latency simulation error"));
assert!(err.to_string().contains("test latency"));
}
#[test]
fn test_config_error() {
let err = Error::config("test config");
assert!(err.to_string().contains("Configuration error"));
assert!(err.to_string().contains("test config"));
}
#[test]
fn test_internal_error() {
let err = Error::internal("test internal");
assert!(err.to_string().contains("Internal error"));
assert!(err.to_string().contains("test internal"));
}
#[test]
#[allow(deprecated)]
fn test_generic_error() {
let err = Error::generic("test generic");
assert!(err.to_string().contains("Generic error"));
assert!(err.to_string().contains("test generic"));
}
#[test]
fn test_not_found_error() {
let err = Error::not_found("User", "user-123");
assert_eq!(err.to_string(), "User not found: user-123");
}
#[test]
fn test_feature_disabled_error() {
let err = Error::feature_disabled("access review");
assert_eq!(err.to_string(), "Feature disabled: access review");
}
#[test]
fn test_already_initialized_error() {
let err = Error::already_initialized("SIEM emitter");
assert_eq!(err.to_string(), "Already initialized: SIEM emitter");
}
#[test]
fn test_invalid_state_error() {
let err = Error::invalid_state("Request is not pending approval");
assert_eq!(err.to_string(), "Invalid state: Request is not pending approval");
}
#[test]
fn test_siem_transport_error() {
let err = Error::siem_transport("Connection refused");
assert_eq!(err.to_string(), "SIEM transport error: Connection refused");
}
#[test]
fn test_io_with_context_error() {
let err = Error::io_with_context("reading risk register", "file not found");
assert_eq!(err.to_string(), "I/O error (reading risk register): file not found");
}
#[test]
fn test_from_string() {
let err: Error = "test message".to_string().into();
assert!(matches!(err, Error::Generic(_)));
assert!(err.to_string().contains("test message"));
}
#[test]
fn test_json_error_conversion() {
let json_err = serde_json::from_str::<serde_json::Value>("invalid json");
assert!(json_err.is_err());
let err: Error = json_err.unwrap_err().into();
assert!(matches!(err, Error::Json(_)));
}
#[test]
fn test_url_parse_error_conversion() {
let url_err = url::Url::parse("not a url");
assert!(url_err.is_err());
let err: Error = url_err.unwrap_err().into();
assert!(matches!(err, Error::UrlParse(_)));
}
#[test]
#[allow(clippy::invalid_regex)]
fn test_regex_error_conversion() {
let regex_err = regex::Regex::new("[invalid(");
assert!(regex_err.is_err());
let err: Error = regex_err.unwrap_err().into();
assert!(matches!(err, Error::Regex(_)));
}
#[test]
#[allow(deprecated)]
fn test_error_display() {
let errors = vec![
(Error::validation("msg"), "Validation error: msg"),
(Error::routing("msg"), "Routing error: msg"),
(Error::proxy("msg"), "Proxy error: msg"),
(Error::latency("msg"), "Latency simulation error: msg"),
(Error::config("msg"), "Configuration error: msg"),
(Error::internal("msg"), "Internal error: msg"),
];
for (err, expected) in errors {
assert_eq!(err.to_string(), expected);
}
}
}