torrust_tracker_deployer_lib/infrastructure/persistence/filesystem/process_id.rs
1//! Process ID type for cross-platform process management
2//!
3//! This module provides a type-safe wrapper around process IDs (PIDs) with
4//! cross-platform support for Unix and Windows systems.
5//!
6//! # Design
7//!
8//! The `ProcessId` type is a newtype wrapper around `u32` that provides:
9//! - Type safety: PIDs can't be confused with other numeric types
10//! - Cross-platform compatibility: Works on both Unix and Windows
11//! - Process liveness checking: Can verify if a process is still running
12//!
13//! # Usage
14//!
15//! ```rust
16//! use torrust_tracker_deployer_lib::infrastructure::persistence::filesystem::process_id::ProcessId;
17//!
18//! // Get the current process ID
19//! let current_pid = ProcessId::current();
20//! println!("Current process: {}", current_pid);
21//!
22//! // Check if a process is alive
23//! assert!(current_pid.is_alive());
24//!
25//! // Parse from string (useful when reading from files)
26//! let pid: ProcessId = "12345".parse().expect("Invalid PID");
27//! ```
28
29use std::process;
30
31use super::platform;
32
33/// Process ID newtype for type safety
34///
35/// Wraps a u32 process ID to provide type safety and prevent accidental misuse.
36/// This ensures PIDs are only used in appropriate contexts and makes the code
37/// more self-documenting.
38///
39/// # Examples
40///
41/// ```rust
42/// use torrust_tracker_deployer_lib::infrastructure::persistence::filesystem::process_id::ProcessId;
43///
44/// // Get current process ID
45/// let pid = ProcessId::current();
46///
47/// // Create from raw value
48/// let pid = ProcessId::from_raw(12345);
49///
50/// // Get raw value
51/// let raw: u32 = pid.as_u32();
52///
53/// // Parse from string
54/// let pid: ProcessId = "12345".parse().unwrap();
55/// ```
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct ProcessId(u32);
58
59impl ProcessId {
60 /// Get the current process ID
61 ///
62 /// # Examples
63 ///
64 /// ```rust
65 /// use torrust_tracker_deployer_lib::infrastructure::persistence::filesystem::process_id::ProcessId;
66 ///
67 /// let current = ProcessId::current();
68 /// assert!(current.is_alive());
69 /// ```
70 #[must_use]
71 pub fn current() -> Self {
72 Self(process::id())
73 }
74
75 /// Create a `ProcessId` from a raw u32
76 ///
77 /// # Examples
78 ///
79 /// ```rust
80 /// use torrust_tracker_deployer_lib::infrastructure::persistence::filesystem::process_id::ProcessId;
81 ///
82 /// let pid = ProcessId::from_raw(12345);
83 /// assert_eq!(pid.as_u32(), 12345);
84 /// ```
85 #[must_use]
86 pub fn from_raw(pid: u32) -> Self {
87 Self(pid)
88 }
89
90 /// Get the raw u32 value
91 ///
92 /// # Examples
93 ///
94 /// ```rust
95 /// use torrust_tracker_deployer_lib::infrastructure::persistence::filesystem::process_id::ProcessId;
96 ///
97 /// let pid = ProcessId::from_raw(12345);
98 /// assert_eq!(pid.as_u32(), 12345);
99 /// ```
100 #[must_use]
101 pub fn as_u32(&self) -> u32 {
102 self.0
103 }
104
105 /// Check if this process is currently alive
106 ///
107 /// Uses platform-specific methods to check if the process exists.
108 ///
109 /// # Examples
110 ///
111 /// ```rust
112 /// use torrust_tracker_deployer_lib::infrastructure::persistence::filesystem::process_id::ProcessId;
113 ///
114 /// let current = ProcessId::current();
115 /// assert!(current.is_alive());
116 ///
117 /// let fake_pid = ProcessId::from_raw(999_999);
118 /// assert!(!fake_pid.is_alive());
119 /// ```
120 #[must_use]
121 pub fn is_alive(&self) -> bool {
122 platform::is_process_alive(*self)
123 }
124}
125
126impl std::fmt::Display for ProcessId {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 write!(f, "{}", self.0)
129 }
130}
131
132impl std::str::FromStr for ProcessId {
133 type Err = std::num::ParseIntError;
134
135 fn from_str(s: &str) -> Result<Self, Self::Err> {
136 Ok(Self(s.parse()?))
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn it_should_get_current_process_id() {
146 let pid = ProcessId::current();
147 assert!(pid.as_u32() > 0, "Current PID should be positive");
148 }
149
150 #[test]
151 fn it_should_create_from_raw_value() {
152 let pid = ProcessId::from_raw(12345);
153 assert_eq!(pid.as_u32(), 12345);
154 }
155
156 #[test]
157 fn it_should_parse_from_string() {
158 let pid: ProcessId = "12345".parse().expect("Should parse valid PID");
159 assert_eq!(pid.as_u32(), 12345);
160 }
161
162 #[test]
163 fn it_should_fail_to_parse_invalid_string() {
164 let result: Result<ProcessId, _> = "not-a-number".parse();
165 assert!(result.is_err(), "Should fail to parse invalid PID");
166 }
167
168 #[test]
169 fn it_should_display_as_string() {
170 let pid = ProcessId::from_raw(12345);
171 assert_eq!(pid.to_string(), "12345");
172 }
173
174 #[test]
175 fn it_should_detect_current_process_as_alive() {
176 let current = ProcessId::current();
177 assert!(current.is_alive(), "Current process should always be alive");
178 }
179
180 #[test]
181 fn it_should_detect_fake_process_as_dead() {
182 let fake_pid = ProcessId::from_raw(999_999);
183 assert!(!fake_pid.is_alive(), "Fake PID 999999 should not be alive");
184 }
185
186 #[test]
187 fn it_should_implement_equality() {
188 let pid1 = ProcessId::from_raw(12345);
189 let pid2 = ProcessId::from_raw(12345);
190 let pid3 = ProcessId::from_raw(67890);
191
192 assert_eq!(pid1, pid2);
193 assert_ne!(pid1, pid3);
194 }
195
196 #[test]
197 fn it_should_be_copyable() {
198 let pid1 = ProcessId::from_raw(12345);
199 let pid2 = pid1; // Copy
200 assert_eq!(pid1, pid2);
201 }
202}