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]
109 #[inline]
110 pub const fn new_unchecked(value: u32) -> Self {
111 Self(value)
112 }
113
114 #[must_use]
116 #[inline]
117 pub const fn as_u32(&self) -> u32 {
118 self.0
119 }
120
121 #[must_use]
123 #[inline]
124 pub const fn is_init(&self) -> bool {
125 self.0 == 1
126 }
127
128 #[must_use]
132 #[inline]
133 pub const fn is_system_process(&self) -> bool {
134 self.0 < 100
135 }
136}
137
138impl TryFrom<u32> for Pid {
139 type Error = PidError;
140
141 fn try_from(value: u32) -> Result<Self, Self::Error> {
142 Self::new(value)
143 }
144}
145
146impl TryFrom<i32> for Pid {
147 type Error = PidError;
148
149 fn try_from(value: i32) -> Result<Self, Self::Error> {
150 if value <= 0 {
151 #[allow(clippy::cast_sign_loss)]
152 return Err(PidError::InvalidValue(value as u32));
153 }
154 #[allow(clippy::cast_sign_loss)]
155 Self::new(value as u32)
156 }
157}
158
159impl FromStr for Pid {
160 type Err = PidError;
161
162 fn from_str(s: &str) -> Result<Self, Self::Err> {
163 let value = s.parse::<u32>().map_err(|_| PidError::InvalidValue(0))?;
164 Self::new(value)
165 }
166}
167
168impl fmt::Display for Pid {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 write!(f, "{}", self.0)
171 }
172}
173
174#[cfg(feature = "arbitrary")]
175impl<'a> arbitrary::Arbitrary<'a> for Pid {
176 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
177 let value = u32::arbitrary(u)?;
178 #[allow(clippy::absurd_extreme_comparisons)]
179 if value == 0 || value > Self::MAX {
180 Ok(Self(1))
181 } else {
182 Ok(Self(value))
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_new_valid() {
193 let pid = Pid::new(1).unwrap();
194 assert_eq!(pid.as_u32(), 1);
195 let pid = Pid::new(100).unwrap();
196 assert_eq!(pid.as_u32(), 100);
197 }
198
199 #[test]
200 fn test_new_zero() {
201 assert!(matches!(Pid::new(0), Err(PidError::InvalidValue(0))));
202 }
203
204 #[test]
205 #[cfg(target_os = "linux")]
206 fn test_new_too_large() {
207 assert!(matches!(Pid::new(4_194_304), Err(PidError::TooLarge(_))));
208 }
209
210 #[test]
211 #[cfg(not(target_os = "linux"))]
212 #[ignore = "On non-Linux platforms, MAX equals u32::MAX so no value can exceed it"]
213 fn test_new_too_large() {
214 assert!(matches!(Pid::new(u32::MAX), Err(PidError::TooLarge(_))));
215 }
216
217 #[test]
218 fn test_try_from_u32() {
219 let pid: Pid = 100u32.try_into().unwrap();
220 assert_eq!(pid.as_u32(), 100);
221 }
222
223 #[test]
224 fn test_try_from_i32_valid() {
225 let pid: Pid = 100i32.try_into().unwrap();
226 assert_eq!(pid.as_u32(), 100);
227 }
228
229 #[test]
230 fn test_try_from_i32_zero() {
231 assert!(matches!(
232 TryInto::<Pid>::try_into(0i32),
233 Err(PidError::InvalidValue(0))
234 ));
235 }
236
237 #[test]
238 fn test_try_from_i32_negative() {
239 assert!(matches!(
240 TryInto::<Pid>::try_into(-1i32),
241 Err(PidError::InvalidValue(_))
242 ));
243 }
244
245 #[test]
246 fn test_from_str() {
247 let pid: Pid = "100".parse().unwrap();
248 assert_eq!(pid.as_u32(), 100);
249 }
250
251 #[test]
252 fn test_from_str_error() {
253 assert!("0".parse::<Pid>().is_err());
254 assert!("abc".parse::<Pid>().is_err());
255 }
256
257 #[test]
258 fn test_display() {
259 let pid = Pid::new(100).unwrap();
260 assert_eq!(format!("{}", pid), "100");
261 }
262
263 #[test]
264 fn test_is_init() {
265 let pid = Pid::new(1).unwrap();
266 assert!(pid.is_init());
267 let pid = Pid::new(100).unwrap();
268 assert!(!pid.is_init());
269 }
270
271 #[test]
272 fn test_is_system_process() {
273 let pid = Pid::new(1).unwrap();
274 assert!(pid.is_system_process());
275 let pid = Pid::new(50).unwrap();
276 assert!(pid.is_system_process());
277 let pid = Pid::new(100).unwrap();
278 assert!(!pid.is_system_process());
279 }
280
281 #[test]
282 fn test_clone() {
283 let pid = Pid::new(100).unwrap();
284 let pid2 = pid.clone();
285 assert_eq!(pid, pid2);
286 }
287
288 #[test]
289 fn test_equality() {
290 let p1 = Pid::new(100).unwrap();
291 let p2 = Pid::new(100).unwrap();
292 let p3 = Pid::new(200).unwrap();
293 assert_eq!(p1, p2);
294 assert_ne!(p1, p3);
295 }
296
297 #[test]
298 fn test_ordering() {
299 let p1 = Pid::new(100).unwrap();
300 let p2 = Pid::new(200).unwrap();
301 assert!(p1 < p2);
302 assert!(p2 > p1);
303 }
304
305 #[test]
306 fn test_new_unchecked() {
307 let pid = Pid::new_unchecked(100);
308 assert_eq!(pid.as_u32(), 100);
309 }
310}