Skip to main content

tpack_core/
error.rs

1use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
2use core::{fmt, fmt::Write};
3
4pub type Result<T> = core::result::Result<T, Error>;
5
6#[derive(Debug, Clone, PartialEq, Eq, Default)]
7pub struct ErrorPath {
8    segments: Vec<PathSegment>,
9}
10
11impl ErrorPath {
12    pub fn is_empty(&self) -> bool {
13        self.segments.is_empty()
14    }
15
16    pub fn segments(&self) -> &[PathSegment] {
17        &self.segments
18    }
19
20    pub fn prepend(&mut self, segment: PathSegment) {
21        self.segments.insert(0, segment);
22    }
23}
24
25impl fmt::Display for ErrorPath {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        for segment in &self.segments {
28            f.write_str("/")?;
29            match segment {
30                PathSegment::Field(name) => write_pointer_segment(f, name)?,
31                PathSegment::Index(index) => write!(f, "{index}")?,
32                PathSegment::Key => f.write_str("<key>")?,
33                PathSegment::Value => f.write_str("<value>")?,
34            }
35        }
36        Ok(())
37    }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum PathSegment {
42    Field(String),
43    Index(usize),
44    Key,
45    Value,
46}
47
48impl PathSegment {
49    pub fn field(name: impl Into<String>) -> Self {
50        Self::Field(name.into())
51    }
52
53    pub fn index(index: usize) -> Self {
54        Self::Index(index)
55    }
56}
57
58fn write_pointer_segment(f: &mut fmt::Formatter<'_>, segment: &str) -> fmt::Result {
59    for ch in segment.chars() {
60        match ch {
61            '~' => f.write_str("~0")?,
62            '/' => f.write_str("~1")?,
63            _ => f.write_char(ch)?,
64        }
65    }
66    Ok(())
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct Error {
71    kind: ErrorKind,
72    path: ErrorPath,
73    source: Option<ErrorSource>,
74}
75
76impl Error {
77    pub fn new(kind: ErrorKind) -> Self {
78        Self {
79            kind,
80            path: ErrorPath::default(),
81            source: None,
82        }
83    }
84
85    pub fn at_field(mut self, field: impl Into<String>) -> Self {
86        self.path.prepend(PathSegment::field(field));
87        self
88    }
89
90    pub fn at_index(mut self, index: usize) -> Self {
91        self.path.prepend(PathSegment::index(index));
92        self
93    }
94
95    pub fn at_key(mut self) -> Self {
96        self.path.prepend(PathSegment::Key);
97        self
98    }
99
100    pub fn at_value(mut self) -> Self {
101        self.path.prepend(PathSegment::Value);
102        self
103    }
104
105    pub fn with_source(mut self, source: impl Into<ErrorSource>) -> Self {
106        self.source = Some(source.into());
107        self
108    }
109
110    pub fn type_mismatch(expected: &'static str) -> Self {
111        Self::new(ErrorKind::TypeMismatch { expected })
112    }
113
114    pub fn kind(&self) -> &ErrorKind {
115        &self.kind
116    }
117
118    pub fn path(&self) -> &ErrorPath {
119        &self.path
120    }
121
122    pub fn source_ref(&self) -> Option<&ErrorSource> {
123        self.source.as_ref()
124    }
125
126    pub(crate) fn invalid(message: impl Into<Cow<'static, str>>) -> Self {
127        Self::new(ErrorKind::Invalid(message.into()))
128    }
129
130    pub(crate) fn limit(name: &'static str) -> Self {
131        Self::new(ErrorKind::LimitExceeded(name))
132    }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub enum ErrorSource {
137    Core(Box<Error>),
138    Utf8(core::str::Utf8Error),
139}
140
141impl From<Error> for ErrorSource {
142    fn from(error: Error) -> Self {
143        Self::Core(Box::new(error))
144    }
145}
146
147impl From<core::str::Utf8Error> for ErrorSource {
148    fn from(error: core::str::Utf8Error) -> Self {
149        Self::Utf8(error)
150    }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub enum ErrorKind {
155    UnexpectedEof,
156    TrailingBytes,
157    InvalidMagic,
158    UnsupportedVersion(u8),
159    UnknownEnvelopeMode(u8),
160    UnknownTypeTag(u8),
161    OverlongVarint,
162    VarintOverflow,
163    SchemaLengthMismatch,
164    SchemaLengthExceeded,
165    InvalidSchemaId,
166    UnknownSchemaId,
167    SchemaRefNotAllowed,
168    EmbeddedSchemaMismatch,
169    InvalidDecimalParameters,
170    InvalidTimestampPrecision(u8),
171    StructFieldIdZero,
172    StructFieldNameEmpty,
173    StructFieldFlagsNonZero(u64),
174    DuplicateStructFieldDefinition,
175    DuplicateStructFieldValue,
176    MissingStructFieldValue,
177    InvalidMapKeyType,
178    DuplicateMapKey,
179    NonCanonicalMapKeyOrder,
180    NaNMapKey,
181    UnionVariantNameEmpty,
182    DuplicateUnionVariantName,
183    UnionVariantIndexOutOfRange,
184    EnumSymbolEmpty,
185    DuplicateEnumSymbol,
186    EnumSymbolIndexOutOfRange,
187    InvalidBoolValue(u8),
188    NonCanonicalF32NaN,
189    NonCanonicalF64NaN,
190    DecimalCoefficientExceedsPrecision,
191    TimeOutOfRange,
192    DateTimeTimeOutOfRange,
193    DateTimeTzTimeOutOfRange,
194    InvalidOptionalPresenceMarker(u8),
195    DurationNanosOutOfRange,
196    DurationSignMismatch,
197    Invalid(Cow<'static, str>),
198    LimitExceeded(&'static str),
199    Utf8,
200    TypeMismatch { expected: &'static str },
201}
202
203impl fmt::Display for Error {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        match &self.kind {
206            ErrorKind::UnexpectedEof => f.write_str("unexpected end of input")?,
207            ErrorKind::TrailingBytes => f.write_str("trailing bytes after value")?,
208            ErrorKind::InvalidMagic => f.write_str("invalid TPACK magic")?,
209            ErrorKind::UnsupportedVersion(version) => {
210                write!(f, "unsupported TPACK version {version}")?
211            }
212            ErrorKind::UnknownEnvelopeMode(mode) => {
213                write!(f, "unknown envelope mode 0x{mode:02X}")?
214            }
215            ErrorKind::UnknownTypeTag(tag) => write!(f, "unknown type tag 0x{tag:02X}")?,
216            ErrorKind::OverlongVarint => f.write_str("overlong variable-length integer")?,
217            ErrorKind::VarintOverflow => {
218                f.write_str("variable-length integer exceeds supported size")?
219            }
220            ErrorKind::SchemaLengthMismatch => {
221                f.write_str("schema length does not match descriptor")?
222            }
223            ErrorKind::SchemaLengthExceeded => {
224                f.write_str("schema length exceeds configured limit")?
225            }
226            ErrorKind::InvalidSchemaId => f.write_str("invalid schema id")?,
227            ErrorKind::UnknownSchemaId => f.write_str("unknown schema id")?,
228            ErrorKind::SchemaRefNotAllowed => f.write_str("schema references are not allowed")?,
229            ErrorKind::EmbeddedSchemaMismatch => {
230                f.write_str("embedded schema does not match cached schema")?
231            }
232            ErrorKind::InvalidDecimalParameters => {
233                f.write_str("invalid Decimal(P,S) parameters")?
234            }
235            ErrorKind::InvalidTimestampPrecision(precision) => {
236                write!(f, "invalid timestamp precision {precision}")?
237            }
238            ErrorKind::StructFieldIdZero => {
239                f.write_str("struct FieldId must be greater than zero")?
240            }
241            ErrorKind::StructFieldNameEmpty => {
242                f.write_str("struct field name must be non-empty")?
243            }
244            ErrorKind::StructFieldFlagsNonZero(flags) => {
245                write!(f, "struct field flags must be zero, got {flags}")?
246            }
247            ErrorKind::DuplicateStructFieldDefinition => {
248                f.write_str("duplicate struct field identifier or name")?
249            }
250            ErrorKind::DuplicateStructFieldValue => f.write_str("duplicate struct field value")?,
251            ErrorKind::MissingStructFieldValue => f.write_str("missing struct field value")?,
252            ErrorKind::InvalidMapKeyType => f.write_str("invalid map key type")?,
253            ErrorKind::DuplicateMapKey => f.write_str("duplicate map key")?,
254            ErrorKind::NonCanonicalMapKeyOrder => f.write_str("non-canonical map key order")?,
255            ErrorKind::NaNMapKey => f.write_str("NaN map key")?,
256            ErrorKind::UnionVariantNameEmpty => {
257                f.write_str("union variant name must be non-empty")?
258            }
259            ErrorKind::DuplicateUnionVariantName => f.write_str("duplicate union variant name")?,
260            ErrorKind::UnionVariantIndexOutOfRange => {
261                f.write_str("union variant index out of range")?
262            }
263            ErrorKind::EnumSymbolEmpty => f.write_str("enum symbol must be non-empty")?,
264            ErrorKind::DuplicateEnumSymbol => f.write_str("duplicate enum symbol")?,
265            ErrorKind::EnumSymbolIndexOutOfRange => {
266                f.write_str("enum symbol index out of range")?
267            }
268            ErrorKind::InvalidBoolValue(value) => write!(f, "invalid bool value {value}")?,
269            ErrorKind::NonCanonicalF32NaN => f.write_str("non-canonical f32 NaN")?,
270            ErrorKind::NonCanonicalF64NaN => f.write_str("non-canonical f64 NaN")?,
271            ErrorKind::DecimalCoefficientExceedsPrecision => {
272                f.write_str("Decimal(P,S) coefficient exceeds precision")?
273            }
274            ErrorKind::TimeOutOfRange => f.write_str("time value exceeds nanos-per-day")?,
275            ErrorKind::DateTimeTimeOutOfRange => {
276                f.write_str("datetime time value exceeds nanos-per-day")?
277            }
278            ErrorKind::DateTimeTzTimeOutOfRange => {
279                f.write_str("datetime-tz time value exceeds nanos-per-day")?
280            }
281            ErrorKind::InvalidOptionalPresenceMarker(marker) => {
282                write!(f, "invalid optional presence marker {marker}")?
283            }
284            ErrorKind::DurationNanosOutOfRange => f.write_str("duration nanos out of range")?,
285            ErrorKind::DurationSignMismatch => {
286                f.write_str("duration seconds and nanos signs differ")?
287            }
288            ErrorKind::Invalid(message) => f.write_str(message)?,
289            ErrorKind::LimitExceeded(name) => write!(f, "{name} limit exceeded")?,
290            ErrorKind::Utf8 => f.write_str("invalid UTF-8")?,
291            ErrorKind::TypeMismatch { expected } => {
292                write!(f, "TPACK type mismatch, expected {expected}")?
293            }
294        }
295        if !self.path.is_empty() {
296            write!(f, " at {}", self.path)?;
297        }
298        Ok(())
299    }
300}
301
302#[cfg(feature = "std")]
303impl std::error::Error for Error {
304    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
305        match self.source.as_ref()? {
306            ErrorSource::Core(error) => Some(error),
307            ErrorSource::Utf8(error) => Some(error),
308        }
309    }
310}
311
312impl From<core::str::Utf8Error> for Error {
313    fn from(error: core::str::Utf8Error) -> Self {
314        Self::new(ErrorKind::Utf8).with_source(error)
315    }
316}