Skip to main content

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}