use crate::CodecError;
use gbp_core::{ErrorClass, errors::ErrorSpec};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ErrorObject {
pub code: u16,
pub class: u8,
pub retryable: bool,
pub fatal: bool,
pub reason: String,
#[serde(rename = "det")]
pub details_cbor: ByteBuf,
}
impl ErrorObject {
pub fn from_spec(spec: ErrorSpec, reason: impl Into<String>) -> Self {
Self {
code: spec.code,
class: spec.class as u8,
retryable: spec.retryable,
fatal: spec.fatal,
reason: reason.into(),
details_cbor: ByteBuf::new(),
}
}
pub fn new(
code: u16,
class: ErrorClass,
retryable: bool,
fatal: bool,
reason: impl Into<String>,
) -> Self {
Self {
code,
class: class as u8,
retryable,
fatal,
reason: reason.into(),
details_cbor: ByteBuf::new(),
}
}
pub fn to_cbor(&self) -> Vec<u8> {
let mut buf = Vec::new();
ciborium::into_writer(self, &mut buf).expect("cbor encode");
buf
}
pub fn from_cbor(data: &[u8]) -> Result<Self, CodecError> {
ciborium::from_reader(data).map_err(|e| CodecError::Decode(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_round_trip() {
let err = ErrorObject::new(404, ErrorClass::Schema, true, false, "not found");
let bytes = err.to_cbor();
let decoded = ErrorObject::from_cbor(&bytes).unwrap();
assert_eq!(decoded.code, 404);
assert_eq!(decoded.class, ErrorClass::Schema as u8);
assert!(decoded.retryable);
assert!(!decoded.fatal);
assert_eq!(decoded.reason, "not found");
assert!(decoded.details_cbor.is_empty());
}
#[test]
fn fatal_error_round_trip() {
let err = ErrorObject::new(500, ErrorClass::Crypto, false, true, "aead failure");
let decoded = ErrorObject::from_cbor(&err.to_cbor()).unwrap();
assert_eq!(decoded.code, 500);
assert!(decoded.fatal);
assert!(!decoded.retryable);
}
#[test]
fn invalid_cbor_returns_decode_error() {
assert!(matches!(
ErrorObject::from_cbor(b"\xFF\xFF"),
Err(CodecError::Decode(_))
));
}
}