Skip to main content

use_capacitor/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7use use_rating::VoltageRating;
8
9/// Commonly used capacitor primitives.
10pub mod prelude {
11    pub use crate::{
12        CapacitanceValue, CapacitanceValueError, CapacitorKind, CapacitorKindParseError,
13        CapacitorPolarity, CapacitorPolarityParseError, CapacitorSpec,
14    };
15}
16
17/// A capacitance value in farads.
18#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
19pub struct CapacitanceValue {
20    farads: f64,
21}
22
23impl CapacitanceValue {
24    /// Creates a non-negative capacitance value in farads.
25    ///
26    /// # Errors
27    ///
28    /// Returns [`CapacitanceValueError`] when the value is not finite or is negative.
29    pub fn new_farads(value: f64) -> Result<Self, CapacitanceValueError> {
30        if !value.is_finite() {
31            return Err(CapacitanceValueError::NonFinite);
32        }
33
34        if value < 0.0 {
35            return Err(CapacitanceValueError::Negative);
36        }
37
38        Ok(Self { farads: value })
39    }
40
41    /// Returns the value in farads.
42    #[must_use]
43    pub const fn farads(self) -> f64 {
44        self.farads
45    }
46}
47
48impl fmt::Display for CapacitanceValue {
49    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(formatter, "{} F", self.farads)
51    }
52}
53
54/// Errors returned while constructing capacitance values.
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub enum CapacitanceValueError {
57    /// The capacitance was not finite.
58    NonFinite,
59    /// The capacitance was negative.
60    Negative,
61}
62
63impl fmt::Display for CapacitanceValueError {
64    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Self::NonFinite => formatter.write_str("capacitance must be finite"),
67            Self::Negative => formatter.write_str("capacitance cannot be negative"),
68        }
69    }
70}
71
72impl Error for CapacitanceValueError {}
73
74/// Descriptive capacitor kind vocabulary.
75#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
76pub enum CapacitorKind {
77    Ceramic,
78    Electrolytic,
79    Tantalum,
80    Film,
81    Supercapacitor,
82    Variable,
83    Unknown,
84    Custom(String),
85}
86
87impl fmt::Display for CapacitorKind {
88    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
89        formatter.write_str(match self {
90            Self::Ceramic => "ceramic",
91            Self::Electrolytic => "electrolytic",
92            Self::Tantalum => "tantalum",
93            Self::Film => "film",
94            Self::Supercapacitor => "supercapacitor",
95            Self::Variable => "variable",
96            Self::Unknown => "unknown",
97            Self::Custom(value) => value.as_str(),
98        })
99    }
100}
101
102impl FromStr for CapacitorKind {
103    type Err = CapacitorKindParseError;
104
105    fn from_str(value: &str) -> Result<Self, Self::Err> {
106        let trimmed = value.trim();
107        if trimmed.is_empty() {
108            return Err(CapacitorKindParseError::Empty);
109        }
110
111        match normalized_token(trimmed).as_str() {
112            "ceramic" => Ok(Self::Ceramic),
113            "electrolytic" => Ok(Self::Electrolytic),
114            "tantalum" => Ok(Self::Tantalum),
115            "film" => Ok(Self::Film),
116            "supercapacitor" => Ok(Self::Supercapacitor),
117            "variable" => Ok(Self::Variable),
118            "unknown" => Ok(Self::Unknown),
119            _ => Ok(Self::Custom(trimmed.to_string())),
120        }
121    }
122}
123
124/// Errors returned while parsing capacitor kinds.
125#[derive(Clone, Copy, Debug, Eq, PartialEq)]
126pub enum CapacitorKindParseError {
127    /// The capacitor kind was empty after trimming whitespace.
128    Empty,
129}
130
131impl fmt::Display for CapacitorKindParseError {
132    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
133        match self {
134            Self::Empty => formatter.write_str("capacitor kind cannot be empty"),
135        }
136    }
137}
138
139impl Error for CapacitorKindParseError {}
140
141/// Descriptive capacitor polarity vocabulary.
142#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
143pub enum CapacitorPolarity {
144    Polarized,
145    NonPolarized,
146    Unknown,
147}
148
149impl fmt::Display for CapacitorPolarity {
150    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151        formatter.write_str(match self {
152            Self::Polarized => "polarized",
153            Self::NonPolarized => "non-polarized",
154            Self::Unknown => "unknown",
155        })
156    }
157}
158
159impl FromStr for CapacitorPolarity {
160    type Err = CapacitorPolarityParseError;
161
162    fn from_str(value: &str) -> Result<Self, Self::Err> {
163        let trimmed = value.trim();
164        if trimmed.is_empty() {
165            return Err(CapacitorPolarityParseError::Empty);
166        }
167
168        match normalized_token(trimmed).as_str() {
169            "polarized" => Ok(Self::Polarized),
170            "non-polarized" | "nonpolarized" => Ok(Self::NonPolarized),
171            "unknown" => Ok(Self::Unknown),
172            _ => Err(CapacitorPolarityParseError::Unknown),
173        }
174    }
175}
176
177/// Errors returned while parsing capacitor polarity.
178#[derive(Clone, Copy, Debug, Eq, PartialEq)]
179pub enum CapacitorPolarityParseError {
180    /// The polarity was empty after trimming whitespace.
181    Empty,
182    /// The polarity was not part of the fixed vocabulary.
183    Unknown,
184}
185
186impl fmt::Display for CapacitorPolarityParseError {
187    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188        match self {
189            Self::Empty => formatter.write_str("capacitor polarity cannot be empty"),
190            Self::Unknown => formatter.write_str("unknown capacitor polarity"),
191        }
192    }
193}
194
195impl Error for CapacitorPolarityParseError {}
196
197/// A descriptive capacitor specification.
198#[derive(Clone, Debug, PartialEq)]
199pub struct CapacitorSpec {
200    capacitance: CapacitanceValue,
201    kind: CapacitorKind,
202    polarity: CapacitorPolarity,
203    voltage_rating: Option<VoltageRating>,
204}
205
206impl CapacitorSpec {
207    /// Creates a capacitor spec from capacitance and kind.
208    #[must_use]
209    pub const fn new(capacitance: CapacitanceValue, kind: CapacitorKind) -> Self {
210        Self {
211            capacitance,
212            kind,
213            polarity: CapacitorPolarity::Unknown,
214            voltage_rating: None,
215        }
216    }
217
218    /// Returns the capacitance value.
219    #[must_use]
220    pub const fn capacitance(&self) -> CapacitanceValue {
221        self.capacitance
222    }
223
224    /// Returns the capacitor kind.
225    #[must_use]
226    pub fn kind(&self) -> CapacitorKind {
227        self.kind.clone()
228    }
229
230    /// Returns the capacitor polarity.
231    #[must_use]
232    pub const fn polarity(&self) -> CapacitorPolarity {
233        self.polarity
234    }
235
236    /// Returns the optional voltage rating.
237    #[must_use]
238    pub const fn voltage_rating(&self) -> Option<VoltageRating> {
239        self.voltage_rating
240    }
241
242    /// Returns this spec with polarity attached.
243    #[must_use]
244    pub const fn with_polarity(mut self, polarity: CapacitorPolarity) -> Self {
245        self.polarity = polarity;
246        self
247    }
248
249    /// Returns this spec with a voltage rating attached.
250    #[must_use]
251    pub const fn with_voltage_rating(mut self, voltage_rating: VoltageRating) -> Self {
252        self.voltage_rating = Some(voltage_rating);
253        self
254    }
255}
256
257fn normalized_token(value: &str) -> String {
258    value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
259}
260
261#[cfg(test)]
262mod tests {
263    use super::{
264        CapacitanceValue, CapacitanceValueError, CapacitorKind, CapacitorPolarity, CapacitorSpec,
265    };
266    use use_rating::VoltageRating;
267
268    #[test]
269    fn accepts_valid_capacitance() -> Result<(), CapacitanceValueError> {
270        let value = CapacitanceValue::new_farads(0.000_001)?;
271
272        assert!((value.farads() - 0.000_001).abs() < f64::EPSILON);
273        Ok(())
274    }
275
276    #[test]
277    fn rejects_negative_capacitance() {
278        assert_eq!(
279            CapacitanceValue::new_farads(-1.0),
280            Err(CapacitanceValueError::Negative)
281        );
282    }
283
284    #[test]
285    fn displays_and_parses_capacitor_kinds() -> Result<(), Box<dyn std::error::Error>> {
286        assert_eq!("ceramic".parse::<CapacitorKind>()?, CapacitorKind::Ceramic);
287        assert_eq!(CapacitorKind::Supercapacitor.to_string(), "supercapacitor");
288        Ok(())
289    }
290
291    #[test]
292    fn displays_and_parses_polarity() -> Result<(), Box<dyn std::error::Error>> {
293        assert_eq!(
294            "non polarized".parse::<CapacitorPolarity>()?,
295            CapacitorPolarity::NonPolarized
296        );
297        assert_eq!(CapacitorPolarity::Polarized.to_string(), "polarized");
298        Ok(())
299    }
300
301    #[test]
302    fn builds_capacitor_specs_with_voltage_rating() -> Result<(), Box<dyn std::error::Error>> {
303        let spec = CapacitorSpec::new(
304            CapacitanceValue::new_farads(0.000_001)?,
305            CapacitorKind::Ceramic,
306        )
307        .with_voltage_rating(VoltageRating::new_volts(16.0)?);
308
309        assert_eq!(spec.voltage_rating().map(VoltageRating::volts), Some(16.0));
310        Ok(())
311    }
312}