Skip to main content

nautilus_serialization/sbe/
error.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Generic SBE error types.
17
18use std::{error::Error, fmt::Display};
19
20/// Maximum allowed group size to prevent DoS from malformed data.
21pub const MAX_GROUP_SIZE: u32 = 10_000;
22
23/// SBE encode error.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum SbeEncodeError {
26    /// String field exceeds the supported encoded length.
27    StringTooLong {
28        /// The field name.
29        field: &'static str,
30        /// Actual string byte length.
31        len: usize,
32        /// Maximum encodable byte length.
33        max: usize,
34    },
35    /// Group count exceeds safety limit.
36    GroupSizeTooLarge {
37        /// The group name.
38        group: &'static str,
39        /// Actual count.
40        count: usize,
41        /// Maximum allowed.
42        max: u32,
43    },
44    /// Numeric value cannot fit the target encoded type.
45    NumericOverflow {
46        /// The field name or description.
47        field: &'static str,
48    },
49    /// Value collides with a sentinel reserved by the wire encoding.
50    ReservedValue {
51        /// The field name or description.
52        field: &'static str,
53    },
54}
55
56impl Display for SbeEncodeError {
57    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58        match self {
59            Self::StringTooLong { field, len, max } => {
60                write!(
61                    f,
62                    "String field `{field}` length {len} exceeds maximum {max}"
63                )
64            }
65            Self::GroupSizeTooLarge { group, count, max } => {
66                write!(f, "Group `{group}` size {count} exceeds maximum {max}")
67            }
68            Self::NumericOverflow { field } => {
69                write!(f, "Numeric value overflows encoded field {field}")
70            }
71            Self::ReservedValue { field } => {
72                write!(f, "Value for {field} is reserved by the wire encoding")
73            }
74        }
75    }
76}
77
78impl Error for SbeEncodeError {}
79
80/// SBE decode error.
81#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum SbeDecodeError {
83    /// Buffer too short to decode expected data.
84    BufferTooShort {
85        /// Expected minimum bytes.
86        expected: usize,
87        /// Actual bytes available.
88        actual: usize,
89    },
90    /// Schema ID mismatch.
91    SchemaMismatch {
92        /// Expected schema ID.
93        expected: u16,
94        /// Actual schema ID.
95        actual: u16,
96    },
97    /// Schema version mismatch.
98    VersionMismatch {
99        /// Expected schema version.
100        expected: u16,
101        /// Actual schema version.
102        actual: u16,
103    },
104    /// Unknown template ID.
105    UnknownTemplateId(u16),
106    /// Group count exceeds safety limit.
107    GroupSizeTooLarge {
108        /// Actual count.
109        count: u32,
110        /// Maximum allowed.
111        max: u32,
112    },
113    /// Invalid block length.
114    InvalidBlockLength {
115        /// Expected block length.
116        expected: u16,
117        /// Actual block length.
118        actual: u16,
119    },
120    /// Invalid UTF-8 in string field.
121    InvalidUtf8,
122    /// Invalid enum discriminant.
123    InvalidEnumValue {
124        /// The enum type name.
125        type_name: &'static str,
126        /// The invalid encoded value.
127        value: u16,
128    },
129    /// Numeric value cannot fit the target type.
130    NumericOverflow {
131        /// The target type name.
132        type_name: &'static str,
133    },
134    /// Encoded field value is invalid.
135    InvalidValue {
136        /// The field name or description.
137        field: &'static str,
138    },
139}
140
141impl Display for SbeDecodeError {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        match self {
144            Self::BufferTooShort { expected, actual } => {
145                write!(
146                    f,
147                    "Buffer too short: expected {expected} bytes, was {actual}"
148                )
149            }
150            Self::SchemaMismatch { expected, actual } => {
151                write!(f, "Schema ID mismatch: expected {expected}, was {actual}")
152            }
153            Self::VersionMismatch { expected, actual } => {
154                write!(
155                    f,
156                    "Schema version mismatch: expected {expected}, was {actual}"
157                )
158            }
159            Self::UnknownTemplateId(id) => write!(f, "Unknown template ID: {id}"),
160            Self::GroupSizeTooLarge { count, max } => {
161                write!(f, "Group size {count} exceeds maximum {max}")
162            }
163            Self::InvalidBlockLength { expected, actual } => {
164                write!(f, "Invalid block length: expected {expected}, was {actual}")
165            }
166            Self::InvalidUtf8 => write!(f, "Invalid UTF-8 in string field"),
167            Self::InvalidEnumValue { type_name, value } => {
168                write!(f, "Invalid enum value {value} for {type_name}")
169            }
170            Self::NumericOverflow { type_name } => {
171                write!(f, "Numeric value overflows target type {type_name}")
172            }
173            Self::InvalidValue { field } => write!(f, "Invalid value for {field}"),
174        }
175    }
176}
177
178impl Error for SbeDecodeError {}
179
180#[cfg(test)]
181mod tests {
182    use rstest::rstest;
183
184    use super::*;
185
186    #[rstest]
187    fn test_string_too_long_display() {
188        let err = SbeEncodeError::StringTooLong {
189            field: "symbol",
190            len: 300,
191            max: 65535,
192        };
193        assert_eq!(
194            err.to_string(),
195            "String field `symbol` length 300 exceeds maximum 65535"
196        );
197    }
198
199    #[rstest]
200    fn test_numeric_overflow_display() {
201        let err = SbeEncodeError::NumericOverflow {
202            field: "BarSpecification.step",
203        };
204        assert_eq!(
205            err.to_string(),
206            "Numeric value overflows encoded field BarSpecification.step"
207        );
208    }
209
210    #[rstest]
211    fn test_reserved_value_display() {
212        let err = SbeEncodeError::ReservedValue {
213            field: "FundingRateUpdate.interval",
214        };
215        assert_eq!(
216            err.to_string(),
217            "Value for FundingRateUpdate.interval is reserved by the wire encoding"
218        );
219    }
220
221    #[rstest]
222    fn test_buffer_too_short_display() {
223        let err = SbeDecodeError::BufferTooShort {
224            expected: 100,
225            actual: 50,
226        };
227        assert_eq!(
228            err.to_string(),
229            "Buffer too short: expected 100 bytes, was 50"
230        );
231    }
232
233    #[rstest]
234    fn test_schema_mismatch_display() {
235        let err = SbeDecodeError::SchemaMismatch {
236            expected: 3,
237            actual: 1,
238        };
239        assert_eq!(err.to_string(), "Schema ID mismatch: expected 3, was 1");
240    }
241
242    #[rstest]
243    fn test_group_size_too_large_display() {
244        let err = SbeDecodeError::GroupSizeTooLarge {
245            count: 50000,
246            max: 10000,
247        };
248        assert_eq!(err.to_string(), "Group size 50000 exceeds maximum 10000");
249    }
250
251    #[rstest]
252    fn test_error_equality() {
253        let err1 = SbeDecodeError::InvalidUtf8;
254        let err2 = SbeDecodeError::InvalidUtf8;
255        assert_eq!(err1, err2);
256    }
257
258    #[rstest]
259    fn test_invalid_enum_value_display() {
260        let err = SbeDecodeError::InvalidEnumValue {
261            type_name: "OrderSide",
262            value: 99,
263        };
264        assert_eq!(err.to_string(), "Invalid enum value 99 for OrderSide");
265    }
266}