use core::fmt;
use core::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum PidError {
InvalidValue(u32),
TooLarge(u32),
}
impl fmt::Display for PidError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidValue(v) => write!(f, "invalid PID value: {v} (must be positive)"),
Self::TooLarge(v) => write!(f, "PID too large: {v}"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for PidError {}
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Pid(u32);
impl Pid {
#[cfg(target_os = "linux")]
pub const MAX: u32 = 4_194_303;
#[cfg(not(target_os = "linux"))]
pub const MAX: u32 = u32::MAX;
pub const fn new(value: u32) -> Result<Self, PidError> {
if value == 0 {
return Err(PidError::InvalidValue(value));
}
#[allow(clippy::absurd_extreme_comparisons)]
if value > Self::MAX {
return Err(PidError::TooLarge(value));
}
Ok(Self(value))
}
#[must_use]
#[inline]
pub const fn new_unchecked(value: u32) -> Self {
Self(value)
}
#[must_use]
#[inline]
pub const fn as_u32(&self) -> u32 {
self.0
}
#[must_use]
#[inline]
pub const fn is_init(&self) -> bool {
self.0 == 1
}
#[must_use]
#[inline]
pub const fn is_system_process(&self) -> bool {
self.0 < 100
}
}
impl TryFrom<u32> for Pid {
type Error = PidError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<i32> for Pid {
type Error = PidError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value <= 0 {
#[allow(clippy::cast_sign_loss)]
return Err(PidError::InvalidValue(value as u32));
}
#[allow(clippy::cast_sign_loss)]
Self::new(value as u32)
}
}
impl FromStr for Pid {
type Err = PidError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let value = s.parse::<u32>().map_err(|_| PidError::InvalidValue(0))?;
Self::new(value)
}
}
impl fmt::Display for Pid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for Pid {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let value = u32::arbitrary(u)?;
#[allow(clippy::absurd_extreme_comparisons)]
if value == 0 || value > Self::MAX {
Ok(Self(1))
} else {
Ok(Self(value))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_valid() {
let pid = Pid::new(1).unwrap();
assert_eq!(pid.as_u32(), 1);
let pid = Pid::new(100).unwrap();
assert_eq!(pid.as_u32(), 100);
}
#[test]
fn test_new_zero() {
assert!(matches!(Pid::new(0), Err(PidError::InvalidValue(0))));
}
#[test]
#[cfg(target_os = "linux")]
fn test_new_too_large() {
assert!(matches!(Pid::new(4_194_304), Err(PidError::TooLarge(_))));
}
#[test]
#[cfg(not(target_os = "linux"))]
#[ignore = "On non-Linux platforms, MAX equals u32::MAX so no value can exceed it"]
fn test_new_too_large() {
assert!(matches!(Pid::new(u32::MAX), Err(PidError::TooLarge(_))));
}
#[test]
fn test_try_from_u32() {
let pid: Pid = 100u32.try_into().unwrap();
assert_eq!(pid.as_u32(), 100);
}
#[test]
fn test_try_from_i32_valid() {
let pid: Pid = 100i32.try_into().unwrap();
assert_eq!(pid.as_u32(), 100);
}
#[test]
fn test_try_from_i32_zero() {
assert!(matches!(
TryInto::<Pid>::try_into(0i32),
Err(PidError::InvalidValue(0))
));
}
#[test]
fn test_try_from_i32_negative() {
assert!(matches!(
TryInto::<Pid>::try_into(-1i32),
Err(PidError::InvalidValue(_))
));
}
#[test]
fn test_from_str() {
let pid: Pid = "100".parse().unwrap();
assert_eq!(pid.as_u32(), 100);
}
#[test]
fn test_from_str_error() {
assert!("0".parse::<Pid>().is_err());
assert!("abc".parse::<Pid>().is_err());
}
#[test]
fn test_display() {
let pid = Pid::new(100).unwrap();
assert_eq!(format!("{}", pid), "100");
}
#[test]
fn test_is_init() {
let pid = Pid::new(1).unwrap();
assert!(pid.is_init());
let pid = Pid::new(100).unwrap();
assert!(!pid.is_init());
}
#[test]
fn test_is_system_process() {
let pid = Pid::new(1).unwrap();
assert!(pid.is_system_process());
let pid = Pid::new(50).unwrap();
assert!(pid.is_system_process());
let pid = Pid::new(100).unwrap();
assert!(!pid.is_system_process());
}
#[test]
fn test_clone() {
let pid = Pid::new(100).unwrap();
let pid2 = pid.clone();
assert_eq!(pid, pid2);
}
#[test]
fn test_equality() {
let p1 = Pid::new(100).unwrap();
let p2 = Pid::new(100).unwrap();
let p3 = Pid::new(200).unwrap();
assert_eq!(p1, p2);
assert_ne!(p1, p3);
}
#[test]
fn test_ordering() {
let p1 = Pid::new(100).unwrap();
let p2 = Pid::new(200).unwrap();
assert!(p1 < p2);
assert!(p2 > p1);
}
#[test]
fn test_new_unchecked() {
let pid = Pid::new_unchecked(100);
assert_eq!(pid.as_u32(), 100);
}
}