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")]
66 pub const MAX: u32 = 4_194_303;
67
68 #[cfg(not(target_os = "linux"))]
69 pub const MAX: u32 = u32::MAX;
70
71 pub const fn new(value: u32) -> Result<Self, PidError> {
77 if value == 0 {
78 return Err(PidError::InvalidValue(value));
79 }
80 if value > Self::MAX {
81 return Err(PidError::TooLarge(value));
82 }
83 Ok(Self(value))
84 }
85
86 #[must_use]
92 #[inline]
93 pub const fn new_unchecked(value: u32) -> Self {
94 Self(value)
95 }
96
97 #[must_use]
99 #[inline]
100 pub const fn as_u32(&self) -> u32 {
101 self.0
102 }
103
104 #[must_use]
106 #[inline]
107 pub const fn is_init(&self) -> bool {
108 self.0 == 1
109 }
110
111 #[must_use]
115 #[inline]
116 pub const fn is_system_process(&self) -> bool {
117 self.0 < 100
118 }
119}
120
121impl TryFrom<u32> for Pid {
122 type Error = PidError;
123
124 fn try_from(value: u32) -> Result<Self, Self::Error> {
125 Self::new(value)
126 }
127}
128
129impl TryFrom<i32> for Pid {
130 type Error = PidError;
131
132 fn try_from(value: i32) -> Result<Self, Self::Error> {
133 if value <= 0 {
134 #[allow(clippy::cast_sign_loss)]
135 return Err(PidError::InvalidValue(value as u32));
136 }
137 #[allow(clippy::cast_sign_loss)]
138 Self::new(value as u32)
139 }
140}
141
142impl FromStr for Pid {
143 type Err = PidError;
144
145 fn from_str(s: &str) -> Result<Self, Self::Err> {
146 let value = s.parse::<u32>().map_err(|_| PidError::InvalidValue(0))?;
147 Self::new(value)
148 }
149}
150
151impl fmt::Display for Pid {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 write!(f, "{}", self.0)
154 }
155}
156
157#[cfg(feature = "arbitrary")]
158impl<'a> arbitrary::Arbitrary<'a> for Pid {
159 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
160 let value = u32::arbitrary(u)?;
161 if value == 0 || value > Self::MAX {
162 Ok(Self(1))
163 } else {
164 Ok(Self(value))
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use core::convert::TryInto;
173
174 #[test]
175 fn test_new_valid() {
176 let pid = Pid::new(1).unwrap();
177 assert_eq!(pid.as_u32(), 1);
178 let pid = Pid::new(100).unwrap();
179 assert_eq!(pid.as_u32(), 100);
180 }
181
182 #[test]
183 fn test_new_zero() {
184 assert!(matches!(Pid::new(0), Err(PidError::InvalidValue(0))));
185 }
186
187 #[test]
188 fn test_new_too_large() {
189 assert!(matches!(Pid::new(u32::MAX), Err(PidError::TooLarge(_))));
190 }
191
192 #[test]
193 fn test_try_from_u32() {
194 let pid: Pid = 100u32.try_into().unwrap();
195 assert_eq!(pid.as_u32(), 100);
196 }
197
198 #[test]
199 fn test_try_from_i32_valid() {
200 let pid: Pid = 100i32.try_into().unwrap();
201 assert_eq!(pid.as_u32(), 100);
202 }
203
204 #[test]
205 fn test_try_from_i32_zero() {
206 assert!(matches!(
207 TryInto::<Pid>::try_into(0i32),
208 Err(PidError::InvalidValue(0))
209 ));
210 }
211
212 #[test]
213 fn test_try_from_i32_negative() {
214 assert!(matches!(
215 TryInto::<Pid>::try_into(-1i32),
216 Err(PidError::InvalidValue(_))
217 ));
218 }
219
220 #[test]
221 fn test_from_str() {
222 let pid: Pid = "100".parse().unwrap();
223 assert_eq!(pid.as_u32(), 100);
224 }
225
226 #[test]
227 fn test_from_str_error() {
228 assert!("0".parse::<Pid>().is_err());
229 assert!("abc".parse::<Pid>().is_err());
230 }
231
232 #[test]
233 fn test_display() {
234 let pid = Pid::new(100).unwrap();
235 assert_eq!(format!("{}", pid), "100");
236 }
237
238 #[test]
239 fn test_is_init() {
240 let pid = Pid::new(1).unwrap();
241 assert!(pid.is_init());
242 let pid = Pid::new(100).unwrap();
243 assert!(!pid.is_init());
244 }
245
246 #[test]
247 fn test_is_system_process() {
248 let pid = Pid::new(1).unwrap();
249 assert!(pid.is_system_process());
250 let pid = Pid::new(50).unwrap();
251 assert!(pid.is_system_process());
252 let pid = Pid::new(100).unwrap();
253 assert!(!pid.is_system_process());
254 }
255
256 #[test]
257 fn test_clone() {
258 let pid = Pid::new(100).unwrap();
259 let pid2 = pid.clone();
260 assert_eq!(pid, pid2);
261 }
262
263 #[test]
264 fn test_equality() {
265 let p1 = Pid::new(100).unwrap();
266 let p2 = Pid::new(100).unwrap();
267 let p3 = Pid::new(200).unwrap();
268 assert_eq!(p1, p2);
269 assert_ne!(p1, p3);
270 }
271
272 #[test]
273 fn test_ordering() {
274 let p1 = Pid::new(100).unwrap();
275 let p2 = Pid::new(200).unwrap();
276 assert!(p1 < p2);
277 assert!(p2 > p1);
278 }
279
280 #[test]
281 fn test_new_unchecked() {
282 let pid = Pid::new_unchecked(100);
283 assert_eq!(pid.as_u32(), 100);
284 }
285}