use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SysprimsError {
#[error("Invalid argument: {message}")]
InvalidArgument {
message: String,
},
#[error("Failed to spawn process: {source}")]
SpawnFailed {
#[source]
source: io::Error,
},
#[error("Operation timed out")]
Timeout,
#[error("Permission denied for '{operation}' on PID {pid}")]
PermissionDenied {
pid: u32,
operation: String,
},
#[error("Process {pid} not found")]
NotFound {
pid: u32,
},
#[error("Command '{command}' not found")]
NotFoundCommand {
command: String,
},
#[error("Permission denied: cannot execute '{command}'")]
PermissionDeniedCommand {
command: String,
},
#[error("Permission denied for '{operation}' on path '{path}'")]
PermissionDeniedPath {
path: String,
operation: String,
},
#[error("Operation '{feature}' not supported on {platform}")]
NotSupported {
feature: String,
platform: String,
},
#[error("Failed to create process group: {message}")]
GroupCreationFailed {
message: String,
},
#[error("System error: {message} (errno: {errno})")]
System {
message: String,
errno: i32,
},
#[error("Internal error: {message}")]
Internal {
message: String,
},
}
impl SysprimsError {
pub fn error_code(&self) -> i32 {
match self {
SysprimsError::InvalidArgument { .. } => 1,
SysprimsError::SpawnFailed { .. } => 2,
SysprimsError::Timeout => 3,
SysprimsError::PermissionDenied { .. } => 4,
SysprimsError::PermissionDeniedCommand { .. } => 4,
SysprimsError::PermissionDeniedPath { .. } => 4,
SysprimsError::NotFound { .. } => 5,
SysprimsError::NotFoundCommand { .. } => 5,
SysprimsError::NotSupported { .. } => 6,
SysprimsError::GroupCreationFailed { .. } => 7,
SysprimsError::System { .. } => 8,
SysprimsError::Internal { .. } => 99,
}
}
}
impl SysprimsError {
pub fn invalid_argument(message: impl Into<String>) -> Self {
SysprimsError::InvalidArgument {
message: message.into(),
}
}
pub fn spawn_failed_io(source: io::Error) -> Self {
SysprimsError::SpawnFailed { source }
}
pub fn permission_denied(pid: u32, operation: impl Into<String>) -> Self {
SysprimsError::PermissionDenied {
pid,
operation: operation.into(),
}
}
pub fn not_found(pid: u32) -> Self {
SysprimsError::NotFound { pid }
}
pub fn not_found_command(command: impl Into<String>) -> Self {
SysprimsError::NotFoundCommand {
command: command.into(),
}
}
pub fn permission_denied_command(command: impl Into<String>) -> Self {
SysprimsError::PermissionDeniedCommand {
command: command.into(),
}
}
pub fn permission_denied_path(path: impl Into<String>, operation: impl Into<String>) -> Self {
SysprimsError::PermissionDeniedPath {
path: path.into(),
operation: operation.into(),
}
}
pub fn spawn_failed(command: impl Into<String>, reason: impl Into<String>) -> Self {
let msg = format!("{}: {}", command.into(), reason.into());
SysprimsError::SpawnFailed {
source: io::Error::other(msg),
}
}
pub fn not_supported(feature: impl Into<String>, platform: impl Into<String>) -> Self {
SysprimsError::NotSupported {
feature: feature.into(),
platform: platform.into(),
}
}
pub fn group_creation_failed(message: impl Into<String>) -> Self {
SysprimsError::GroupCreationFailed {
message: message.into(),
}
}
pub fn system(message: impl Into<String>, errno: i32) -> Self {
SysprimsError::System {
message: message.into(),
errno,
}
}
pub fn internal(message: impl Into<String>) -> Self {
SysprimsError::Internal {
message: message.into(),
}
}
}
impl From<io::Error> for SysprimsError {
fn from(source: io::Error) -> Self {
match source.kind() {
io::ErrorKind::NotFound => SysprimsError::Internal {
message: format!("IO not found: {}", source),
},
io::ErrorKind::PermissionDenied => SysprimsError::Internal {
message: format!("IO permission denied: {}", source),
},
_ => SysprimsError::SpawnFailed { source },
}
}
}
pub type SysprimsResult<T> = Result<T, SysprimsError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = SysprimsError::invalid_argument("pid must be > 0");
assert_eq!(err.to_string(), "Invalid argument: pid must be > 0");
let err = SysprimsError::permission_denied(1234, "terminate");
assert_eq!(
err.to_string(),
"Permission denied for 'terminate' on PID 1234"
);
let err = SysprimsError::not_found(5678);
assert_eq!(err.to_string(), "Process 5678 not found");
let err = SysprimsError::not_supported("killpg", "windows");
assert_eq!(
err.to_string(),
"Operation 'killpg' not supported on windows"
);
let err = SysprimsError::Timeout;
assert_eq!(err.to_string(), "Operation timed out");
}
#[test]
fn test_error_codes() {
assert_eq!(SysprimsError::invalid_argument("").error_code(), 1);
assert_eq!(
SysprimsError::spawn_failed_io(io::Error::other("test")).error_code(),
2
);
assert_eq!(SysprimsError::Timeout.error_code(), 3);
assert_eq!(SysprimsError::permission_denied(0, "").error_code(), 4);
assert_eq!(SysprimsError::not_found(0).error_code(), 5);
assert_eq!(SysprimsError::not_supported("", "").error_code(), 6);
assert_eq!(SysprimsError::group_creation_failed("").error_code(), 7);
assert_eq!(SysprimsError::system("", 0).error_code(), 8);
assert_eq!(SysprimsError::internal("").error_code(), 99);
}
#[test]
fn test_spawn_failed_source() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "command not found");
let err = SysprimsError::spawn_failed_io(io_err);
match err {
SysprimsError::SpawnFailed { ref source } => {
assert_eq!(source.kind(), io::ErrorKind::NotFound);
}
_ => panic!("Expected SpawnFailed"),
}
}
#[test]
fn test_pid_is_u32() {
let err = SysprimsError::permission_denied(u32::MAX, "signal");
match err {
SysprimsError::PermissionDenied { pid, .. } => {
assert_eq!(pid, u32::MAX);
}
_ => panic!("Expected PermissionDenied"),
}
let err = SysprimsError::not_found(u32::MAX);
match err {
SysprimsError::NotFound { pid } => {
assert_eq!(pid, u32::MAX);
}
_ => panic!("Expected NotFound"),
}
}
#[test]
fn test_io_error_conversion() {
let io_err = io::Error::other("test error");
let sysprims_err: SysprimsError = io_err.into();
match sysprims_err {
SysprimsError::SpawnFailed { source } => {
assert_eq!(source.kind(), io::ErrorKind::Other);
}
_ => panic!("Expected SpawnFailed from IO error"),
}
}
}