display_types/
manufacture.rs1#[non_exhaustive]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ManufactureDate {
14 Manufactured {
17 week: Option<u8>,
19 year: u16,
21 },
22 ModelYear(u16),
24}
25
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47#[cfg_attr(feature = "serde", serde(try_from = "[u8; 3]", into = "[u8; 3]"))]
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct ManufacturerId(pub [u8; 3]);
50
51impl ManufacturerId {
52 pub fn from_ascii(bytes: [u8; 3]) -> Option<Self> {
55 if bytes.iter().all(|&b| b.is_ascii_uppercase()) {
56 Some(Self(bytes))
57 } else {
58 None
59 }
60 }
61
62 pub fn as_str(&self) -> &str {
67 let valid = self.is_valid();
68 debug_assert!(
69 valid,
70 "ManufacturerId invariant violated: bytes must be ASCII uppercase A-Z, got {:?}",
71 self.0
72 );
73 if valid {
74 core::str::from_utf8(&self.0).unwrap_or("")
76 } else {
77 ""
78 }
79 }
80
81 fn is_valid(&self) -> bool {
82 self.0.iter().all(u8::is_ascii_uppercase)
83 }
84}
85
86impl TryFrom<[u8; 3]> for ManufacturerId {
87 type Error = &'static str;
88
89 fn try_from(bytes: [u8; 3]) -> Result<Self, Self::Error> {
91 Self::from_ascii(bytes).ok_or("ManufacturerId bytes must be ASCII uppercase A-Z")
92 }
93}
94
95impl From<ManufacturerId> for [u8; 3] {
96 fn from(id: ManufacturerId) -> Self {
97 id.0
98 }
99}
100
101impl core::fmt::Display for ManufacturerId {
102 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
105 if self.is_valid() {
106 f.write_str(self.as_str())
107 } else {
108 write!(f, "{}", self.0.escape_ascii())
109 }
110 }
111}
112
113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub struct MonitorString(pub [u8; 13]);
125
126impl MonitorString {
127 pub fn as_str(&self) -> &str {
131 let bytes = &self.0;
132 let end = bytes.iter().position(|&b| b == 0x0A).unwrap_or(bytes.len());
133 let trimmed = match bytes[..end].iter().rposition(|&b| b != b' ') {
134 Some(i) => &bytes[..=i],
135 None => &[][..],
136 };
137 core::str::from_utf8(trimmed).unwrap_or("")
138 }
139}
140
141impl core::fmt::Display for MonitorString {
142 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
143 f.write_str(self.as_str())
144 }
145}
146
147impl core::ops::Deref for MonitorString {
148 type Target = str;
149 fn deref(&self) -> &str {
150 self.as_str()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 #[cfg(any(feature = "alloc", feature = "std"))]
158 use crate::alloc::string::ToString;
159
160 #[test]
163 fn manufacturer_id_valid_ascii_uppercase() {
164 let id = ManufacturerId::from_ascii(*b"DEL").unwrap();
165 assert_eq!(id.as_str(), "DEL");
166 }
167
168 #[test]
169 fn manufacturer_id_rejects_lowercase() {
170 assert!(ManufacturerId::from_ascii(*b"del").is_none());
171 }
172
173 #[test]
174 fn manufacturer_id_rejects_digit() {
175 assert!(ManufacturerId::from_ascii(*b"D3L").is_none());
176 }
177
178 #[test]
179 #[cfg(any(feature = "alloc", feature = "std"))]
180 fn manufacturer_id_display() {
181 let id = ManufacturerId::from_ascii(*b"SAM").unwrap();
182 assert_eq!(id.to_string(), "SAM");
183 }
184
185 #[test]
186 fn manufacturer_id_try_from_checks_invariant() {
187 assert_eq!(
188 ManufacturerId::try_from(*b"GSM"),
189 Ok(ManufacturerId(*b"GSM"))
190 );
191 assert!(ManufacturerId::try_from([0xFF, 0x00, 0x00]).is_err());
192 }
193
194 #[test]
195 #[cfg(any(feature = "alloc", feature = "std"))]
196 fn manufacturer_id_display_escapes_invalid_bytes() {
197 assert_eq!(
200 ManufacturerId([0xFF, 0x00, 0x00]).to_string(),
201 r"\xff\x00\x00"
202 );
203 assert_eq!(ManufacturerId(*b"del").to_string(), "del");
204 }
205
206 #[test]
207 #[cfg(not(debug_assertions))]
208 fn manufacturer_id_as_str_invalid_bytes_is_empty_in_release() {
209 assert_eq!(ManufacturerId([0xFF, 0x00, 0x00]).as_str(), "");
210 }
211
212 #[test]
213 #[cfg(debug_assertions)]
214 #[should_panic(expected = "ManufacturerId invariant violated")]
215 fn manufacturer_id_as_str_invalid_bytes_panics_in_debug() {
216 let _ = ManufacturerId([0xFF, 0x00, 0x00]).as_str();
217 }
218
219 #[test]
220 #[cfg(all(feature = "serde", any(feature = "alloc", feature = "std")))]
221 fn manufacturer_id_serde_wire_format_is_byte_array() {
222 let id = ManufacturerId(*b"DEL");
223 let json = serde_json::to_string(&id).unwrap();
224 assert_eq!(json, "[68,69,76]");
225 assert_eq!(serde_json::from_str::<ManufacturerId>(&json).unwrap(), id);
226 }
227
228 #[test]
229 #[cfg(feature = "serde")]
230 fn manufacturer_id_deserialize_rejects_invalid_bytes() {
231 assert!(serde_json::from_str::<ManufacturerId>("[255,0,0]").is_err());
233 assert!(serde_json::from_str::<ManufacturerId>("[100,101,108]").is_err()); }
235
236 #[test]
239 fn monitor_string_strips_terminator_and_spaces() {
240 let mut buf = [b' '; 13];
241 let name = b"DELL U2722D";
242 buf[..name.len()].copy_from_slice(name);
243 buf[name.len()] = 0x0A;
244 assert_eq!(MonitorString(buf).as_str(), "DELL U2722D");
245 }
246
247 #[test]
248 fn monitor_string_no_terminator_strips_trailing_spaces() {
249 let mut buf = [b' '; 13];
250 buf[..3].copy_from_slice(b"ABC");
251 assert_eq!(MonitorString(buf).as_str(), "ABC");
252 }
253
254 #[test]
255 fn monitor_string_all_padding_gives_empty() {
256 assert_eq!(MonitorString([b' '; 13]).as_str(), "");
257 }
258
259 #[test]
260 fn monitor_string_deref() {
261 let mut buf = [b' '; 13];
262 buf[..3].copy_from_slice(b"LEN");
263 buf[3] = 0x0A;
264 let ms = MonitorString(buf);
265 let s: &str = &ms;
266 assert_eq!(s, "LEN");
267 }
268
269 #[test]
270 #[cfg(any(feature = "alloc", feature = "std"))]
271 fn monitor_string_display() {
272 let mut buf = [b' '; 13];
273 buf[..3].copy_from_slice(b"GSM");
274 buf[3] = 0x0A;
275 assert_eq!(MonitorString(buf).to_string(), "GSM");
276 }
277}