Skip to main content

wire/
error.rs

1//! Error types for wire format operations.
2
3use std::fmt;
4
5/// Result type for wire format operations.
6pub type WireResult<T> = Result<T, WireError>;
7
8/// Errors that can occur during wire format encoding/decoding.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum WireError {
11    /// Packet is too small to contain the required header.
12    PacketTooSmall {
13        /// Actual size in bytes.
14        actual: usize,
15        /// Minimum required size in bytes.
16        required: usize,
17    },
18
19    /// Invalid magic number in packet header.
20    InvalidMagic {
21        /// The invalid magic value found.
22        found: u32,
23    },
24
25    /// Unsupported wire version.
26    UnsupportedVersion {
27        /// The version found in the packet.
28        found: u16,
29    },
30
31    /// Invalid flags combination.
32    InvalidFlags {
33        /// The invalid flags value.
34        flags: u16,
35    },
36
37    /// Schema hash mismatch between packet and expected schema.
38    SchemaMismatch {
39        /// Schema hash in the packet.
40        packet_hash: u64,
41        /// Expected schema hash.
42        expected_hash: u64,
43    },
44
45    /// Packet exceeds configured size limit.
46    PacketTooLarge {
47        /// Actual size in bytes.
48        actual: usize,
49        /// Maximum allowed size in bytes.
50        limit: usize,
51    },
52
53    /// Section count exceeds limit.
54    TooManySections {
55        /// Number of sections found.
56        count: usize,
57        /// Maximum allowed sections.
58        limit: usize,
59    },
60
61    /// Entity count exceeds limit for the operation.
62    TooManyEntities {
63        /// Operation type (create, update, destroy).
64        operation: &'static str,
65        /// Number of entities.
66        count: usize,
67        /// Maximum allowed.
68        limit: usize,
69    },
70
71    /// Unknown section tag encountered.
72    UnknownSectionTag {
73        /// The unknown tag value.
74        tag: u8,
75    },
76
77    /// Payload length mismatch.
78    PayloadLengthMismatch {
79        /// Declared length in header.
80        declared: usize,
81        /// Actual available bytes.
82        actual: usize,
83    },
84
85    /// Underlying bitstream error.
86    BitError(bitstream::BitError),
87}
88
89impl fmt::Display for WireError {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self {
92            Self::PacketTooSmall { actual, required } => {
93                write!(
94                    f,
95                    "packet too small: {actual} bytes, need at least {required}"
96                )
97            }
98            Self::InvalidMagic { found } => {
99                write!(f, "invalid magic number: 0x{found:08X}")
100            }
101            Self::UnsupportedVersion { found } => {
102                write!(f, "unsupported wire version: {found}")
103            }
104            Self::InvalidFlags { flags } => {
105                write!(f, "invalid flags: 0x{flags:04X}")
106            }
107            Self::SchemaMismatch {
108                packet_hash,
109                expected_hash,
110            } => {
111                write!(
112                    f,
113                    "schema mismatch: packet has 0x{packet_hash:016X}, expected 0x{expected_hash:016X}"
114                )
115            }
116            Self::PacketTooLarge { actual, limit } => {
117                write!(
118                    f,
119                    "packet too large: {actual} bytes exceeds limit of {limit}"
120                )
121            }
122            Self::TooManySections { count, limit } => {
123                write!(f, "too many sections: {count} exceeds limit of {limit}")
124            }
125            Self::TooManyEntities {
126                operation,
127                count,
128                limit,
129            } => {
130                write!(
131                    f,
132                    "too many entities in {operation}: {count} exceeds limit of {limit}"
133                )
134            }
135            Self::UnknownSectionTag { tag } => {
136                write!(f, "unknown section tag: {tag}")
137            }
138            Self::PayloadLengthMismatch { declared, actual } => {
139                write!(
140                    f,
141                    "payload length mismatch: declared {declared} bytes but {actual} available"
142                )
143            }
144            Self::BitError(e) => {
145                write!(f, "bitstream error: {e}")
146            }
147        }
148    }
149}
150
151impl std::error::Error for WireError {
152    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
153        match self {
154            Self::BitError(e) => Some(e),
155            _ => None,
156        }
157    }
158}
159
160impl From<bitstream::BitError> for WireError {
161    fn from(err: bitstream::BitError) -> Self {
162        Self::BitError(err)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn error_display_packet_too_small() {
172        let err = WireError::PacketTooSmall {
173            actual: 10,
174            required: 28,
175        };
176        let msg = err.to_string();
177        assert!(msg.contains("10"), "should mention actual size");
178        assert!(msg.contains("28"), "should mention required size");
179    }
180
181    #[test]
182    fn error_display_invalid_magic() {
183        let err = WireError::InvalidMagic { found: 0xDEAD_BEEF };
184        let msg = err.to_string();
185        assert!(msg.contains("DEADBEEF"), "should show hex magic");
186    }
187
188    #[test]
189    fn error_display_unsupported_version() {
190        let err = WireError::UnsupportedVersion { found: 99 };
191        let msg = err.to_string();
192        assert!(msg.contains("99"), "should mention version");
193    }
194
195    #[test]
196    fn error_display_invalid_flags() {
197        let err = WireError::InvalidFlags { flags: 0xFF00 };
198        let msg = err.to_string();
199        assert!(msg.contains("FF00"), "should show hex flags");
200    }
201
202    #[test]
203    fn error_display_schema_mismatch() {
204        let err = WireError::SchemaMismatch {
205            packet_hash: 0x1234,
206            expected_hash: 0x5678,
207        };
208        let msg = err.to_string();
209        assert!(msg.contains("1234"), "should show packet hash");
210        assert!(msg.contains("5678"), "should show expected hash");
211    }
212
213    #[test]
214    fn error_display_packet_too_large() {
215        let err = WireError::PacketTooLarge {
216            actual: 100_000,
217            limit: 65536,
218        };
219        let msg = err.to_string();
220        assert!(msg.contains("100000"), "should mention actual size");
221        assert!(msg.contains("65536"), "should mention limit");
222    }
223
224    #[test]
225    fn error_display_too_many_sections() {
226        let err = WireError::TooManySections {
227            count: 100,
228            limit: 16,
229        };
230        let msg = err.to_string();
231        assert!(msg.contains("100"), "should mention count");
232        assert!(msg.contains("16"), "should mention limit");
233    }
234
235    #[test]
236    fn error_display_too_many_entities() {
237        let err = WireError::TooManyEntities {
238            operation: "create",
239            count: 500,
240            limit: 256,
241        };
242        let msg = err.to_string();
243        assert!(msg.contains("create"), "should mention operation");
244        assert!(msg.contains("500"), "should mention count");
245        assert!(msg.contains("256"), "should mention limit");
246    }
247
248    #[test]
249    fn error_display_unknown_section_tag() {
250        let err = WireError::UnknownSectionTag { tag: 42 };
251        let msg = err.to_string();
252        assert!(msg.contains("42"), "should mention tag");
253    }
254
255    #[test]
256    fn error_display_payload_length_mismatch() {
257        let err = WireError::PayloadLengthMismatch {
258            declared: 100,
259            actual: 50,
260        };
261        let msg = err.to_string();
262        assert!(msg.contains("100"), "should mention declared");
263        assert!(msg.contains("50"), "should mention actual");
264    }
265
266    #[test]
267    fn error_from_bit_error() {
268        let bit_err = bitstream::BitError::UnexpectedEof {
269            requested: 8,
270            available: 0,
271        };
272        let wire_err: WireError = bit_err.into();
273        assert!(matches!(wire_err, WireError::BitError(_)));
274
275        let msg = wire_err.to_string();
276        assert!(msg.contains("bitstream"), "should mention source");
277    }
278
279    #[test]
280    fn error_source_bit_error() {
281        let bit_err = bitstream::BitError::UnexpectedEof {
282            requested: 8,
283            available: 0,
284        };
285        let wire_err = WireError::BitError(bit_err);
286
287        // Test std::error::Error::source()
288        let source = std::error::Error::source(&wire_err);
289        assert!(source.is_some(), "should have a source");
290    }
291
292    #[test]
293    fn error_source_none_for_others() {
294        let err = WireError::InvalidMagic { found: 0 };
295        let source = std::error::Error::source(&err);
296        assert!(source.is_none(), "non-wrapped errors should have no source");
297    }
298
299    #[test]
300    fn error_equality() {
301        let err1 = WireError::InvalidMagic { found: 0x1234 };
302        let err2 = WireError::InvalidMagic { found: 0x1234 };
303        let err3 = WireError::InvalidMagic { found: 0x5678 };
304        assert_eq!(err1, err2);
305        assert_ne!(err1, err3);
306    }
307
308    #[test]
309    fn error_is_std_error() {
310        fn assert_error<E: std::error::Error>() {}
311        assert_error::<WireError>();
312    }
313}