Skip to main content

chrony_confile/
values.rs

1//! Bounded integer newtypes with compile-time range checking.
2//!
3//! Each type in this module represents a bounded integer used in chrony configuration values.
4//! Construction via [`new`](PollInterval::new) validates the value is within range and returns
5//! a [`ValueError`] if it is not. [`new_unchecked`](PollInterval::new_unchecked) skips validation
6//! for use in const contexts or when the value is known to be valid.
7//!
8//! # Types
9//!
10//! | Type | Inner | Range | Usage |
11//! |------|-------|-------|-------|
12//! | [`PollInterval`] | `i8` | -32..=32 | NTP poll interval as power of 2 |
13//! | [`UdpPort`] | `u16` | 0..=65535 | UDP port number |
14//! | [`Dscp`] | `u8` | 0..=63 | Differentiated Services Code Point |
15//! | [`Stratum`] | `u8` | 0..=15 | NTP stratum level |
16//! | [`SamplesCount`] | `u32` | 0..=i32::MAX | Sample count |
17//! | [`PollTarget`] | `u32` | 6..=60 | Target number of poll samples |
18//! | [`NtpVersion`] | `u8` | 1..=4 | NTP protocol version |
19//! | [`PpsRate`] | `u32` | 1..=i32::MAX | PPS rate |
20//! | [`SchedPriority`] | `u8` | 0..=100 | Scheduling priority |
21//! | [`PtpDomain`] | `u8` | 0..=255 | PTP domain number |
22//! | [`ClientLogSize`] | `u64` | 0..=2147483648 | Client log size |
23//! | [`TxBuffers`] | `u32` | 0..=1048576 | TX buffer count |
24//! | [`NtsProcesses`] | `u16` | 0..=1000 | NTS process count |
25
26use std::fmt;
27
28use crate::error::ValueError;
29
30macro_rules! bounded_int {
31    ($name:ident, $inner:ty, $min:expr, $max:expr $(,)?) => {
32        /// A bounded integer value with range checking at construction time.
33        ///
34        /// See the [module-level documentation](self) for the valid range for each type.
35        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
36        pub struct $name($inner);
37
38        impl $name {
39            /// The minimum valid value for this type.
40            pub const MIN: $inner = $min;
41            /// The maximum valid value for this type.
42            pub const MAX: $inner = $max;
43
44            /// Creates a new value, returning an error if out of range.
45            ///
46            /// # Examples
47            ///
48            /// ```rust
49            /// use chrony_confile::values::PollInterval;
50            ///
51            /// let val = PollInterval::new(6)?;
52            /// assert_eq!(val.get(), 6);
53            ///
54            /// let err = PollInterval::new(100);
55            /// assert!(err.is_err());
56            /// # Ok::<_, chrony_confile::ValueError>(())
57            /// ```
58            pub fn new(value: $inner) -> Result<Self, ValueError> {
59                if ($min..=$max).contains(&value) {
60                    Ok(Self(value))
61                } else {
62                    Err(ValueError::OutOfRange {
63                        value: value as i64,
64                        min: $min as i64,
65                        max: $max as i64,
66                    })
67                }
68            }
69
70            /// Creates a new value without bounds checking.
71            ///
72            /// This is safe because an out-of-range value is not undefined behavior;
73            /// the caller is responsible for ensuring the value is within `MIN..=MAX`.
74            pub const fn new_unchecked(value: $inner) -> Self {
75                Self(value)
76            }
77
78            /// Returns the inner numeric value.
79            pub fn get(&self) -> $inner {
80                self.0
81            }
82        }
83
84        impl fmt::Display for $name {
85            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86                write!(f, "{}", self.0)
87            }
88        }
89    };
90}
91
92bounded_int!(PollInterval, i8, -32, 32);
93bounded_int!(UdpPort, u16, 0, 65535);
94bounded_int!(Dscp, u8, 0, 63);
95bounded_int!(Stratum, u8, 0, 15);
96bounded_int!(SamplesCount, u32, 0, i32::MAX as u32);
97bounded_int!(PollTarget, u32, 6, 60);
98bounded_int!(NtpVersion, u8, 1, 4);
99bounded_int!(PpsRate, u32, 1, i32::MAX as u32);
100bounded_int!(SchedPriority, u8, 0, 100);
101bounded_int!(PtpDomain, u8, 0, 255);
102bounded_int!(ClientLogSize, u64, 0, 2147483648);
103bounded_int!(TxBuffers, u32, 0, 1048576);
104bounded_int!(NtsProcesses, u16, 0, 1000);
105
106// --- TryFrom impls ---
107
108impl TryFrom<i32> for PollInterval {
109    type Error = ValueError;
110    fn try_from(v: i32) -> Result<Self, Self::Error> {
111        Self::new(v as i8)
112    }
113}
114
115impl TryFrom<i32> for UdpPort {
116    type Error = ValueError;
117    fn try_from(v: i32) -> Result<Self, Self::Error> {
118        Self::new(v as u16)
119    }
120}
121
122impl TryFrom<i32> for Dscp {
123    type Error = ValueError;
124    fn try_from(v: i32) -> Result<Self, Self::Error> {
125        Self::new(v as u8)
126    }
127}
128
129impl TryFrom<i32> for Stratum {
130    type Error = ValueError;
131    fn try_from(v: i32) -> Result<Self, Self::Error> {
132        Self::new(v as u8)
133    }
134}
135
136impl TryFrom<i32> for PollTarget {
137    type Error = ValueError;
138    fn try_from(v: i32) -> Result<Self, Self::Error> {
139        Self::new(v as u32)
140    }
141}
142
143impl TryFrom<i32> for NtpVersion {
144    type Error = ValueError;
145    fn try_from(v: i32) -> Result<Self, Self::Error> {
146        Self::new(v as u8)
147    }
148}
149
150impl TryFrom<i32> for PpsRate {
151    type Error = ValueError;
152    fn try_from(v: i32) -> Result<Self, Self::Error> {
153        Self::new(v as u32)
154    }
155}
156
157impl TryFrom<i32> for SchedPriority {
158    type Error = ValueError;
159    fn try_from(v: i32) -> Result<Self, Self::Error> {
160        Self::new(v as u8)
161    }
162}
163
164impl TryFrom<i32> for PtpDomain {
165    type Error = ValueError;
166    fn try_from(v: i32) -> Result<Self, Self::Error> {
167        Self::new(v as u8)
168    }
169}
170
171impl TryFrom<i32> for TxBuffers {
172    type Error = ValueError;
173    fn try_from(v: i32) -> Result<Self, Self::Error> {
174        Self::new(v as u32)
175    }
176}
177
178impl TryFrom<i32> for NtsProcesses {
179    type Error = ValueError;
180    fn try_from(v: i32) -> Result<Self, Self::Error> {
181        Self::new(v as u16)
182    }
183}
184
185impl TryFrom<i64> for ClientLogSize {
186    type Error = ValueError;
187    fn try_from(v: i64) -> Result<Self, Self::Error> {
188        Self::new(v as u64)
189    }
190}
191
192// --- From impls (infallible conversions to primitives) ---
193
194impl From<PollInterval> for i8 { fn from(v: PollInterval) -> Self { v.0 } }
195impl From<UdpPort> for u16 { fn from(v: UdpPort) -> Self { v.0 } }
196impl From<Dscp> for u8 { fn from(v: Dscp) -> Self { v.0 } }
197impl From<Stratum> for u8 { fn from(v: Stratum) -> Self { v.0 } }
198impl From<SamplesCount> for u32 { fn from(v: SamplesCount) -> Self { v.0 } }
199impl From<PollTarget> for u32 { fn from(v: PollTarget) -> Self { v.0 } }
200impl From<NtpVersion> for u8 { fn from(v: NtpVersion) -> Self { v.0 } }
201impl From<PpsRate> for u32 { fn from(v: PpsRate) -> Self { v.0 } }
202impl From<SchedPriority> for u8 { fn from(v: SchedPriority) -> Self { v.0 } }
203impl From<PtpDomain> for u8 { fn from(v: PtpDomain) -> Self { v.0 } }
204impl From<ClientLogSize> for u64 { fn from(v: ClientLogSize) -> Self { v.0 } }
205impl From<TxBuffers> for u32 { fn from(v: TxBuffers) -> Self { v.0 } }
206impl From<NtsProcesses> for u16 { fn from(v: NtsProcesses) -> Self { v.0 } }
207
208// --- From<UdpPort> for u16 (explicitly listed for documentation purposes)
209// Already covered by the generic From impl above.