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
9pub mod prelude {
11 pub use crate::{
12 DiodeKind, DiodeKindParseError, DiodePolarity, DiodePolarityParseError, DiodeSpec,
13 };
14}
15
16#[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub enum DiodeKindParseError {
72 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#[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
121pub enum DiodePolarityParseError {
122 Empty,
124 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#[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 #[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 #[must_use]
162 pub fn kind(&self) -> DiodeKind {
163 self.kind.clone()
164 }
165
166 #[must_use]
168 pub const fn forward_voltage(&self) -> Option<VoltageRating> {
169 self.forward_voltage
170 }
171
172 #[must_use]
174 pub const fn reverse_voltage_rating(&self) -> Option<VoltageRating> {
175 self.reverse_voltage_rating
176 }
177
178 #[must_use]
180 pub const fn current_rating(&self) -> Option<CurrentRating> {
181 self.current_rating
182 }
183
184 #[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 #[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 #[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}