pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Process error: {message}")]
Process {
message: String,
},
#[error("Process not found: {identifier}")]
ProcessNotFound {
identifier: String,
},
#[error("Process already exists: {name}")]
ProcessAlreadyExists {
name: String,
},
#[error("Process already running: {0}")]
ProcessAlreadyRunning(String),
#[error("Failed to start process {name}: {reason}")]
ProcessStartFailed {
name: String,
reason: String,
},
#[error("Failed to stop process {name}: {reason}")]
ProcessStopFailed {
name: String,
reason: String,
},
#[error("Configuration error: {message}")]
Config {
message: String,
},
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("TOML error: {0}")]
Toml(#[from] toml::de::Error),
#[error("System error: {0}")]
#[cfg(unix)]
System(#[from] nix::Error),
#[error("Signal error: {message}")]
Signal {
message: String,
},
#[error("Monitoring error: {message}")]
Monitoring {
message: String,
},
#[error("Web server error: {message}")]
WebServer {
message: String,
},
#[error("Operation timed out: {operation}")]
Timeout {
operation: String,
},
#[error("Permission denied: {message}")]
PermissionDenied {
message: String,
},
#[error("Invalid argument: {message}")]
InvalidArgument {
message: String,
},
#[error("Resource not available: {resource}")]
ResourceNotAvailable {
resource: String,
},
#[error("Internal error: {message}")]
Internal {
message: String,
},
#[error("Health check error: {message}")]
HealthCheck {
message: String,
},
}
impl Error {
pub fn process<S: Into<String>>(message: S) -> Self {
Self::Process {
message: message.into(),
}
}
pub fn process_not_found<S: Into<String>>(identifier: S) -> Self {
Self::ProcessNotFound {
identifier: identifier.into(),
}
}
pub fn process_already_exists<S: Into<String>>(name: S) -> Self {
Self::ProcessAlreadyExists { name: name.into() }
}
pub fn config<S: Into<String>>(message: S) -> Self {
Self::Config {
message: message.into(),
}
}
pub fn signal<S: Into<String>>(message: S) -> Self {
Self::Signal {
message: message.into(),
}
}
pub fn monitoring<S: Into<String>>(message: S) -> Self {
Self::Monitoring {
message: message.into(),
}
}
pub fn web_server<S: Into<String>>(message: S) -> Self {
Self::WebServer {
message: message.into(),
}
}
pub fn timeout<S: Into<String>>(operation: S) -> Self {
Self::Timeout {
operation: operation.into(),
}
}
pub fn permission_denied<S: Into<String>>(message: S) -> Self {
Self::PermissionDenied {
message: message.into(),
}
}
pub fn invalid_argument<S: Into<String>>(message: S) -> Self {
Self::InvalidArgument {
message: message.into(),
}
}
pub fn resource_not_available<S: Into<String>>(resource: S) -> Self {
Self::ResourceNotAvailable {
resource: resource.into(),
}
}
pub fn internal<S: Into<String>>(message: S) -> Self {
Self::Internal {
message: message.into(),
}
}
pub fn health_check<S: Into<String>>(message: S) -> Self {
Self::HealthCheck {
message: message.into(),
}
}
pub fn is_process_error(&self) -> bool {
matches!(
self,
Error::Process { .. }
| Error::ProcessNotFound { .. }
| Error::ProcessAlreadyExists { .. }
| Error::ProcessAlreadyRunning(_)
| Error::ProcessStartFailed { .. }
| Error::ProcessStopFailed { .. }
)
}
pub fn is_config_error(&self) -> bool {
matches!(self, Error::Config { .. })
}
pub fn is_system_error(&self) -> bool {
match self {
Error::Io(_) => true,
#[cfg(unix)]
Error::System(_) => true,
_ => false,
}
}
pub fn category(&self) -> &'static str {
match self {
Error::Io(_) => "io",
Error::Process { .. } => "process",
Error::ProcessNotFound { .. } => "process_not_found",
Error::ProcessAlreadyExists { .. } => "process_already_exists",
Error::ProcessAlreadyRunning(_) => "process_already_running",
Error::ProcessStartFailed { .. } => "process_start_failed",
Error::ProcessStopFailed { .. } => "process_stop_failed",
Error::Config { .. } => "config",
Error::Serialization(_) => "serialization",
Error::Toml(_) => "toml",
#[cfg(unix)]
Error::System(_) => "system",
Error::Signal { .. } => "signal",
Error::Monitoring { .. } => "monitoring",
Error::WebServer { .. } => "web_server",
Error::Timeout { .. } => "timeout",
Error::PermissionDenied { .. } => "permission_denied",
Error::InvalidArgument { .. } => "invalid_argument",
Error::ResourceNotAvailable { .. } => "resource_not_available",
Error::Internal { .. } => "internal",
Error::HealthCheck { .. } => "health_check",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::io;
#[test]
fn test_error_constructors() {
let err = Error::process("test message");
assert!(matches!(err, Error::Process { .. }));
assert_eq!(err.to_string(), "Process error: test message");
let err = Error::process_not_found("test-process");
assert!(matches!(err, Error::ProcessNotFound { .. }));
assert_eq!(err.to_string(), "Process not found: test-process");
let err = Error::process_already_exists("test-process");
assert!(matches!(err, Error::ProcessAlreadyExists { .. }));
assert_eq!(err.to_string(), "Process already exists: test-process");
let err = Error::config("invalid config");
assert!(matches!(err, Error::Config { .. }));
assert_eq!(err.to_string(), "Configuration error: invalid config");
let err = Error::signal("signal failed");
assert!(matches!(err, Error::Signal { .. }));
assert_eq!(err.to_string(), "Signal error: signal failed");
let err = Error::monitoring("monitoring failed");
assert!(matches!(err, Error::Monitoring { .. }));
assert_eq!(err.to_string(), "Monitoring error: monitoring failed");
let err = Error::web_server("server failed");
assert!(matches!(err, Error::WebServer { .. }));
assert_eq!(err.to_string(), "Web server error: server failed");
let err = Error::timeout("start process");
assert!(matches!(err, Error::Timeout { .. }));
assert_eq!(err.to_string(), "Operation timed out: start process");
let err = Error::permission_denied("access denied");
assert!(matches!(err, Error::PermissionDenied { .. }));
assert_eq!(err.to_string(), "Permission denied: access denied");
let err = Error::invalid_argument("bad arg");
assert!(matches!(err, Error::InvalidArgument { .. }));
assert_eq!(err.to_string(), "Invalid argument: bad arg");
let err = Error::resource_not_available("port 8080");
assert!(matches!(err, Error::ResourceNotAvailable { .. }));
assert_eq!(err.to_string(), "Resource not available: port 8080");
let err = Error::internal("internal failure");
assert!(matches!(err, Error::Internal { .. }));
assert_eq!(err.to_string(), "Internal error: internal failure");
let err = Error::health_check("health check failed");
assert!(matches!(err, Error::HealthCheck { .. }));
assert_eq!(err.to_string(), "Health check error: health check failed");
}
#[test]
fn test_error_from_io() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let err: Error = io_err.into();
assert!(matches!(err, Error::Io(_)));
assert!(err.to_string().contains("file not found"));
}
#[test]
fn test_error_from_serde_json() {
let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
let err: Error = json_err.into();
assert!(matches!(err, Error::Serialization(_)));
}
#[test]
fn test_error_from_toml() {
let toml_err = toml::from_str::<toml::Value>("invalid = toml = syntax").unwrap_err();
let err: Error = toml_err.into();
assert!(matches!(err, Error::Toml(_)));
}
#[test]
fn test_error_is_process_error() {
assert!(Error::process("test").is_process_error());
assert!(Error::process_not_found("test").is_process_error());
assert!(Error::process_already_exists("test").is_process_error());
assert!(Error::ProcessAlreadyRunning("test".to_string()).is_process_error());
assert!(Error::ProcessStartFailed {
name: "test".to_string(),
reason: "failed".to_string()
}
.is_process_error());
assert!(Error::ProcessStopFailed {
name: "test".to_string(),
reason: "failed".to_string()
}
.is_process_error());
assert!(!Error::config("test").is_process_error());
assert!(!Error::signal("test").is_process_error());
}
#[test]
fn test_error_is_config_error() {
assert!(Error::config("test").is_config_error());
assert!(!Error::process("test").is_config_error());
assert!(!Error::signal("test").is_config_error());
}
#[test]
fn test_error_is_system_error() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let err: Error = io_err.into();
assert!(err.is_system_error());
assert!(!Error::config("test").is_system_error());
assert!(!Error::process("test").is_system_error());
}
#[test]
fn test_error_category() {
assert_eq!(Error::process("test").category(), "process");
assert_eq!(
Error::process_not_found("test").category(),
"process_not_found"
);
assert_eq!(
Error::process_already_exists("test").category(),
"process_already_exists"
);
assert_eq!(
Error::ProcessAlreadyRunning("test".to_string()).category(),
"process_already_running"
);
assert_eq!(
Error::ProcessStartFailed {
name: "test".to_string(),
reason: "failed".to_string()
}
.category(),
"process_start_failed"
);
assert_eq!(
Error::ProcessStopFailed {
name: "test".to_string(),
reason: "failed".to_string()
}
.category(),
"process_stop_failed"
);
assert_eq!(Error::config("test").category(), "config");
assert_eq!(Error::signal("test").category(), "signal");
assert_eq!(Error::monitoring("test").category(), "monitoring");
assert_eq!(Error::web_server("test").category(), "web_server");
assert_eq!(Error::timeout("test").category(), "timeout");
assert_eq!(
Error::permission_denied("test").category(),
"permission_denied"
);
assert_eq!(
Error::invalid_argument("test").category(),
"invalid_argument"
);
assert_eq!(
Error::resource_not_available("test").category(),
"resource_not_available"
);
assert_eq!(Error::internal("test").category(), "internal");
assert_eq!(Error::health_check("test").category(), "health_check");
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let err: Error = io_err.into();
assert_eq!(err.category(), "io");
let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
let err: Error = json_err.into();
assert_eq!(err.category(), "serialization");
let toml_err = toml::from_str::<toml::Value>("invalid = toml = syntax").unwrap_err();
let err: Error = toml_err.into();
assert_eq!(err.category(), "toml");
}
#[test]
fn test_result_type_alias() {
fn test_function() -> Result<String> {
Ok("success".to_string())
}
fn test_function_error() -> Result<String> {
Err(Error::process("test error"))
}
assert!(test_function().is_ok());
assert!(test_function_error().is_err());
}
#[test]
fn test_error_debug_format() {
let err = Error::process("test message");
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("Process"));
assert!(debug_str.contains("test message"));
}
#[test]
fn test_error_display_format() {
let err = Error::ProcessStartFailed {
name: "myapp".to_string(),
reason: "command not found".to_string(),
};
assert_eq!(
err.to_string(),
"Failed to start process myapp: command not found"
);
let err = Error::ProcessStopFailed {
name: "myapp".to_string(),
reason: "process not responding".to_string(),
};
assert_eq!(
err.to_string(),
"Failed to stop process myapp: process not responding"
);
}
}