1use crate::CodecError;
4use gbp_core::{ErrorClass, errors::ErrorSpec};
5use serde::{Deserialize, Serialize};
6use serde_bytes::ByteBuf;
7
8#[derive(Clone, Debug, Serialize, Deserialize)]
14pub struct ErrorObject {
15 pub code: u16,
17 pub class: u8,
19 pub retryable: bool,
21 pub fatal: bool,
23 pub reason: String,
25 #[serde(rename = "det")]
27 pub details_cbor: ByteBuf,
28}
29
30impl ErrorObject {
31 pub fn from_spec(spec: ErrorSpec, reason: impl Into<String>) -> Self {
33 Self {
34 code: spec.code,
35 class: spec.class as u8,
36 retryable: spec.retryable,
37 fatal: spec.fatal,
38 reason: reason.into(),
39 details_cbor: ByteBuf::new(),
40 }
41 }
42
43 pub fn new(
46 code: u16,
47 class: ErrorClass,
48 retryable: bool,
49 fatal: bool,
50 reason: impl Into<String>,
51 ) -> Self {
52 Self {
53 code,
54 class: class as u8,
55 retryable,
56 fatal,
57 reason: reason.into(),
58 details_cbor: ByteBuf::new(),
59 }
60 }
61
62 pub fn to_cbor(&self) -> Vec<u8> {
64 let mut buf = Vec::new();
65 ciborium::into_writer(self, &mut buf).expect("cbor encode");
66 buf
67 }
68
69 pub fn from_cbor(data: &[u8]) -> Result<Self, CodecError> {
71 ciborium::from_reader(data).map_err(|e| CodecError::Decode(e.to_string()))
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn new_round_trip() {
81 let err = ErrorObject::new(404, ErrorClass::Schema, true, false, "not found");
82 let bytes = err.to_cbor();
83 let decoded = ErrorObject::from_cbor(&bytes).unwrap();
84 assert_eq!(decoded.code, 404);
85 assert_eq!(decoded.class, ErrorClass::Schema as u8);
86 assert!(decoded.retryable);
87 assert!(!decoded.fatal);
88 assert_eq!(decoded.reason, "not found");
89 assert!(decoded.details_cbor.is_empty());
90 }
91
92 #[test]
93 fn fatal_error_round_trip() {
94 let err = ErrorObject::new(500, ErrorClass::Crypto, false, true, "aead failure");
95 let decoded = ErrorObject::from_cbor(&err.to_cbor()).unwrap();
96 assert_eq!(decoded.code, 500);
97 assert!(decoded.fatal);
98 assert!(!decoded.retryable);
99 }
100
101 #[test]
102 fn invalid_cbor_returns_decode_error() {
103 assert!(matches!(
104 ErrorObject::from_cbor(b"\xFF\xFF"),
105 Err(CodecError::Decode(_))
106 ));
107 }
108}