1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, num::NonZeroU32, str::FromStr};
5use std::error::Error;
6
7pub mod prelude {
9 pub use crate::{
10 PackageKind, PackageKindParseError, PackageName, PackageNameError, PackagePitch,
11 PackagePitchError, PinCount, PinCountError,
12 };
13}
14
15#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
17pub struct PackageName(String);
18
19impl PackageName {
20 pub fn new(value: impl AsRef<str>) -> Result<Self, PackageNameError> {
26 let trimmed = value.as_ref().trim();
27 if trimmed.is_empty() {
28 Err(PackageNameError::Empty)
29 } else {
30 Ok(Self(trimmed.to_string()))
31 }
32 }
33
34 #[must_use]
36 pub fn as_str(&self) -> &str {
37 &self.0
38 }
39
40 #[must_use]
42 pub fn into_string(self) -> String {
43 self.0
44 }
45}
46
47impl AsRef<str> for PackageName {
48 fn as_ref(&self) -> &str {
49 self.as_str()
50 }
51}
52
53impl fmt::Display for PackageName {
54 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
55 formatter.write_str(self.as_str())
56 }
57}
58
59impl FromStr for PackageName {
60 type Err = PackageNameError;
61
62 fn from_str(value: &str) -> Result<Self, Self::Err> {
63 Self::new(value)
64 }
65}
66
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
69pub enum PackageNameError {
70 Empty,
72}
73
74impl fmt::Display for PackageNameError {
75 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
76 match self {
77 Self::Empty => formatter.write_str("package name cannot be empty"),
78 }
79 }
80}
81
82impl Error for PackageNameError {}
83
84#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
86pub enum PackageKind {
87 Dip,
88 Soic,
89 Sop,
90 Tssop,
91 Qfp,
92 Qfn,
93 Bga,
94 Sot,
95 To,
96 Chip,
97 ThroughHole,
98 Unknown,
99 Custom(String),
100}
101
102impl fmt::Display for PackageKind {
103 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
104 formatter.write_str(match self {
105 Self::Dip => "dip",
106 Self::Soic => "soic",
107 Self::Sop => "sop",
108 Self::Tssop => "tssop",
109 Self::Qfp => "qfp",
110 Self::Qfn => "qfn",
111 Self::Bga => "bga",
112 Self::Sot => "sot",
113 Self::To => "to",
114 Self::Chip => "chip",
115 Self::ThroughHole => "through-hole",
116 Self::Unknown => "unknown",
117 Self::Custom(value) => value.as_str(),
118 })
119 }
120}
121
122impl FromStr for PackageKind {
123 type Err = PackageKindParseError;
124
125 fn from_str(value: &str) -> Result<Self, Self::Err> {
126 let trimmed = value.trim();
127 if trimmed.is_empty() {
128 return Err(PackageKindParseError::Empty);
129 }
130
131 match normalized_token(trimmed).as_str() {
132 "dip" => Ok(Self::Dip),
133 "soic" => Ok(Self::Soic),
134 "sop" => Ok(Self::Sop),
135 "tssop" => Ok(Self::Tssop),
136 "qfp" => Ok(Self::Qfp),
137 "qfn" => Ok(Self::Qfn),
138 "bga" => Ok(Self::Bga),
139 "sot" => Ok(Self::Sot),
140 "to" => Ok(Self::To),
141 "chip" => Ok(Self::Chip),
142 "through-hole" => Ok(Self::ThroughHole),
143 "unknown" => Ok(Self::Unknown),
144 _ => Ok(Self::Custom(trimmed.to_string())),
145 }
146 }
147}
148
149#[derive(Clone, Copy, Debug, Eq, PartialEq)]
151pub enum PackageKindParseError {
152 Empty,
154}
155
156impl fmt::Display for PackageKindParseError {
157 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
158 match self {
159 Self::Empty => formatter.write_str("package kind cannot be empty"),
160 }
161 }
162}
163
164impl Error for PackageKindParseError {}
165
166#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
168pub struct PinCount(NonZeroU32);
169
170impl PinCount {
171 pub fn new(value: u32) -> Result<Self, PinCountError> {
177 NonZeroU32::new(value).map(Self).ok_or(PinCountError::Zero)
178 }
179
180 #[must_use]
182 pub const fn get(self) -> u32 {
183 self.0.get()
184 }
185}
186
187impl fmt::Display for PinCount {
188 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
189 self.get().fmt(formatter)
190 }
191}
192
193#[derive(Clone, Copy, Debug, Eq, PartialEq)]
195pub enum PinCountError {
196 Zero,
198}
199
200impl fmt::Display for PinCountError {
201 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
202 match self {
203 Self::Zero => formatter.write_str("pin count must be non-zero"),
204 }
205 }
206}
207
208impl Error for PinCountError {}
209
210#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
212pub struct PackagePitch {
213 millimeters: f64,
214}
215
216impl PackagePitch {
217 pub fn from_millimeters(value: f64) -> Result<Self, PackagePitchError> {
223 if !value.is_finite() {
224 return Err(PackagePitchError::NonFinite);
225 }
226
227 if value <= 0.0 {
228 return Err(PackagePitchError::NonPositive);
229 }
230
231 Ok(Self { millimeters: value })
232 }
233
234 #[must_use]
236 pub const fn millimeters(self) -> f64 {
237 self.millimeters
238 }
239}
240
241impl fmt::Display for PackagePitch {
242 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
243 write!(formatter, "{} mm", self.millimeters)
244 }
245}
246
247#[derive(Clone, Copy, Debug, Eq, PartialEq)]
249pub enum PackagePitchError {
250 NonFinite,
252 NonPositive,
254}
255
256impl fmt::Display for PackagePitchError {
257 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
258 match self {
259 Self::NonFinite => formatter.write_str("package pitch must be finite"),
260 Self::NonPositive => formatter.write_str("package pitch must be positive"),
261 }
262 }
263}
264
265impl Error for PackagePitchError {}
266
267fn normalized_token(value: &str) -> String {
268 value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
269}
270
271#[cfg(test)]
272mod tests {
273 use super::{
274 PackageKind, PackageKindParseError, PackageName, PackageNameError, PinCount, PinCountError,
275 };
276
277 #[test]
278 fn accepts_valid_package_names() -> Result<(), PackageNameError> {
279 let name = PackageName::new("QFN-32")?;
280
281 assert_eq!(name.as_str(), "QFN-32");
282 assert_eq!(name.to_string(), "QFN-32");
283 Ok(())
284 }
285
286 #[test]
287 fn rejects_empty_package_names() {
288 assert_eq!(PackageName::new(""), Err(PackageNameError::Empty));
289 }
290
291 #[test]
292 fn accepts_valid_pin_counts() -> Result<(), PinCountError> {
293 let count = PinCount::new(8)?;
294
295 assert_eq!(count.get(), 8);
296 assert_eq!(count.to_string(), "8");
297 Ok(())
298 }
299
300 #[test]
301 fn rejects_zero_pin_counts() {
302 assert_eq!(PinCount::new(0), Err(PinCountError::Zero));
303 }
304
305 #[test]
306 fn displays_and_parses_package_kinds() -> Result<(), PackageKindParseError> {
307 assert_eq!("SOIC".parse::<PackageKind>()?, PackageKind::Soic);
308 assert_eq!(
309 "through hole".parse::<PackageKind>()?,
310 PackageKind::ThroughHole
311 );
312 assert_eq!(PackageKind::Bga.to_string(), "bga");
313 Ok(())
314 }
315
316 #[test]
317 fn supports_custom_package_kinds() -> Result<(), PackageKindParseError> {
318 assert_eq!(
319 "wafer-level".parse::<PackageKind>()?,
320 PackageKind::Custom("wafer-level".to_string())
321 );
322 Ok(())
323 }
324}