1use core::fmt;
7use core::str::FromStr;
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15#[non_exhaustive]
16pub enum PidError {
17 InvalidValue(u32),
23 TooLarge(u32),
30}
31
32impl fmt::Display for PidError {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 Self::InvalidValue(v) => write!(f, "invalid PID value: {v} (must be positive)"),
36 Self::TooLarge(v) => write!(f, "PID too large: {v}"),
37 }
38 }
39}
40
41#[cfg(feature = "std")]
42impl std::error::Error for PidError {}
43
44#[repr(transparent)]
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
60#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61pub struct Pid(u32);
62
63impl Pid {
64 #[cfg(target_os = "linux")]
69 pub const MAX: u32 = 4_194_303;
70
71 #[cfg(not(target_os = "linux"))]
76 pub const MAX: u32 = u32::MAX;
77
78 pub const fn new(value: u32) -> Result<Self, PidError> {
84 if value == 0 {
85 return Err(PidError::InvalidValue(value));
86 }
87 #[allow(clippy::absurd_extreme_comparisons)]
88 if value > Self::MAX {
89 return Err(PidError::TooLarge(value));
90 }
91 Ok(Self(value))
92 }
93
94 #[must_use]
100 #[inline]
101 pub const fn new_unchecked(value: u32) -> Self {
102 Self(value)
103 }
104
105 #[must_use]
107 #[inline]
108 pub const fn as_u32(&self) -> u32 {
109 self.0
110 }
111
112 #[must_use]
114 #[inline]
115 pub const fn is_init(&self) -> bool {
116 self.0 == 1
117 }
118
119 #[must_use]
123 #[inline]
124 pub const fn is_system_process(&self) -> bool {
125 self.0 < 100
126 }
127}
128
129impl TryFrom<u32> for Pid {
130 type Error = PidError;
131
132 fn try_from(value: u32) -> Result<Self, Self::Error> {
133 Self::new(value)
134 }
135}
136
137impl TryFrom<i32> for Pid {
138 type Error = PidError;
139
140 fn try_from(value: i32) -> Result<Self, Self::Error> {
141 if value <= 0 {
142 #[allow(clippy::cast_sign_loss)]
143 return Err(PidError::InvalidValue(value as u32));
144 }
145 #[allow(clippy::cast_sign_loss)]
146 Self::new(value as u32)
147 }
148}
149
150impl FromStr for Pid {
151 type Err = PidError;
152
153 fn from_str(s: &str) -> Result<Self, Self::Err> {
154 let value = s.parse::<u32>().map_err(|_| PidError::InvalidValue(0))?;
155 Self::new(value)
156 }
157}
158
159impl fmt::Display for Pid {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 write!(f, "{}", self.0)
162 }
163}
164
165#[cfg(feature = "arbitrary")]
166impl<'a> arbitrary::Arbitrary<'a> for Pid {
167 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
168 let value = u32::arbitrary(u)?;
169 #[allow(clippy::absurd_extreme_comparisons)]
170 if value == 0 || value > Self::MAX {
171 Ok(Self(1))
172 } else {
173 Ok(Self(value))
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_new_valid() {
184 let pid = Pid::new(1).unwrap();
185 assert_eq!(pid.as_u32(), 1);
186 let pid = Pid::new(100).unwrap();
187 assert_eq!(pid.as_u32(), 100);
188 }
189
190 #[test]
191 fn test_new_zero() {
192 assert!(matches!(Pid::new(0), Err(PidError::InvalidValue(0))));
193 }
194
195 #[test]
196 #[cfg(target_os = "linux")]
197 fn test_new_too_large() {
198 assert!(matches!(Pid::new(4_194_304), Err(PidError::TooLarge(_))));
199 }
200
201 #[test]
202 #[cfg(not(target_os = "linux"))]
203 #[ignore = "On non-Linux platforms, MAX equals u32::MAX so no value can exceed it"]
204 fn test_new_too_large() {
205 assert!(matches!(Pid::new(u32::MAX), Err(PidError::TooLarge(_))));
206 }
207
208 #[test]
209 fn test_try_from_u32() {
210 let pid: Pid = 100u32.try_into().unwrap();
211 assert_eq!(pid.as_u32(), 100);
212 }
213
214 #[test]
215 fn test_try_from_i32_valid() {
216 let pid: Pid = 100i32.try_into().unwrap();
217 assert_eq!(pid.as_u32(), 100);
218 }
219
220 #[test]
221 fn test_try_from_i32_zero() {
222 assert!(matches!(
223 TryInto::<Pid>::try_into(0i32),
224 Err(PidError::InvalidValue(0))
225 ));
226 }
227
228 #[test]
229 fn test_try_from_i32_negative() {
230 assert!(matches!(
231 TryInto::<Pid>::try_into(-1i32),
232 Err(PidError::InvalidValue(_))
233 ));
234 }
235
236 #[test]
237 fn test_from_str() {
238 let pid: Pid = "100".parse().unwrap();
239 assert_eq!(pid.as_u32(), 100);
240 }
241
242 #[test]
243 fn test_from_str_error() {
244 assert!("0".parse::<Pid>().is_err());
245 assert!("abc".parse::<Pid>().is_err());
246 }
247
248 #[test]
249 fn test_display() {
250 let pid = Pid::new(100).unwrap();
251 assert_eq!(format!("{}", pid), "100");
252 }
253
254 #[test]
255 fn test_is_init() {
256 let pid = Pid::new(1).unwrap();
257 assert!(pid.is_init());
258 let pid = Pid::new(100).unwrap();
259 assert!(!pid.is_init());
260 }
261
262 #[test]
263 fn test_is_system_process() {
264 let pid = Pid::new(1).unwrap();
265 assert!(pid.is_system_process());
266 let pid = Pid::new(50).unwrap();
267 assert!(pid.is_system_process());
268 let pid = Pid::new(100).unwrap();
269 assert!(!pid.is_system_process());
270 }
271
272 #[test]
273 fn test_clone() {
274 let pid = Pid::new(100).unwrap();
275 let pid2 = pid.clone();
276 assert_eq!(pid, pid2);
277 }
278
279 #[test]
280 fn test_equality() {
281 let p1 = Pid::new(100).unwrap();
282 let p2 = Pid::new(100).unwrap();
283 let p3 = Pid::new(200).unwrap();
284 assert_eq!(p1, p2);
285 assert_ne!(p1, p3);
286 }
287
288 #[test]
289 fn test_ordering() {
290 let p1 = Pid::new(100).unwrap();
291 let p2 = Pid::new(200).unwrap();
292 assert!(p1 < p2);
293 assert!(p2 > p1);
294 }
295
296 #[test]
297 fn test_new_unchecked() {
298 let pid = Pid::new_unchecked(100);
299 assert_eq!(pid.as_u32(), 100);
300 }
301}