Skip to main content

wowlab_parsers/parsers/parcel/
errors.rs

1//! Canonical error codes matching `SPEC.md` §7 byte-for-byte.
2
3wowlab_engine_macros::define_error! {
4/// Errors surfaced by the parcel codec on encode and decode (precedence per SPEC §6 / §6.1).
5#[derive(Clone, Debug)]
6pub struct ParcelError {
7    #[source]
8    kind: ParcelErrorKind,
9}
10
11#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)]
12enum ParcelErrorKind {
13    #[error("ERR_ENCODED_TOO_LARGE")]
14    EncodedTooLarge,
15    #[error("ERR_PREFIX")]
16    Prefix,
17    #[error("ERR_FIELDS")]
18    Fields,
19    #[error("ERR_CONTENT_TYPE")]
20    ContentType,
21    #[error("ERR_CHECKSUM_TOKEN")]
22    ChecksumToken,
23    #[error("ERR_BASE64_TOKEN")]
24    Base64Token,
25    #[error("ERR_BASE64_DECODE")]
26    Base64Decode {
27        #[source]
28        source: Option<base64::DecodeError>,
29    },
30    #[error("ERR_BODY_TOO_LARGE")]
31    BodyTooLarge,
32    #[error("ERR_DEFLATE_UNAVAILABLE")]
33    DeflateUnavailable,
34    #[error("ERR_DECOMPRESS")]
35    Decompress,
36    #[error("ERR_PAYLOAD_TOO_LARGE")]
37    PayloadTooLarge,
38    #[error("ERR_CHECKSUM_MISMATCH")]
39    ChecksumMismatch,
40    #[error("ERR_PAYLOAD_TYPE")]
41    PayloadType,
42    #[error("ERR_COMPRESS")]
43    Compress,
44}
45}
46
47#[expect(
48    non_upper_case_globals,
49    reason = "associated constants preserve the stable ParcelError category API"
50)]
51impl ParcelError {
52    pub const EncodedTooLarge: Self = Self::new(ParcelErrorKind::EncodedTooLarge);
53    pub const Prefix: Self = Self::new(ParcelErrorKind::Prefix);
54    pub const Fields: Self = Self::new(ParcelErrorKind::Fields);
55    pub const ContentType: Self = Self::new(ParcelErrorKind::ContentType);
56    pub const ChecksumToken: Self = Self::new(ParcelErrorKind::ChecksumToken);
57    pub const Base64Token: Self = Self::new(ParcelErrorKind::Base64Token);
58    pub const Base64Decode: Self = Self::new(ParcelErrorKind::Base64Decode { source: None });
59    pub const BodyTooLarge: Self = Self::new(ParcelErrorKind::BodyTooLarge);
60    pub const DeflateUnavailable: Self = Self::new(ParcelErrorKind::DeflateUnavailable);
61    pub const Decompress: Self = Self::new(ParcelErrorKind::Decompress);
62    pub const PayloadTooLarge: Self = Self::new(ParcelErrorKind::PayloadTooLarge);
63    pub const ChecksumMismatch: Self = Self::new(ParcelErrorKind::ChecksumMismatch);
64    pub const PayloadType: Self = Self::new(ParcelErrorKind::PayloadType);
65    pub const Compress: Self = Self::new(ParcelErrorKind::Compress);
66
67    const fn new(kind: ParcelErrorKind) -> Self {
68        Self { kind }
69    }
70
71    /// The canonical SPEC §7 error string (stable, externally-visible).
72    #[must_use]
73    pub const fn code(&self) -> &'static str {
74        match self.kind {
75            ParcelErrorKind::EncodedTooLarge => "ERR_ENCODED_TOO_LARGE",
76            ParcelErrorKind::Prefix => "ERR_PREFIX",
77            ParcelErrorKind::Fields => "ERR_FIELDS",
78            ParcelErrorKind::ContentType => "ERR_CONTENT_TYPE",
79            ParcelErrorKind::ChecksumToken => "ERR_CHECKSUM_TOKEN",
80            ParcelErrorKind::Base64Token => "ERR_BASE64_TOKEN",
81            ParcelErrorKind::Base64Decode { .. } => "ERR_BASE64_DECODE",
82            ParcelErrorKind::BodyTooLarge => "ERR_BODY_TOO_LARGE",
83            ParcelErrorKind::DeflateUnavailable => "ERR_DEFLATE_UNAVAILABLE",
84            ParcelErrorKind::Decompress => "ERR_DECOMPRESS",
85            ParcelErrorKind::PayloadTooLarge => "ERR_PAYLOAD_TOO_LARGE",
86            ParcelErrorKind::ChecksumMismatch => "ERR_CHECKSUM_MISMATCH",
87            ParcelErrorKind::PayloadType => "ERR_PAYLOAD_TYPE",
88            ParcelErrorKind::Compress => "ERR_COMPRESS",
89        }
90    }
91}
92
93impl From<base64::DecodeError> for ParcelError {
94    fn from(source: base64::DecodeError) -> Self {
95        Self::new(ParcelErrorKind::Base64Decode {
96            source: Some(source),
97        })
98    }
99}
100
101impl PartialEq for ParcelError {
102    fn eq(&self, other: &Self) -> bool {
103        self.code() == other.code()
104    }
105}
106
107impl Eq for ParcelError {}
108
109wowlab_engine_macros::define_error! {
110/// Errors raised when constructing a [`crate::parcel::Codec`].
111#[derive(Clone, Debug, Eq, PartialEq)]
112pub struct CodecBuildError(CodecBuildErrorKind);
113
114#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)]
115enum CodecBuildErrorKind {
116    #[error("`prefix` must be a non-empty string")]
117    EmptyPrefix,
118    #[error("prefix must not contain `.` (it is the field separator)")]
119    PrefixContainsDot,
120    #[error("`contentTypes` must be a non-empty array of strings")]
121    EmptyContentTypes,
122    #[error("contentType `{0}` must match ^[a-z][a-z0-9]*$")]
123    InvalidContentType(String),
124    #[error("defaultContentType `{0}` not in contentTypes")]
125    InvalidDefaultContentType(String),
126}
127}
128
129impl CodecBuildError {
130    pub(super) const fn empty_prefix() -> Self {
131        Self(CodecBuildErrorKind::EmptyPrefix)
132    }
133
134    pub(super) const fn prefix_contains_dot() -> Self {
135        Self(CodecBuildErrorKind::PrefixContainsDot)
136    }
137
138    pub(super) const fn empty_content_types() -> Self {
139        Self(CodecBuildErrorKind::EmptyContentTypes)
140    }
141
142    pub(super) fn invalid_content_type(content_type: String) -> Self {
143        Self(CodecBuildErrorKind::InvalidContentType(content_type))
144    }
145
146    pub(super) fn invalid_default_content_type(content_type: String) -> Self {
147        Self(CodecBuildErrorKind::InvalidDefaultContentType(content_type))
148    }
149}