use_process_status/
lib.rs1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7use use_process_id::ProcessId;
8
9#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
11pub enum ProcessState {
12 Created,
14 Running,
16 Exited,
18 Failed,
20 Unknown,
22}
23
24impl fmt::Display for ProcessState {
25 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 Self::Created => formatter.write_str("created"),
28 Self::Running => formatter.write_str("running"),
29 Self::Exited => formatter.write_str("exited"),
30 Self::Failed => formatter.write_str("failed"),
31 Self::Unknown => formatter.write_str("unknown"),
32 }
33 }
34}
35
36impl FromStr for ProcessState {
37 type Err = ProcessStateParseError;
38
39 fn from_str(value: &str) -> Result<Self, Self::Err> {
40 let trimmed = value.trim();
41
42 if trimmed.is_empty() {
43 return Err(ProcessStateParseError::Empty);
44 }
45
46 match trimmed.to_ascii_lowercase().as_str() {
47 "create" | "created" => Ok(Self::Created),
48 "run" | "running" => Ok(Self::Running),
49 "exit" | "exited" => Ok(Self::Exited),
50 "fail" | "failed" => Ok(Self::Failed),
51 "unknown" => Ok(Self::Unknown),
52 _ => Err(ProcessStateParseError::Unknown),
53 }
54 }
55}
56
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59pub enum ProcessStateParseError {
60 Empty,
62 Unknown,
64}
65
66impl fmt::Display for ProcessStateParseError {
67 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Self::Empty => formatter.write_str("process state cannot be empty"),
70 Self::Unknown => formatter.write_str("unknown process state"),
71 }
72 }
73}
74
75impl Error for ProcessStateParseError {}
76
77#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
79pub struct ProcessStatus {
80 state: ProcessState,
81 status_code: Option<i32>,
82 message: Option<String>,
83}
84
85impl ProcessStatus {
86 #[must_use]
88 pub const fn new(state: ProcessState) -> Self {
89 Self {
90 state,
91 status_code: None,
92 message: None,
93 }
94 }
95
96 #[must_use]
98 pub const fn state(&self) -> ProcessState {
99 self.state
100 }
101
102 #[must_use]
104 pub const fn status_code(&self) -> Option<i32> {
105 self.status_code
106 }
107
108 #[must_use]
110 pub fn message(&self) -> Option<&str> {
111 self.message.as_deref()
112 }
113
114 #[must_use]
116 pub const fn with_status_code(mut self, status_code: i32) -> Self {
117 self.status_code = Some(status_code);
118 self
119 }
120
121 #[must_use]
123 pub fn with_message(mut self, message: impl Into<String>) -> Self {
124 self.message = Some(message.into());
125 self
126 }
127}
128
129#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
131pub struct ProcessOutcome {
132 process_id: Option<ProcessId>,
133 status: ProcessStatus,
134}
135
136impl ProcessOutcome {
137 #[must_use]
139 pub const fn new(process_id: Option<ProcessId>, status: ProcessStatus) -> Self {
140 Self { process_id, status }
141 }
142
143 #[must_use]
145 pub const fn for_process(process_id: ProcessId, status: ProcessStatus) -> Self {
146 Self::new(Some(process_id), status)
147 }
148
149 #[must_use]
151 pub const fn without_process(status: ProcessStatus) -> Self {
152 Self::new(None, status)
153 }
154
155 #[must_use]
157 pub const fn process_id(&self) -> Option<ProcessId> {
158 self.process_id
159 }
160
161 #[must_use]
163 pub const fn status(&self) -> &ProcessStatus {
164 &self.status
165 }
166
167 #[must_use]
169 pub const fn state(&self) -> ProcessState {
170 self.status.state()
171 }
172
173 #[must_use]
175 pub const fn status_code(&self) -> Option<i32> {
176 self.status.status_code()
177 }
178
179 #[must_use]
181 pub fn message(&self) -> Option<&str> {
182 self.status.message()
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::{ProcessOutcome, ProcessState, ProcessStateParseError, ProcessStatus};
189 use use_process_id::ProcessId;
190
191 #[test]
192 fn parses_and_displays_process_states() -> Result<(), ProcessStateParseError> {
193 assert_eq!("created".parse::<ProcessState>()?, ProcessState::Created);
194 assert_eq!("run".parse::<ProcessState>()?, ProcessState::Running);
195 assert_eq!("exit".parse::<ProcessState>()?, ProcessState::Exited);
196 assert_eq!("fail".parse::<ProcessState>()?, ProcessState::Failed);
197 assert_eq!(ProcessState::Unknown.to_string(), "unknown");
198 Ok(())
199 }
200
201 #[test]
202 fn rejects_empty_or_unknown_process_states() {
203 assert_eq!(
204 "".parse::<ProcessState>(),
205 Err(ProcessStateParseError::Empty)
206 );
207 assert_eq!(
208 "sleeping".parse::<ProcessState>(),
209 Err(ProcessStateParseError::Unknown)
210 );
211 }
212
213 #[test]
214 fn status_stores_plain_metadata() {
215 let status = ProcessStatus::new(ProcessState::Failed)
216 .with_status_code(1)
217 .with_message("process failed");
218
219 assert_eq!(status.state(), ProcessState::Failed);
220 assert_eq!(status.status_code(), Some(1));
221 assert_eq!(status.message(), Some("process failed"));
222 }
223
224 #[test]
225 fn outcome_stores_optional_process_identity() {
226 let process_id = ProcessId::new(42).unwrap();
227 let status = ProcessStatus::new(ProcessState::Exited).with_status_code(0);
228 let outcome = ProcessOutcome::for_process(process_id, status);
229
230 assert_eq!(outcome.process_id(), Some(process_id));
231 assert_eq!(outcome.state(), ProcessState::Exited);
232 assert_eq!(outcome.status_code(), Some(0));
233 }
234}