#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::error::Error;
use use_process_id::ProcessId;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ProcessState {
Created,
Running,
Exited,
Failed,
Unknown,
}
impl fmt::Display for ProcessState {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Created => formatter.write_str("created"),
Self::Running => formatter.write_str("running"),
Self::Exited => formatter.write_str("exited"),
Self::Failed => formatter.write_str("failed"),
Self::Unknown => formatter.write_str("unknown"),
}
}
}
impl FromStr for ProcessState {
type Err = ProcessStateParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(ProcessStateParseError::Empty);
}
match trimmed.to_ascii_lowercase().as_str() {
"create" | "created" => Ok(Self::Created),
"run" | "running" => Ok(Self::Running),
"exit" | "exited" => Ok(Self::Exited),
"fail" | "failed" => Ok(Self::Failed),
"unknown" => Ok(Self::Unknown),
_ => Err(ProcessStateParseError::Unknown),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProcessStateParseError {
Empty,
Unknown,
}
impl fmt::Display for ProcessStateParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("process state cannot be empty"),
Self::Unknown => formatter.write_str("unknown process state"),
}
}
}
impl Error for ProcessStateParseError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ProcessStatus {
state: ProcessState,
status_code: Option<i32>,
message: Option<String>,
}
impl ProcessStatus {
#[must_use]
pub const fn new(state: ProcessState) -> Self {
Self {
state,
status_code: None,
message: None,
}
}
#[must_use]
pub const fn state(&self) -> ProcessState {
self.state
}
#[must_use]
pub const fn status_code(&self) -> Option<i32> {
self.status_code
}
#[must_use]
pub fn message(&self) -> Option<&str> {
self.message.as_deref()
}
#[must_use]
pub const fn with_status_code(mut self, status_code: i32) -> Self {
self.status_code = Some(status_code);
self
}
#[must_use]
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ProcessOutcome {
process_id: Option<ProcessId>,
status: ProcessStatus,
}
impl ProcessOutcome {
#[must_use]
pub const fn new(process_id: Option<ProcessId>, status: ProcessStatus) -> Self {
Self { process_id, status }
}
#[must_use]
pub const fn for_process(process_id: ProcessId, status: ProcessStatus) -> Self {
Self::new(Some(process_id), status)
}
#[must_use]
pub const fn without_process(status: ProcessStatus) -> Self {
Self::new(None, status)
}
#[must_use]
pub const fn process_id(&self) -> Option<ProcessId> {
self.process_id
}
#[must_use]
pub const fn status(&self) -> &ProcessStatus {
&self.status
}
#[must_use]
pub const fn state(&self) -> ProcessState {
self.status.state()
}
#[must_use]
pub const fn status_code(&self) -> Option<i32> {
self.status.status_code()
}
#[must_use]
pub fn message(&self) -> Option<&str> {
self.status.message()
}
}
#[cfg(test)]
mod tests {
use super::{ProcessOutcome, ProcessState, ProcessStateParseError, ProcessStatus};
use use_process_id::ProcessId;
#[test]
fn parses_and_displays_process_states() -> Result<(), ProcessStateParseError> {
assert_eq!("created".parse::<ProcessState>()?, ProcessState::Created);
assert_eq!("run".parse::<ProcessState>()?, ProcessState::Running);
assert_eq!("exit".parse::<ProcessState>()?, ProcessState::Exited);
assert_eq!("fail".parse::<ProcessState>()?, ProcessState::Failed);
assert_eq!(ProcessState::Unknown.to_string(), "unknown");
Ok(())
}
#[test]
fn rejects_empty_or_unknown_process_states() {
assert_eq!(
"".parse::<ProcessState>(),
Err(ProcessStateParseError::Empty)
);
assert_eq!(
"sleeping".parse::<ProcessState>(),
Err(ProcessStateParseError::Unknown)
);
}
#[test]
fn status_stores_plain_metadata() {
let status = ProcessStatus::new(ProcessState::Failed)
.with_status_code(1)
.with_message("process failed");
assert_eq!(status.state(), ProcessState::Failed);
assert_eq!(status.status_code(), Some(1));
assert_eq!(status.message(), Some("process failed"));
}
#[test]
fn outcome_stores_optional_process_identity() {
let process_id = ProcessId::new(42).unwrap();
let status = ProcessStatus::new(ProcessState::Exited).with_status_code(0);
let outcome = ProcessOutcome::for_process(process_id, status);
assert_eq!(outcome.process_id(), Some(process_id));
assert_eq!(outcome.state(), ProcessState::Exited);
assert_eq!(outcome.status_code(), Some(0));
}
}