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 if self.reason.as_bytes().len() == REASON_LEN {
241 defmt::write!(
242 f,
243 "{}: {} (reason may be truncated)",
244 self.kind,
245 self.reason.as_str()
246 );
247 } else {
248 defmt::write!(f, "{}: {}", self.kind, self.reason.as_str());
249 }
250 }
251}
252
253#[cfg(feature = "wire")]
254impl<K, const REASON_LEN: usize> AnErr<K, REASON_LEN>
255where
256 K: Copy + Clone + core::fmt::Debug + PartialEq + Eq,
257{
258 pub fn to_wire_bytes(
263 &self,
264 kind_to_u16: impl Fn(K) -> u16,
265 buf: &mut [u8],
266 ) -> Result<usize, ()> {
267 let needed = Self::wire_size();
268 if buf.len() < needed {
269 return Err(());
270 }
271
272 let mut offset = 0;
273 buf[offset] = 1; offset += 1;
275
276 let kind_val = kind_to_u16(self.kind);
277 buf[offset..offset + 2].copy_from_slice(&kind_val.to_le_bytes());
278 offset += 2;
279
280 buf[offset..offset + REASON_LEN].copy_from_slice(&self.reason.bytes);
281
282 Ok(needed)
283 }
284
285 pub const fn wire_size() -> usize {
287 1 + 2 + REASON_LEN
288 }
289
290 pub fn from_wire_bytes(bytes: &[u8], u16_to_kind: impl Fn(u16) -> Option<K>) -> Option<Self> {
295 if bytes.len() != Self::wire_size() {
296 return None;
297 }
298
299 let mut offset = 0;
300 if bytes[offset] != 1 {
301 return None;
302 }
303 offset += 1;
304
305 let kind_bytes = <[u8; 2]>::try_from(&bytes[offset..offset + 2]).ok()?;
306 let kind_u16 = u16::from_le_bytes(kind_bytes);
307 let kind = u16_to_kind(kind_u16)?;
308
309 offset += 2;
310
311 let reason_bytes = &bytes[offset..offset + REASON_LEN];
312 let reason = BufStr::from_bytes(reason_bytes);
313
314 Some(Self { kind, reason })
315 }
316}
317
318#[cfg(feature = "wire")]
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
324 #[repr(u8)]
325 enum TestKind {
326 Foo,
327 }
328
329 #[test]
330 fn test_wire_roundtrip_with_append() {
331 let err: AnErr<TestKind, 15> = an_err!("bar" => an_err!(TestKind::Foo, "foo"));
332
333 let size = AnErr::<TestKind, 15>::wire_size();
334 let mut buf = [0u8; 32];
335
336 let written = err.to_wire_bytes(|k| k as u16, &mut buf).unwrap();
337 assert_eq!(written, size);
338
339 let decoded = AnErr::<TestKind, 15>::from_wire_bytes(&buf[..written], |v| {
340 if v == 0 { Some(TestKind::Foo) } else { None }
341 })
342 .unwrap();
343
344 assert_eq!(decoded.kind(), TestKind::Foo);
345 assert_eq!(decoded.reason.as_str(), "foobar");
346 }
347}