1use crate::BufStr;
71use core::fmt;
72use core::fmt::Write;
73
74#[derive(Clone, PartialEq, Eq)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84#[must_use = "this error should be handled or converted to a different type e.g. `pub type DtErr = AnErr<MyKind, 31>;`"]
85pub struct AnErr<K, const REASON_LEN: usize = 31>
86where
87 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
88{
89 pub kind: K,
91
92 pub reason: BufStr<REASON_LEN>,
95}
96
97impl<K, const REASON_LEN: usize> AnErr<K, REASON_LEN>
98where
99 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
100{
101 #[inline(always)]
103 pub const fn new(kind: K) -> Self {
104 Self {
105 kind,
106 reason: BufStr {
107 bytes: [0; REASON_LEN],
108 len: 0,
109 },
110 }
111 }
112
113 #[inline(always)]
115 pub const fn with_reason(kind: K, reason: BufStr<REASON_LEN>) -> Self {
116 Self { kind, reason }
117 }
118
119 #[inline]
123 pub fn with_fmt(kind: K, args: core::fmt::Arguments<'_>) -> Self {
124 let mut reason = BufStr::<REASON_LEN>::default();
125 let _ = write!(&mut reason, "{}", args);
126 Self { kind, reason }
127 }
128
129 #[inline(always)]
132 pub fn context(&mut self, new_reason: BufStr<REASON_LEN>) {
133 self.append_reason(new_reason);
134 }
135
136 #[inline]
138 pub fn context_fmt(&mut self, args: core::fmt::Arguments<'_>) {
139 let mut new_reason = BufStr::<REASON_LEN>::default();
140 let _ = write!(&mut new_reason, "{}", args);
141 self.append_reason(new_reason);
142 }
143
144 #[inline(always)]
145 fn append_reason(&mut self, new_reason: BufStr<REASON_LEN>) {
146 let _ = write!(&mut self.reason, "{}", new_reason.as_str());
147 }
148
149 #[inline(always)]
151 pub const fn kind(&self) -> K {
152 self.kind
153 }
154
155 #[inline(always)]
157 pub const fn reason(&self) -> &BufStr<REASON_LEN> {
158 &self.reason
159 }
160}
161
162impl<K, const REASON_LEN: usize> From<K> for AnErr<K, REASON_LEN>
163where
164 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
165{
166 #[inline]
167 fn from(kind: K) -> Self {
168 Self::new(kind)
169 }
170}
171
172impl<K, const REASON_LEN: usize> core::fmt::Display for AnErr<K, REASON_LEN>
173where
174 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
175{
176 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
177 write!(f, "{:?}", self.kind)?;
178
179 if !self.reason.as_bytes().is_empty() {
180 write!(f, ": {}", self.reason.as_str())?;
181 if self.reason.as_bytes().len() == REASON_LEN {
182 write!(f, " (reason may be truncated)")?;
183 }
184 }
185
186 Ok(())
187 }
188}
189
190impl<K, const REASON_LEN: usize> fmt::Debug for AnErr<K, REASON_LEN>
191where
192 K: Copy + Clone + fmt::Debug + PartialEq + Eq,
193{
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 fmt::Display::fmt(self, f)
196 }
197}
198
199impl<K, const REASON_LEN: usize> core::error::Error for AnErr<K, REASON_LEN> where
200 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq
201{
202}
203
204#[macro_export]
216macro_rules! an_err {
217 ($kind:expr) => {
218 $crate::AnErr::new($kind)
219 };
220
221 ($fmt:literal $(, $arg:expr)* => $inner:expr $(,)?) => {{
222 let mut e = $inner;
223 e.context_fmt(format_args!($fmt $(, $arg)*));
224 e
225 }};
226
227 ($kind:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
228 $crate::AnErr::with_fmt($kind, format_args!($fmt $(, $arg)*))
229 };
230}
231
232#[cfg(feature = "defmt")]
233impl<K, const REASON_LEN: usize> defmt::Format for AnErr<K, REASON_LEN>
234where
235 K: defmt::Format + Copy + Clone + core::fmt::Debug + PartialEq + Eq,
236{
237 fn format(&self, f: defmt::Formatter) {
238 if self.reason.as_bytes().is_empty() {
239 defmt::write!(f, "{}", self.kind);
240 } else {
241 defmt::write!(f, "{}: {}", self.kind, self.reason.as_str());
242 if self.reason.as_bytes().len() == REASON_LEN {
243 defmt::write!(f, " (reason may be truncated)");
244 }
245 }
246 }
247}
248
249#[cfg(feature = "wire")]
250impl<K, const REASON_LEN: usize> AnErr<K, REASON_LEN>
251where
252 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
253{
254 pub fn to_wire_bytes(
259 &self,
260 kind_to_u16: impl Fn(K) -> u16,
261 buf: &mut [u8],
262 ) -> Result<usize, ()> {
263 let needed = Self::wire_size();
264 if buf.len() < needed {
265 return Err(());
266 }
267
268 let mut offset = 0;
269 buf[offset] = 1; offset += 1;
271
272 let kind_val = kind_to_u16(self.kind);
273 buf[offset..offset + 2].copy_from_slice(&kind_val.to_le_bytes());
274 offset += 2;
275
276 buf[offset..offset + REASON_LEN].copy_from_slice(&self.reason.bytes);
277
278 Ok(needed)
279 }
280
281 pub const fn wire_size() -> usize {
283 1 + 2 + REASON_LEN
284 }
285
286 pub fn from_wire_bytes(bytes: &[u8], u16_to_kind: impl Fn(u16) -> Option<K>) -> Option<Self> {
291 if bytes.len() != Self::wire_size() {
292 return None;
293 }
294
295 let mut offset = 0;
296 if bytes[offset] != 1 {
297 return None;
298 }
299 offset += 1;
300
301 let kind_bytes = <[u8; 2]>::try_from(&bytes[offset..offset + 2]).ok()?;
302 let kind_u16 = u16::from_le_bytes(kind_bytes);
303 let kind = u16_to_kind(kind_u16)?;
304
305 offset += 2;
306
307 let reason_bytes = &bytes[offset..offset + REASON_LEN];
308 let reason = BufStr::from_bytes(reason_bytes);
309
310 Some(Self { kind, reason })
311 }
312}
313
314#[cfg(feature = "wire")]
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
320 #[repr(u8)]
321 enum TestKind {
322 Foo,
323 }
324
325 #[test]
326 fn test_wire_roundtrip_with_append() {
327 let err: AnErr<TestKind, 15> = an_err!("bar" => an_err!(TestKind::Foo, "foo"));
328
329 let size = AnErr::<TestKind, 15>::wire_size();
330 let mut buf = [0u8; 32];
331
332 let written = err.to_wire_bytes(|k| k as u16, &mut buf).unwrap();
333 assert_eq!(written, size);
334
335 let decoded = AnErr::<TestKind, 15>::from_wire_bytes(&buf[..written], |v| {
336 if v == 0 { Some(TestKind::Foo) } else { None }
337 })
338 .unwrap();
339
340 assert_eq!(decoded.kind(), TestKind::Foo);
341 assert_eq!(decoded.reason.as_str(), "foobar");
342 }
343}