chrony-confile 0.1.0

A full-featured Rust library for parsing, editing, validating, and serializing chrony configuration files
Documentation
//! Bounded integer newtypes with compile-time range checking.
//!
//! Each type in this module represents a bounded integer used in chrony configuration values.
//! Construction via [`new`](PollInterval::new) validates the value is within range and returns
//! a [`ValueError`] if it is not. [`new_unchecked`](PollInterval::new_unchecked) skips validation
//! for use in const contexts or when the value is known to be valid.
//!
//! # Types
//!
//! | Type | Inner | Range | Usage |
//! |------|-------|-------|-------|
//! | [`PollInterval`] | `i8` | -32..=32 | NTP poll interval as power of 2 |
//! | [`UdpPort`] | `u16` | 0..=65535 | UDP port number |
//! | [`Dscp`] | `u8` | 0..=63 | Differentiated Services Code Point |
//! | [`Stratum`] | `u8` | 0..=15 | NTP stratum level |
//! | [`SamplesCount`] | `u32` | 0..=i32::MAX | Sample count |
//! | [`PollTarget`] | `u32` | 6..=60 | Target number of poll samples |
//! | [`NtpVersion`] | `u8` | 1..=4 | NTP protocol version |
//! | [`PpsRate`] | `u32` | 1..=i32::MAX | PPS rate |
//! | [`SchedPriority`] | `u8` | 0..=100 | Scheduling priority |
//! | [`PtpDomain`] | `u8` | 0..=255 | PTP domain number |
//! | [`ClientLogSize`] | `u64` | 0..=2147483648 | Client log size |
//! | [`TxBuffers`] | `u32` | 0..=1048576 | TX buffer count |
//! | [`NtsProcesses`] | `u16` | 0..=1000 | NTS process count |

use std::fmt;

use crate::error::ValueError;

macro_rules! bounded_int {
    ($name:ident, $inner:ty, $min:expr, $max:expr $(,)?) => {
        /// A bounded integer value with range checking at construction time.
        ///
        /// See the [module-level documentation](self) for the valid range for each type.
        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
        pub struct $name($inner);

        impl $name {
            /// The minimum valid value for this type.
            pub const MIN: $inner = $min;
            /// The maximum valid value for this type.
            pub const MAX: $inner = $max;

            /// Creates a new value, returning an error if out of range.
            ///
            /// # Examples
            ///
            /// ```rust
            /// use chrony_confile::values::PollInterval;
            ///
            /// let val = PollInterval::new(6)?;
            /// assert_eq!(val.get(), 6);
            ///
            /// let err = PollInterval::new(100);
            /// assert!(err.is_err());
            /// # Ok::<_, chrony_confile::ValueError>(())
            /// ```
            pub fn new(value: $inner) -> Result<Self, ValueError> {
                if ($min..=$max).contains(&value) {
                    Ok(Self(value))
                } else {
                    Err(ValueError::OutOfRange {
                        value: value as i64,
                        min: $min as i64,
                        max: $max as i64,
                    })
                }
            }

            /// Creates a new value without bounds checking.
            ///
            /// This is safe because an out-of-range value is not undefined behavior;
            /// the caller is responsible for ensuring the value is within `MIN..=MAX`.
            pub const fn new_unchecked(value: $inner) -> Self {
                Self(value)
            }

            /// Returns the inner numeric value.
            pub fn get(&self) -> $inner {
                self.0
            }
        }

        impl fmt::Display for $name {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "{}", self.0)
            }
        }
    };
}

bounded_int!(PollInterval, i8, -32, 32);
bounded_int!(UdpPort, u16, 0, 65535);
bounded_int!(Dscp, u8, 0, 63);
bounded_int!(Stratum, u8, 0, 15);
bounded_int!(SamplesCount, u32, 0, i32::MAX as u32);
bounded_int!(PollTarget, u32, 6, 60);
bounded_int!(NtpVersion, u8, 1, 4);
bounded_int!(PpsRate, u32, 1, i32::MAX as u32);
bounded_int!(SchedPriority, u8, 0, 100);
bounded_int!(PtpDomain, u8, 0, 255);
bounded_int!(ClientLogSize, u64, 0, 2147483648);
bounded_int!(TxBuffers, u32, 0, 1048576);
bounded_int!(NtsProcesses, u16, 0, 1000);

// --- TryFrom impls ---

impl TryFrom<i32> for PollInterval {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as i8)
    }
}

impl TryFrom<i32> for UdpPort {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u16)
    }
}

impl TryFrom<i32> for Dscp {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u8)
    }
}

impl TryFrom<i32> for Stratum {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u8)
    }
}

impl TryFrom<i32> for PollTarget {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u32)
    }
}

impl TryFrom<i32> for NtpVersion {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u8)
    }
}

impl TryFrom<i32> for PpsRate {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u32)
    }
}

impl TryFrom<i32> for SchedPriority {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u8)
    }
}

impl TryFrom<i32> for PtpDomain {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u8)
    }
}

impl TryFrom<i32> for TxBuffers {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u32)
    }
}

impl TryFrom<i32> for NtsProcesses {
    type Error = ValueError;
    fn try_from(v: i32) -> Result<Self, Self::Error> {
        Self::new(v as u16)
    }
}

impl TryFrom<i64> for ClientLogSize {
    type Error = ValueError;
    fn try_from(v: i64) -> Result<Self, Self::Error> {
        Self::new(v as u64)
    }
}

// --- From impls (infallible conversions to primitives) ---

impl From<PollInterval> for i8 { fn from(v: PollInterval) -> Self { v.0 } }
impl From<UdpPort> for u16 { fn from(v: UdpPort) -> Self { v.0 } }
impl From<Dscp> for u8 { fn from(v: Dscp) -> Self { v.0 } }
impl From<Stratum> for u8 { fn from(v: Stratum) -> Self { v.0 } }
impl From<SamplesCount> for u32 { fn from(v: SamplesCount) -> Self { v.0 } }
impl From<PollTarget> for u32 { fn from(v: PollTarget) -> Self { v.0 } }
impl From<NtpVersion> for u8 { fn from(v: NtpVersion) -> Self { v.0 } }
impl From<PpsRate> for u32 { fn from(v: PpsRate) -> Self { v.0 } }
impl From<SchedPriority> for u8 { fn from(v: SchedPriority) -> Self { v.0 } }
impl From<PtpDomain> for u8 { fn from(v: PtpDomain) -> Self { v.0 } }
impl From<ClientLogSize> for u64 { fn from(v: ClientLogSize) -> Self { v.0 } }
impl From<TxBuffers> for u32 { fn from(v: TxBuffers) -> Self { v.0 } }
impl From<NtsProcesses> for u16 { fn from(v: NtsProcesses) -> Self { v.0 } }

// --- From<UdpPort> for u16 (explicitly listed for documentation purposes)
// Already covered by the generic From impl above.