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