Skip to main content

zerodds_cdr/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Encoder and decoder errors.
4//!
5//! Deliberately separated: a value can only be encoded or decoded, but
6//! the error categories differ. Encoder errors are buffer/format
7//! related; decoder errors are additionally validation errors
8//! (UnexpectedEof, InvalidUtf8, etc.).
9
10use core::fmt;
11
12/// Error while encoding a value.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum EncodeError {
15    /// Write buffer too small. Cannot happen with a `Vec<u8>`-based
16    /// writer; can happen with fixed buffers (no_std + static array).
17    BufferTooSmall {
18        /// How many additional bytes would have been needed.
19        needed: usize,
20        /// How many were actually available.
21        available: usize,
22    },
23    /// Value cannot be encoded — e.g. a `String` length exceeds
24    /// `u32::MAX` (XCDR limit).
25    ValueOutOfRange {
26        /// Description of the violation.
27        message: &'static str,
28    },
29    /// A mutable encode omitted a non-optional member
30    /// (XTypes 1.3 §7.4.1.2.3 — "the serialized representation MUST
31    /// contain at least the values of all the non-optional members").
32    MissingNonOptionalMember {
33        /// Member ID that was missing.
34        member_id: u32,
35    },
36}
37
38impl fmt::Display for EncodeError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            Self::BufferTooSmall { needed, available } => write!(
42                f,
43                "encoder buffer too small: needed {needed} bytes, available {available}"
44            ),
45            Self::ValueOutOfRange { message } => write!(f, "value out of range: {message}"),
46            Self::MissingNonOptionalMember { member_id } => write!(
47                f,
48                "mutable struct missing non-optional member: id={member_id}"
49            ),
50        }
51    }
52}
53
54#[cfg(feature = "std")]
55impl std::error::Error for EncodeError {}
56
57/// Error while decoding a value.
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub enum DecodeError {
60    /// Input ended before the expected end.
61    UnexpectedEof {
62        /// How many more bytes were expected.
63        needed: usize,
64        /// Position in the stream where the EOF occurred.
65        offset: usize,
66    },
67    /// UTF-8 validation for a string value failed.
68    InvalidUtf8 {
69        /// Position where the string began.
70        offset: usize,
71    },
72    /// Boolean byte was neither 0 nor 1 — forbidden by the XCDR spec.
73    InvalidBool {
74        /// Byte actually read.
75        value: u8,
76        /// Position of the byte.
77        offset: usize,
78    },
79    /// Char value is not a valid Unicode codepoint.
80    InvalidChar {
81        /// u32 value actually read.
82        value: u32,
83        /// Position.
84        offset: usize,
85    },
86    /// Sequence/array length exceeds the bound or the remaining bytes.
87    LengthExceeded {
88        /// How many elements the length announces.
89        announced: usize,
90        /// How many are actually still readable (best-effort).
91        remaining: usize,
92        /// Position.
93        offset: usize,
94    },
95    /// String-format violation (e.g. missing null terminator, length 0).
96    InvalidString {
97        /// Position where the string began.
98        offset: usize,
99        /// Short description (static).
100        reason: &'static str,
101    },
102    /// Unknown/invalid enum discriminator — used by policy decoders that
103    /// operate in strict mode (e.g. QoS enums).
104    InvalidEnum {
105        /// Enum name for debugging (e.g. "DurabilityKind").
106        kind: &'static str,
107        /// Discriminator value that was read.
108        value: u32,
109    },
110    /// A mutable decode read a `must_understand` member with an unknown
111    /// member ID (XTypes 1.3 §7.4.1.2.3 — the receiver MUST discard the
112    /// message in that case).
113    UnknownMustUnderstandMember {
114        /// Member ID that was not recognized.
115        member_id: u32,
116    },
117    /// A mutable decode did not find a non-optional member in the wire
118    /// data (XTypes 1.3 §7.4.1.2.3).
119    MissingNonOptionalMember {
120        /// Member ID that is missing.
121        member_id: u32,
122    },
123}
124
125impl fmt::Display for DecodeError {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            Self::UnexpectedEof { needed, offset } => {
129                write!(
130                    f,
131                    "unexpected EOF: needed {needed} more bytes at offset {offset}"
132                )
133            }
134            Self::InvalidUtf8 { offset } => write!(f, "invalid UTF-8 at offset {offset}"),
135            Self::InvalidBool { value, offset } => {
136                write!(f, "invalid bool byte 0x{value:02x} at offset {offset}")
137            }
138            Self::InvalidChar { value, offset } => {
139                write!(f, "invalid char codepoint U+{value:04X} at offset {offset}")
140            }
141            Self::LengthExceeded {
142                announced,
143                remaining,
144                offset,
145            } => write!(
146                f,
147                "length {announced} at offset {offset} exceeds remaining {remaining} bytes"
148            ),
149            Self::InvalidString { offset, reason } => {
150                write!(f, "invalid CDR string at offset {offset}: {reason}")
151            }
152            Self::InvalidEnum { kind, value } => {
153                write!(f, "invalid {kind} discriminator: {value}")
154            }
155            Self::UnknownMustUnderstandMember { member_id } => {
156                write!(f, "unknown must_understand member id: {member_id}")
157            }
158            Self::MissingNonOptionalMember { member_id } => {
159                write!(f, "missing non-optional member id: {member_id}")
160            }
161        }
162    }
163}
164
165#[cfg(feature = "std")]
166impl std::error::Error for DecodeError {}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    #[cfg(feature = "alloc")]
173    extern crate alloc;
174    #[cfg(feature = "alloc")]
175    use alloc::format;
176
177    #[test]
178    fn encode_error_display_buffer_too_small() {
179        let e = EncodeError::BufferTooSmall {
180            needed: 8,
181            available: 4,
182        };
183        let s = format!("{e}");
184        assert!(s.contains("8"));
185        assert!(s.contains("4"));
186    }
187
188    #[test]
189    fn encode_error_display_value_out_of_range() {
190        let e = EncodeError::ValueOutOfRange {
191            message: "string too long",
192        };
193        assert!(format!("{e}").contains("string too long"));
194    }
195
196    #[test]
197    fn decode_error_display_unexpected_eof() {
198        let e = DecodeError::UnexpectedEof {
199            needed: 4,
200            offset: 12,
201        };
202        let s = format!("{e}");
203        assert!(s.contains("4"));
204        assert!(s.contains("12"));
205    }
206
207    #[test]
208    fn decode_error_display_invalid_utf8() {
209        let e = DecodeError::InvalidUtf8 { offset: 5 };
210        assert!(format!("{e}").contains("5"));
211    }
212
213    #[test]
214    fn decode_error_display_invalid_bool() {
215        let e = DecodeError::InvalidBool {
216            value: 0xff,
217            offset: 0,
218        };
219        assert!(format!("{e}").contains("ff"));
220    }
221
222    #[test]
223    fn decode_error_display_invalid_char() {
224        let e = DecodeError::InvalidChar {
225            value: 0xD800,
226            offset: 0,
227        };
228        assert!(format!("{e}").contains("D800"));
229    }
230
231    #[test]
232    fn decode_error_display_length_exceeded() {
233        let e = DecodeError::LengthExceeded {
234            announced: 100,
235            remaining: 4,
236            offset: 0,
237        };
238        let s = format!("{e}");
239        assert!(s.contains("100"));
240        assert!(s.contains("4"));
241    }
242
243    #[test]
244    fn errors_are_clone_eq() {
245        let e1 = EncodeError::ValueOutOfRange { message: "x" };
246        let e2 = e1.clone();
247        assert_eq!(e1, e2);
248        let d1 = DecodeError::UnexpectedEof {
249            needed: 1,
250            offset: 0,
251        };
252        assert_eq!(d1.clone(), d1);
253    }
254}