Skip to main content

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 {}