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