1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::error::Error;
6
7pub mod prelude {
9 pub use crate::{
10 CurrentRating, FrequencyRating, PowerRating, RatingError, TemperatureRating, Tolerance,
11 VoltageRating,
12 };
13}
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum RatingError {
18 NonFinite,
20 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#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
37pub struct VoltageRating {
38 volts: f64,
39}
40
41impl VoltageRating {
42 pub fn new_volts(value: f64) -> Result<Self, RatingError> {
48 non_negative_finite(value).map(|volts| Self { volts })
49 }
50
51 #[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#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
66pub struct CurrentRating {
67 amperes: f64,
68}
69
70impl CurrentRating {
71 pub fn new_amperes(value: f64) -> Result<Self, RatingError> {
77 non_negative_finite(value).map(|amperes| Self { amperes })
78 }
79
80 #[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#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
95pub struct PowerRating {
96 watts: f64,
97}
98
99impl PowerRating {
100 pub fn new_watts(value: f64) -> Result<Self, RatingError> {
106 non_negative_finite(value).map(|watts| Self { watts })
107 }
108
109 #[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#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
124pub struct Tolerance {
125 percent: f64,
126}
127
128impl Tolerance {
129 pub fn from_percent(value: f64) -> Result<Self, RatingError> {
135 non_negative_finite(value).map(|percent| Self { percent })
136 }
137
138 #[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#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
153pub struct TemperatureRating {
154 celsius: f64,
155}
156
157impl TemperatureRating {
158 pub fn new_celsius(value: f64) -> Result<Self, RatingError> {
164 finite(value).map(|celsius| Self { celsius })
165 }
166
167 #[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#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
182pub struct FrequencyRating {
183 hertz: f64,
184}
185
186impl FrequencyRating {
187 pub fn new_hertz(value: f64) -> Result<Self, RatingError> {
193 non_negative_finite(value).map(|hertz| Self { hertz })
194 }
195
196 #[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}