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