Skip to main content

use_diode/
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::{CurrentRating, VoltageRating};
8
9/// Commonly used diode primitives.
10pub mod prelude {
11    pub use crate::{
12        DiodeKind, DiodeKindParseError, DiodePolarity, DiodePolarityParseError, DiodeSpec,
13    };
14}
15
16/// Descriptive diode kind vocabulary.
17#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
18pub enum DiodeKind {
19    Signal,
20    Rectifier,
21    Zener,
22    Schottky,
23    Led,
24    Tvs,
25    Photodiode,
26    Unknown,
27    Custom(String),
28}
29
30impl fmt::Display for DiodeKind {
31    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
32        formatter.write_str(match self {
33            Self::Signal => "signal",
34            Self::Rectifier => "rectifier",
35            Self::Zener => "zener",
36            Self::Schottky => "schottky",
37            Self::Led => "led",
38            Self::Tvs => "tvs",
39            Self::Photodiode => "photodiode",
40            Self::Unknown => "unknown",
41            Self::Custom(value) => value.as_str(),
42        })
43    }
44}
45
46impl FromStr for DiodeKind {
47    type Err = DiodeKindParseError;
48
49    fn from_str(value: &str) -> Result<Self, Self::Err> {
50        let trimmed = value.trim();
51        if trimmed.is_empty() {
52            return Err(DiodeKindParseError::Empty);
53        }
54
55        match normalized_token(trimmed).as_str() {
56            "signal" => Ok(Self::Signal),
57            "rectifier" => Ok(Self::Rectifier),
58            "zener" => Ok(Self::Zener),
59            "schottky" => Ok(Self::Schottky),
60            "led" => Ok(Self::Led),
61            "tvs" => Ok(Self::Tvs),
62            "photodiode" => Ok(Self::Photodiode),
63            "unknown" => Ok(Self::Unknown),
64            _ => Ok(Self::Custom(trimmed.to_string())),
65        }
66    }
67}
68
69/// Errors returned while parsing diode kinds.
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub enum DiodeKindParseError {
72    /// The diode kind was empty after trimming whitespace.
73    Empty,
74}
75
76impl fmt::Display for DiodeKindParseError {
77    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78        match self {
79            Self::Empty => formatter.write_str("diode kind cannot be empty"),
80        }
81    }
82}
83
84impl Error for DiodeKindParseError {}
85
86/// Diode terminal polarity vocabulary.
87#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
88pub enum DiodePolarity {
89    Anode,
90    Cathode,
91}
92
93impl fmt::Display for DiodePolarity {
94    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
95        formatter.write_str(match self {
96            Self::Anode => "anode",
97            Self::Cathode => "cathode",
98        })
99    }
100}
101
102impl FromStr for DiodePolarity {
103    type Err = DiodePolarityParseError;
104
105    fn from_str(value: &str) -> Result<Self, Self::Err> {
106        let trimmed = value.trim();
107        if trimmed.is_empty() {
108            return Err(DiodePolarityParseError::Empty);
109        }
110
111        match normalized_token(trimmed).as_str() {
112            "anode" => Ok(Self::Anode),
113            "cathode" => Ok(Self::Cathode),
114            _ => Err(DiodePolarityParseError::Unknown),
115        }
116    }
117}
118
119/// Errors returned while parsing diode polarity.
120#[derive(Clone, Copy, Debug, Eq, PartialEq)]
121pub enum DiodePolarityParseError {
122    /// The polarity was empty after trimming whitespace.
123    Empty,
124    /// The polarity was not part of the fixed vocabulary.
125    Unknown,
126}
127
128impl fmt::Display for DiodePolarityParseError {
129    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
130        match self {
131            Self::Empty => formatter.write_str("diode polarity cannot be empty"),
132            Self::Unknown => formatter.write_str("unknown diode polarity"),
133        }
134    }
135}
136
137impl Error for DiodePolarityParseError {}
138
139/// A descriptive diode specification.
140#[derive(Clone, Debug, PartialEq)]
141pub struct DiodeSpec {
142    kind: DiodeKind,
143    forward_voltage: Option<VoltageRating>,
144    reverse_voltage_rating: Option<VoltageRating>,
145    current_rating: Option<CurrentRating>,
146}
147
148impl DiodeSpec {
149    /// Creates a diode spec from a diode kind.
150    #[must_use]
151    pub const fn new(kind: DiodeKind) -> Self {
152        Self {
153            kind,
154            forward_voltage: None,
155            reverse_voltage_rating: None,
156            current_rating: None,
157        }
158    }
159
160    /// Returns the diode kind.
161    #[must_use]
162    pub fn kind(&self) -> DiodeKind {
163        self.kind.clone()
164    }
165
166    /// Returns the optional forward voltage metadata.
167    #[must_use]
168    pub const fn forward_voltage(&self) -> Option<VoltageRating> {
169        self.forward_voltage
170    }
171
172    /// Returns the optional reverse voltage rating.
173    #[must_use]
174    pub const fn reverse_voltage_rating(&self) -> Option<VoltageRating> {
175        self.reverse_voltage_rating
176    }
177
178    /// Returns the optional current rating.
179    #[must_use]
180    pub const fn current_rating(&self) -> Option<CurrentRating> {
181        self.current_rating
182    }
183
184    /// Returns this spec with forward voltage metadata attached.
185    #[must_use]
186    pub const fn with_forward_voltage(mut self, forward_voltage: VoltageRating) -> Self {
187        self.forward_voltage = Some(forward_voltage);
188        self
189    }
190
191    /// Returns this spec with reverse voltage rating metadata attached.
192    #[must_use]
193    pub const fn with_reverse_voltage_rating(mut self, reverse_voltage: VoltageRating) -> Self {
194        self.reverse_voltage_rating = Some(reverse_voltage);
195        self
196    }
197
198    /// Returns this spec with current rating metadata attached.
199    #[must_use]
200    pub const fn with_current_rating(mut self, current_rating: CurrentRating) -> Self {
201        self.current_rating = Some(current_rating);
202        self
203    }
204}
205
206fn normalized_token(value: &str) -> String {
207    value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
208}
209
210#[cfg(test)]
211mod tests {
212    use super::{DiodeKind, DiodePolarity, DiodeSpec};
213    use use_rating::{CurrentRating, VoltageRating};
214
215    #[test]
216    fn displays_and_parses_diode_kinds() -> Result<(), Box<dyn std::error::Error>> {
217        assert_eq!("schottky".parse::<DiodeKind>()?, DiodeKind::Schottky);
218        assert_eq!(DiodeKind::Photodiode.to_string(), "photodiode");
219        Ok(())
220    }
221
222    #[test]
223    fn supports_custom_diode_kinds() -> Result<(), Box<dyn std::error::Error>> {
224        assert_eq!(
225            "pin-diode".parse::<DiodeKind>()?,
226            DiodeKind::Custom("pin-diode".to_string())
227        );
228        Ok(())
229    }
230
231    #[test]
232    fn displays_diode_polarity() {
233        assert_eq!(DiodePolarity::Anode.to_string(), "anode");
234        assert_eq!(DiodePolarity::Cathode.to_string(), "cathode");
235    }
236
237    #[test]
238    fn builds_diode_specs_with_ratings() -> Result<(), Box<dyn std::error::Error>> {
239        let spec = DiodeSpec::new(DiodeKind::Zener)
240            .with_forward_voltage(VoltageRating::new_volts(0.7)?)
241            .with_reverse_voltage_rating(VoltageRating::new_volts(5.1)?)
242            .with_current_rating(CurrentRating::new_amperes(0.02)?);
243
244        assert_eq!(spec.kind(), DiodeKind::Zener);
245        assert_eq!(
246            spec.current_rating().map(CurrentRating::amperes),
247            Some(0.02)
248        );
249        Ok(())
250    }
251}