Skip to main content

use_rating/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7/// Commonly used rating primitives.
8pub mod prelude {
9    pub use crate::{
10        CurrentRating, FrequencyRating, PowerRating, RatingError, TemperatureRating, Tolerance,
11        VoltageRating,
12    };
13}
14
15/// Errors returned while constructing rating values.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum RatingError {
18    /// The rating value was not finite.
19    NonFinite,
20    /// The rating value was negative where only non-negative values make sense.
21    Negative,
22}
23
24impl fmt::Display for RatingError {
25    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            Self::NonFinite => formatter.write_str("rating value must be finite"),
28            Self::Negative => formatter.write_str("rating value cannot be negative"),
29        }
30    }
31}
32
33impl Error for RatingError {}
34
35/// A voltage rating in volts.
36#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
37pub struct VoltageRating {
38    volts: f64,
39}
40
41impl VoltageRating {
42    /// Creates a non-negative voltage rating in volts.
43    ///
44    /// # Errors
45    ///
46    /// Returns [`RatingError`] when the value is not finite or is negative.
47    pub fn new_volts(value: f64) -> Result<Self, RatingError> {
48        non_negative_finite(value).map(|volts| Self { volts })
49    }
50
51    /// Returns the rating in volts.
52    #[must_use]
53    pub const fn volts(self) -> f64 {
54        self.volts
55    }
56}
57
58impl fmt::Display for VoltageRating {
59    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(formatter, "{} V", self.volts)
61    }
62}
63
64/// A current rating in amperes.
65#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
66pub struct CurrentRating {
67    amperes: f64,
68}
69
70impl CurrentRating {
71    /// Creates a non-negative current rating in amperes.
72    ///
73    /// # Errors
74    ///
75    /// Returns [`RatingError`] when the value is not finite or is negative.
76    pub fn new_amperes(value: f64) -> Result<Self, RatingError> {
77        non_negative_finite(value).map(|amperes| Self { amperes })
78    }
79
80    /// Returns the rating in amperes.
81    #[must_use]
82    pub const fn amperes(self) -> f64 {
83        self.amperes
84    }
85}
86
87impl fmt::Display for CurrentRating {
88    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
89        write!(formatter, "{} A", self.amperes)
90    }
91}
92
93/// A power rating in watts.
94#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
95pub struct PowerRating {
96    watts: f64,
97}
98
99impl PowerRating {
100    /// Creates a non-negative power rating in watts.
101    ///
102    /// # Errors
103    ///
104    /// Returns [`RatingError`] when the value is not finite or is negative.
105    pub fn new_watts(value: f64) -> Result<Self, RatingError> {
106        non_negative_finite(value).map(|watts| Self { watts })
107    }
108
109    /// Returns the rating in watts.
110    #[must_use]
111    pub const fn watts(self) -> f64 {
112        self.watts
113    }
114}
115
116impl fmt::Display for PowerRating {
117    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
118        write!(formatter, "{} W", self.watts)
119    }
120}
121
122/// A tolerance value in percent.
123#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
124pub struct Tolerance {
125    percent: f64,
126}
127
128impl Tolerance {
129    /// Creates a non-negative tolerance percentage.
130    ///
131    /// # Errors
132    ///
133    /// Returns [`RatingError`] when the value is not finite or is negative.
134    pub fn from_percent(value: f64) -> Result<Self, RatingError> {
135        non_negative_finite(value).map(|percent| Self { percent })
136    }
137
138    /// Returns the tolerance in percent.
139    #[must_use]
140    pub const fn percent(self) -> f64 {
141        self.percent
142    }
143}
144
145impl fmt::Display for Tolerance {
146    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
147        write!(formatter, "+/-{}%", self.percent)
148    }
149}
150
151/// A temperature rating in degrees Celsius.
152#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
153pub struct TemperatureRating {
154    celsius: f64,
155}
156
157impl TemperatureRating {
158    /// Creates a temperature rating in degrees Celsius.
159    ///
160    /// # Errors
161    ///
162    /// Returns [`RatingError::NonFinite`] when the value is not finite.
163    pub fn new_celsius(value: f64) -> Result<Self, RatingError> {
164        finite(value).map(|celsius| Self { celsius })
165    }
166
167    /// Returns the rating in degrees Celsius.
168    #[must_use]
169    pub const fn celsius(self) -> f64 {
170        self.celsius
171    }
172}
173
174impl fmt::Display for TemperatureRating {
175    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
176        write!(formatter, "{} C", self.celsius)
177    }
178}
179
180/// A frequency rating in hertz.
181#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
182pub struct FrequencyRating {
183    hertz: f64,
184}
185
186impl FrequencyRating {
187    /// Creates a non-negative frequency rating in hertz.
188    ///
189    /// # Errors
190    ///
191    /// Returns [`RatingError`] when the value is not finite or is negative.
192    pub fn new_hertz(value: f64) -> Result<Self, RatingError> {
193        non_negative_finite(value).map(|hertz| Self { hertz })
194    }
195
196    /// Returns the rating in hertz.
197    #[must_use]
198    pub const fn hertz(self) -> f64 {
199        self.hertz
200    }
201}
202
203impl fmt::Display for FrequencyRating {
204    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
205        write!(formatter, "{} Hz", self.hertz)
206    }
207}
208
209const fn finite(value: f64) -> Result<f64, RatingError> {
210    if value.is_finite() {
211        Ok(value)
212    } else {
213        Err(RatingError::NonFinite)
214    }
215}
216
217fn non_negative_finite(value: f64) -> Result<f64, RatingError> {
218    let value = finite(value)?;
219    if value < 0.0 {
220        Err(RatingError::Negative)
221    } else {
222        Ok(value)
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use super::{CurrentRating, PowerRating, RatingError, Tolerance, VoltageRating};
229
230    #[test]
231    fn constructs_voltage_ratings() -> Result<(), RatingError> {
232        let rating = VoltageRating::new_volts(16.0)?;
233
234        assert!((rating.volts() - 16.0).abs() < f64::EPSILON);
235        Ok(())
236    }
237
238    #[test]
239    fn constructs_current_ratings() -> Result<(), RatingError> {
240        let rating = CurrentRating::new_amperes(0.5)?;
241
242        assert!((rating.amperes() - 0.5).abs() < f64::EPSILON);
243        Ok(())
244    }
245
246    #[test]
247    fn constructs_power_ratings() -> Result<(), RatingError> {
248        let rating = PowerRating::new_watts(0.25)?;
249
250        assert!((rating.watts() - 0.25).abs() < f64::EPSILON);
251        Ok(())
252    }
253
254    #[test]
255    fn constructs_tolerance_percentages() -> Result<(), RatingError> {
256        let tolerance = Tolerance::from_percent(1.0)?;
257
258        assert!((tolerance.percent() - 1.0).abs() < f64::EPSILON);
259        Ok(())
260    }
261
262    #[test]
263    fn rejects_negative_tolerance() {
264        assert_eq!(Tolerance::from_percent(-1.0), Err(RatingError::Negative));
265    }
266
267    #[test]
268    fn displays_rating_values() -> Result<(), RatingError> {
269        assert_eq!(VoltageRating::new_volts(5.0)?.to_string(), "5 V");
270        assert_eq!(CurrentRating::new_amperes(2.0)?.to_string(), "2 A");
271        assert_eq!(PowerRating::new_watts(0.25)?.to_string(), "0.25 W");
272        assert_eq!(Tolerance::from_percent(10.0)?.to_string(), "+/-10%");
273        Ok(())
274    }
275}