bare-types 0.3.0

A zero-cost foundation for type-safe domain modeling in Rust. Implements the 'Parse, don't validate' philosophy to eliminate primitive obsession and ensure data integrity at the system boundary.
Documentation
//! Process ID type for system information.
//!
//! This module provides a type-safe abstraction for process IDs,
//! ensuring valid PID values.

use core::fmt;
use core::str::FromStr;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Error type for PID parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum PidError {
    /// Invalid PID value (must be positive)
    ///
    /// Process IDs must be positive integers (greater than 0).
    /// PID 0 is typically reserved for the kernel or scheduler.
    /// This variant contains the invalid value.
    InvalidValue(u32),
    /// PID too large (platform-dependent)
    ///
    /// The provided PID exceeds the maximum valid value for the platform.
    /// On Linux, the maximum is typically 2^31-1 (4,194,303).
    /// On other platforms, it may be `u32::MAX`.
    /// This variant contains the invalid value.
    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 {}

/// Process ID.
///
/// This type provides type-safe process IDs. It uses the newtype pattern
/// with `#[repr(transparent)]` for zero-cost abstraction.
///
/// # Platform Differences
///
/// - **Linux/Unix**: PIDs are typically 32-bit unsigned integers
/// - **Windows**: PIDs are typically 32-bit unsigned integers
///
/// # Special Values
///
/// - `0`: Typically refers to the kernel or scheduler (may be invalid on some systems)
/// - `1`: Typically the init process (or systemd on modern Linux)
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Pid(u32);

impl Pid {
    /// Maximum valid PID value for the current platform.
    ///
    /// On Linux, this is typically 2^31-1 (4,194,303).
    /// On other platforms, this is `u32::MAX`.
    #[cfg(target_os = "linux")]
    pub const MAX: u32 = 4_194_303;

    /// Maximum valid PID value for the current platform.
    ///
    /// On Linux, this is typically 2^31-1 (4,194,303).
    /// On other platforms, this is `u32::MAX`.
    #[cfg(not(target_os = "linux"))]
    pub const MAX: u32 = u32::MAX;

    /// Creates a new PID from a value.
    ///
    /// # Errors
    ///
    /// Returns an error if the value is zero or exceeds the maximum.
    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))
    }

    /// Creates a new PID without validation.
    ///
    /// This constructor skips validation and is useful when the caller has
    /// already validated the input or obtained the PID from a trusted source.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use bare_types::sys::Pid;
    ///
    /// // Safe because 100 is a valid PID
    /// let pid = Pid::new_unchecked(100);
    /// assert_eq!(pid.as_u32(), 100);
    /// ```
    #[must_use]
    #[inline]
    pub const fn new_unchecked(value: u32) -> Self {
        Self(value)
    }

    /// Returns the PID value.
    #[must_use]
    #[inline]
    pub const fn as_u32(&self) -> u32 {
        self.0
    }

    /// Returns `true` if this is the init process (PID 1).
    #[must_use]
    #[inline]
    pub const fn is_init(&self) -> bool {
        self.0 == 1
    }

    /// Returns `true` if this is likely a system process.
    ///
    /// System processes typically have low PIDs (below 100 on most systems).
    #[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);
    }
}