zerodds_opcua_pubsub/error.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Encoder and decoder errors for the OPC-UA binary wire format and the
4//! UADP framing layers.
5//!
6//! Mirrors the split used in `zerodds-cdr`: encode errors are
7//! buffer/range related, decode errors additionally cover validation
8//! (truncation, invalid discriminants, bad UTF-8).
9
10use core::fmt;
11
12/// Error while encoding an OPC-UA / UADP value.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum EncodeError {
15 /// A length field exceeds the `i32::MAX` ceiling the OPC-UA binary
16 /// encoding mandates for `String`/`ByteString`/array prefixes
17 /// (OPC-UA Part 6 ยง5.2.2.4 โ length is an `Int32`).
18 LengthOverflow {
19 /// The offending element kind (e.g. `"String"`, `"Array"`).
20 what: &'static str,
21 /// The length that could not be represented.
22 len: usize,
23 },
24 /// A value cannot be represented on the wire โ e.g. a UADP header
25 /// field whose value is outside its defined range.
26 ValueOutOfRange {
27 /// Description of the violation.
28 message: &'static str,
29 },
30}
31
32impl fmt::Display for EncodeError {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 match self {
35 Self::LengthOverflow { what, len } => {
36 write!(f, "{what} length {len} exceeds OPC-UA Int32 ceiling")
37 }
38 Self::ValueOutOfRange { message } => write!(f, "value out of range: {message}"),
39 }
40 }
41}
42
43#[cfg(feature = "std")]
44impl std::error::Error for EncodeError {}
45
46/// Error while decoding an OPC-UA / UADP value.
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum DecodeError {
49 /// Input ended before the expected number of bytes were available.
50 UnexpectedEof {
51 /// How many more bytes were needed.
52 needed: usize,
53 /// How many remained in the buffer.
54 remaining: usize,
55 },
56 /// A `String` or `XmlElement` body was not valid UTF-8.
57 InvalidUtf8,
58 /// A discriminant byte did not match any defined case โ carries the
59 /// field name and the offending value for diagnosis.
60 InvalidDiscriminant {
61 /// Name of the field being decoded (e.g. `"NodeId encoding"`).
62 field: &'static str,
63 /// The value that did not match.
64 value: u32,
65 },
66 /// A length prefix was negative but the type does not permit a null
67 /// form (only `String`/`ByteString` use `-1` for null).
68 NegativeLength {
69 /// Name of the field being decoded.
70 field: &'static str,
71 },
72 /// A structural invariant of a UADP message was violated (e.g. a
73 /// payload header announced more DataSetMessages than the payload
74 /// carried).
75 MalformedMessage {
76 /// Description of the violation.
77 message: &'static str,
78 },
79}
80
81impl fmt::Display for DecodeError {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Self::UnexpectedEof { needed, remaining } => write!(
85 f,
86 "unexpected end of input: needed {needed} bytes, {remaining} remaining"
87 ),
88 Self::InvalidUtf8 => write!(f, "invalid UTF-8 in string body"),
89 Self::InvalidDiscriminant { field, value } => {
90 write!(f, "invalid discriminant for {field}: {value}")
91 }
92 Self::NegativeLength { field } => {
93 write!(f, "negative length for non-nullable field {field}")
94 }
95 Self::MalformedMessage { message } => write!(f, "malformed UADP message: {message}"),
96 }
97 }
98}
99
100#[cfg(feature = "std")]
101impl std::error::Error for DecodeError {}