1use std::fmt;
4
5use schema::{ComponentId, FieldId};
6
7pub type CodecResult<T> = Result<T, CodecError>;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum CodecError {
13 Wire(wire::DecodeError),
15
16 Bitstream(bitstream::BitError),
18
19 OutputTooSmall { needed: usize, available: usize },
21
22 SchemaMismatch { expected: u64, found: u64 },
24
25 LimitsExceeded {
27 kind: LimitKind,
28 limit: usize,
29 actual: usize,
30 },
31
32 InvalidMask { kind: MaskKind, reason: MaskReason },
34
35 InvalidValue {
37 component: ComponentId,
38 field: FieldId,
39 reason: ValueReason,
40 },
41
42 InvalidEntityOrder { previous: u32, current: u32 },
44
45 TrailingSectionData {
47 section: wire::SectionTag,
48 remaining_bits: usize,
49 },
50
51 UnexpectedSection { section: wire::SectionTag },
53
54 DuplicateSection { section: wire::SectionTag },
56
57 DuplicateUpdateEncoding,
59
60 BaselineTickMismatch { expected: u32, found: u32 },
62
63 BaselineNotFound {
65 requested_tick: u32,
67 },
68
69 EntityNotFound {
71 entity_id: u32,
73 },
74
75 ComponentNotFound {
77 entity_id: u32,
79 component_id: u16,
81 },
82
83 DuplicateEntity {
85 entity_id: u32,
87 },
88
89 EntityAlreadyExists {
91 entity_id: u32,
93 },
94
95 SessionMissing,
97
98 SessionInitInvalid,
100
101 SessionUnsupportedMode { mode: u8 },
103
104 SessionOutOfOrder { previous: u32, current: u32 },
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum LimitKind {
111 EntitiesCreate,
112 EntitiesUpdate,
113 EntitiesDestroy,
114 TotalEntitiesAfterApply,
115 ComponentsPerEntity,
116 FieldsPerComponent,
117 SectionBytes,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum MaskKind {
123 ComponentMask,
124 FieldMask { component: ComponentId },
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum MaskReason {
130 NotEnoughBits { expected: usize, available: usize },
131 FieldCountMismatch { expected: usize, actual: usize },
132 MissingField { field: FieldId },
133 UnknownComponent { component: ComponentId },
134 InvalidComponentId { raw: u16 },
135 InvalidFieldIndex { field_index: usize, max: usize },
136 ComponentPresenceMismatch { component: ComponentId },
137 EmptyFieldMask { component: ComponentId },
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum ValueReason {
143 UnsignedOutOfRange {
144 bits: u8,
145 value: u64,
146 },
147 SignedOutOfRange {
148 bits: u8,
149 value: i64,
150 },
151 VarUIntOutOfRange {
152 value: u64,
153 },
154 VarSIntOutOfRange {
155 value: i64,
156 },
157 FixedPointOutOfRange {
158 min_q: i64,
159 max_q: i64,
160 value: i64,
161 },
162 TypeMismatch {
163 expected: &'static str,
164 found: &'static str,
165 },
166}
167
168impl fmt::Display for CodecError {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 match self {
171 Self::Wire(e) => write!(f, "wire error: {e}"),
172 Self::Bitstream(e) => write!(f, "bitstream error: {e}"),
173 Self::OutputTooSmall { needed, available } => {
174 write!(f, "output too small: need {needed}, have {available}")
175 }
176 Self::SchemaMismatch { expected, found } => {
177 write!(
178 f,
179 "schema hash mismatch: expected 0x{expected:016X}, found 0x{found:016X}"
180 )
181 }
182 Self::LimitsExceeded {
183 kind,
184 limit,
185 actual,
186 } => {
187 write!(f, "{kind} limit exceeded: {actual} > {limit}")
188 }
189 Self::InvalidMask { kind, reason } => {
190 write!(f, "invalid {kind}: {reason}")
191 }
192 Self::InvalidValue {
193 component,
194 field,
195 reason,
196 } => {
197 write!(f, "invalid value for {component:?}:{field:?}: {reason}")
198 }
199 Self::InvalidEntityOrder { previous, current } => {
200 write!(f, "entity order invalid: {previous} then {current}")
201 }
202 Self::TrailingSectionData {
203 section,
204 remaining_bits,
205 } => {
206 write!(
207 f,
208 "trailing data in section {section:?}: {remaining_bits} bits"
209 )
210 }
211 Self::UnexpectedSection { section } => {
212 write!(f, "unexpected section {section:?} in full snapshot")
213 }
214 Self::DuplicateSection { section } => {
215 write!(f, "duplicate section {section:?} in packet")
216 }
217 Self::DuplicateUpdateEncoding => {
218 write!(f, "multiple update encodings present in packet")
219 }
220 Self::BaselineTickMismatch { expected, found } => {
221 write!(
222 f,
223 "baseline tick mismatch: expected {expected}, found {found}"
224 )
225 }
226 Self::BaselineNotFound { requested_tick } => {
227 write!(f, "baseline tick {requested_tick} not found in history")
228 }
229 Self::EntityNotFound { entity_id } => {
230 write!(f, "entity {entity_id} not found")
231 }
232 Self::ComponentNotFound {
233 entity_id,
234 component_id,
235 } => {
236 write!(
237 f,
238 "component {component_id} not found on entity {entity_id}"
239 )
240 }
241 Self::DuplicateEntity { entity_id } => {
242 write!(f, "duplicate entity {entity_id} in create section")
243 }
244 Self::EntityAlreadyExists { entity_id } => {
245 write!(f, "entity {entity_id} already exists")
246 }
247 Self::SessionMissing => {
248 write!(f, "session state missing for compact packet")
249 }
250 Self::SessionInitInvalid => {
251 write!(f, "session init packet invalid")
252 }
253 Self::SessionUnsupportedMode { mode } => {
254 write!(f, "session compact mode {mode} unsupported")
255 }
256 Self::SessionOutOfOrder { previous, current } => {
257 write!(f, "session packet out of order: {previous} then {current}")
258 }
259 }
260 }
261}
262
263impl fmt::Display for LimitKind {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 let name = match self {
266 Self::EntitiesCreate => "entities",
267 Self::EntitiesUpdate => "update entities",
268 Self::EntitiesDestroy => "destroy entities",
269 Self::TotalEntitiesAfterApply => "total entities",
270 Self::ComponentsPerEntity => "components per entity",
271 Self::FieldsPerComponent => "fields per component",
272 Self::SectionBytes => "section bytes",
273 };
274 write!(f, "{name}")
275 }
276}
277
278impl fmt::Display for MaskKind {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 match self {
281 Self::ComponentMask => write!(f, "component mask"),
282 Self::FieldMask { component } => write!(f, "field mask for {component:?}"),
283 }
284 }
285}
286
287impl fmt::Display for MaskReason {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 match self {
290 Self::NotEnoughBits {
291 expected,
292 available,
293 } => {
294 write!(f, "need {expected} bits, have {available}")
295 }
296 Self::FieldCountMismatch { expected, actual } => {
297 write!(f, "expected {expected} fields, got {actual}")
298 }
299 Self::MissingField { field } => {
300 write!(f, "missing field {field:?} in full snapshot")
301 }
302 Self::UnknownComponent { component } => {
303 write!(f, "unknown component {component:?} in snapshot")
304 }
305 Self::InvalidComponentId { raw } => {
306 write!(f, "invalid component id {raw} in snapshot")
307 }
308 Self::InvalidFieldIndex { field_index, max } => {
309 write!(f, "field index {field_index} exceeds max {max}")
310 }
311 Self::ComponentPresenceMismatch { component } => {
312 write!(f, "component presence mismatch for {component:?}")
313 }
314 Self::EmptyFieldMask { component } => {
315 write!(f, "empty field mask for {component:?} is invalid")
316 }
317 }
318 }
319}
320
321impl fmt::Display for ValueReason {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 match self {
324 Self::UnsignedOutOfRange { bits, value } => {
325 write!(f, "unsigned value {value} does not fit in {bits} bits")
326 }
327 Self::SignedOutOfRange { bits, value } => {
328 write!(f, "signed value {value} does not fit in {bits} bits")
329 }
330 Self::VarUIntOutOfRange { value } => {
331 write!(f, "varuint value {value} exceeds u32::MAX")
332 }
333 Self::VarSIntOutOfRange { value } => {
334 write!(f, "varsint value {value} exceeds i32 range")
335 }
336 Self::FixedPointOutOfRange {
337 min_q,
338 max_q,
339 value,
340 } => {
341 write!(f, "fixed-point value {value} outside [{min_q}, {max_q}]")
342 }
343 Self::TypeMismatch { expected, found } => {
344 write!(f, "expected {expected} but got {found}")
345 }
346 }
347 }
348}
349
350impl std::error::Error for CodecError {
351 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
352 match self {
353 Self::Wire(e) => Some(e),
354 Self::Bitstream(e) => Some(e),
355 _ => None,
356 }
357 }
358}
359
360impl From<wire::DecodeError> for CodecError {
361 fn from(err: wire::DecodeError) -> Self {
362 Self::Wire(err)
363 }
364}
365
366impl From<bitstream::BitError> for CodecError {
367 fn from(err: bitstream::BitError) -> Self {
368 Self::Bitstream(err)
369 }
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn error_display_baseline_not_found() {
378 let err = CodecError::BaselineNotFound { requested_tick: 42 };
379 let msg = err.to_string();
380 assert!(msg.contains("42"), "should mention tick");
381 assert!(msg.contains("baseline"), "should mention baseline");
382 }
383
384 #[test]
385 fn error_display_entity_not_found() {
386 let err = CodecError::EntityNotFound { entity_id: 123 };
387 let msg = err.to_string();
388 assert!(msg.contains("123"), "should mention entity id");
389 }
390
391 #[test]
392 fn error_display_component_not_found() {
393 let err = CodecError::ComponentNotFound {
394 entity_id: 10,
395 component_id: 5,
396 };
397 let msg = err.to_string();
398 assert!(msg.contains("10"), "should mention entity id");
399 assert!(msg.contains('5'), "should mention component id");
400 }
401
402 #[test]
403 fn error_display_duplicate_entity() {
404 let err = CodecError::DuplicateEntity { entity_id: 42 };
405 let msg = err.to_string();
406 assert!(msg.contains("42"), "should mention entity id");
407 assert!(msg.contains("duplicate"), "should mention duplicate");
408 }
409
410 #[test]
411 fn error_display_entity_already_exists() {
412 let err = CodecError::EntityAlreadyExists { entity_id: 99 };
413 let msg = err.to_string();
414 assert!(msg.contains("99"), "should mention entity id");
415 assert!(msg.contains("exists"), "should mention exists");
416 }
417
418 #[test]
419 fn error_from_wire_error() {
420 let wire_err = wire::DecodeError::InvalidMagic { found: 0x1234 };
421 let codec_err: CodecError = wire_err.into();
422 assert!(matches!(codec_err, CodecError::Wire(_)));
423 }
424
425 #[test]
426 fn error_from_bitstream_error() {
427 let bit_err = bitstream::BitError::UnexpectedEof {
428 requested: 1,
429 available: 0,
430 };
431 let codec_err: CodecError = bit_err.into();
432 assert!(matches!(codec_err, CodecError::Bitstream(_)));
433 }
434
435 #[test]
436 fn error_source_wire() {
437 let wire_err = wire::DecodeError::InvalidMagic { found: 0x1234 };
438 let codec_err = CodecError::Wire(wire_err);
439 let source = std::error::Error::source(&codec_err);
440 assert!(source.is_some(), "should have a source");
441 }
442
443 #[test]
444 fn error_source_none_for_others() {
445 let err = CodecError::EntityNotFound { entity_id: 1 };
446 let source = std::error::Error::source(&err);
447 assert!(source.is_none(), "non-wrapped errors should have no source");
448 }
449
450 #[test]
451 fn error_equality() {
452 let err1 = CodecError::EntityNotFound { entity_id: 42 };
453 let err2 = CodecError::EntityNotFound { entity_id: 42 };
454 let err3 = CodecError::EntityNotFound { entity_id: 43 };
455
456 assert_eq!(err1, err2);
457 assert_ne!(err1, err3);
458 }
459
460 #[test]
461 fn error_is_std_error() {
462 fn assert_error<E: std::error::Error>() {}
463 assert_error::<CodecError>();
464 }
465}