Skip to main content

codec/
error.rs

1//! Error types for codec operations.
2
3use std::fmt;
4
5/// Result type for codec operations.
6pub type CodecResult<T> = Result<T, CodecError>;
7
8/// Errors that can occur during snapshot/delta encoding/decoding.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum CodecError {
11    /// Wire format error.
12    Wire(wire::WireError),
13
14    /// Baseline tick not found in history.
15    BaselineNotFound {
16        /// The requested baseline tick.
17        requested_tick: u32,
18    },
19
20    /// Entity not found when applying delta.
21    EntityNotFound {
22        /// The missing entity ID.
23        entity_id: u32,
24    },
25
26    /// Component not found when applying delta.
27    ComponentNotFound {
28        /// The entity ID.
29        entity_id: u32,
30        /// The missing component ID.
31        component_id: u16,
32    },
33
34    /// Duplicate entity in create section.
35    DuplicateEntity {
36        /// The duplicate entity ID.
37        entity_id: u32,
38    },
39
40    /// Entity already exists when creating.
41    EntityAlreadyExists {
42        /// The existing entity ID.
43        entity_id: u32,
44    },
45}
46
47impl fmt::Display for CodecError {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            Self::Wire(e) => write!(f, "wire error: {e}"),
51            Self::BaselineNotFound { requested_tick } => {
52                write!(f, "baseline tick {requested_tick} not found in history")
53            }
54            Self::EntityNotFound { entity_id } => {
55                write!(f, "entity {entity_id} not found")
56            }
57            Self::ComponentNotFound {
58                entity_id,
59                component_id,
60            } => {
61                write!(
62                    f,
63                    "component {component_id} not found on entity {entity_id}"
64                )
65            }
66            Self::DuplicateEntity { entity_id } => {
67                write!(f, "duplicate entity {entity_id} in create section")
68            }
69            Self::EntityAlreadyExists { entity_id } => {
70                write!(f, "entity {entity_id} already exists")
71            }
72        }
73    }
74}
75
76impl std::error::Error for CodecError {
77    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
78        match self {
79            Self::Wire(e) => Some(e),
80            _ => None,
81        }
82    }
83}
84
85impl From<wire::WireError> for CodecError {
86    fn from(err: wire::WireError) -> Self {
87        Self::Wire(err)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn error_display_baseline_not_found() {
97        let err = CodecError::BaselineNotFound { requested_tick: 42 };
98        let msg = err.to_string();
99        assert!(msg.contains("42"), "should mention tick");
100        assert!(msg.contains("baseline"), "should mention baseline");
101    }
102
103    #[test]
104    fn error_display_entity_not_found() {
105        let err = CodecError::EntityNotFound { entity_id: 123 };
106        let msg = err.to_string();
107        assert!(msg.contains("123"), "should mention entity id");
108    }
109
110    #[test]
111    fn error_display_component_not_found() {
112        let err = CodecError::ComponentNotFound {
113            entity_id: 10,
114            component_id: 5,
115        };
116        let msg = err.to_string();
117        assert!(msg.contains("10"), "should mention entity id");
118        assert!(msg.contains('5'), "should mention component id");
119    }
120
121    #[test]
122    fn error_display_duplicate_entity() {
123        let err = CodecError::DuplicateEntity { entity_id: 42 };
124        let msg = err.to_string();
125        assert!(msg.contains("42"), "should mention entity id");
126        assert!(msg.contains("duplicate"), "should mention duplicate");
127    }
128
129    #[test]
130    fn error_display_entity_already_exists() {
131        let err = CodecError::EntityAlreadyExists { entity_id: 99 };
132        let msg = err.to_string();
133        assert!(msg.contains("99"), "should mention entity id");
134        assert!(msg.contains("exists"), "should mention exists");
135    }
136
137    #[test]
138    fn error_from_wire_error() {
139        let wire_err = wire::WireError::InvalidMagic { found: 0x1234 };
140        let codec_err: CodecError = wire_err.into();
141        assert!(matches!(codec_err, CodecError::Wire(_)));
142    }
143
144    #[test]
145    fn error_source_wire() {
146        let wire_err = wire::WireError::InvalidMagic { found: 0x1234 };
147        let codec_err = CodecError::Wire(wire_err);
148        let source = std::error::Error::source(&codec_err);
149        assert!(source.is_some(), "should have a source");
150    }
151
152    #[test]
153    fn error_source_none_for_others() {
154        let err = CodecError::EntityNotFound { entity_id: 1 };
155        let source = std::error::Error::source(&err);
156        assert!(source.is_none(), "non-wrapped errors should have no source");
157    }
158
159    #[test]
160    fn error_equality() {
161        let err1 = CodecError::EntityNotFound { entity_id: 42 };
162        let err2 = CodecError::EntityNotFound { entity_id: 42 };
163        let err3 = CodecError::EntityNotFound { entity_id: 43 };
164
165        assert_eq!(err1, err2);
166        assert_ne!(err1, err3);
167    }
168
169    #[test]
170    fn error_is_std_error() {
171        fn assert_error<E: std::error::Error>() {}
172        assert_error::<CodecError>();
173    }
174}