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}