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