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    /// Fixed-length group does not contain the required number of entries.
27    InvalidGroupSize {
28        /// Group name.
29        group: &'static str,
30        /// Actual entry count.
31        count: usize,
32        /// Required entry count.
33        expected: usize,
34    },
35    /// String field exceeds the supported encoded length.
36    StringTooLong {
37        /// The field name.
38        field: &'static str,
39        /// Actual string byte length.
40        len: usize,
41        /// Maximum encodable byte length.
42        max: usize,
43    },
44    /// Group count exceeds safety limit.
45    GroupSizeTooLarge {
46        /// The group name.
47        group: &'static str,
48        /// Actual count.
49        count: usize,
50        /// Maximum allowed.
51        max: u32,
52    },
53    /// Numeric value cannot fit the target encoded type.
54    NumericOverflow {
55        /// The field name or description.
56        field: &'static str,
57    },
58    /// Value collides with a sentinel reserved by the wire encoding.
59    ReservedValue {
60        /// The field name or description.
61        field: &'static str,
62    },
63}
64
65impl Display for SbeEncodeError {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        match self {
68            Self::InvalidGroupSize {
69                group,
70                count,
71                expected,
72            } => {
73                write!(
74                    f,
75                    "Group `{group}` requires {expected} entries, found {count}"
76                )
77            }
78
79            Self::StringTooLong { field, len, max } => {
80                write!(
81                    f,
82                    "String field `{field}` length {len} exceeds maximum {max}"
83                )
84            }
85            Self::GroupSizeTooLarge { group, count, max } => {
86                write!(f, "Group `{group}` size {count} exceeds maximum {max}")
87            }
88            Self::NumericOverflow { field } => {
89                write!(f, "Numeric value overflows encoded field {field}")
90            }
91            Self::ReservedValue { field } => {
92                write!(f, "Value for {field} is reserved by the wire encoding")
93            }
94        }
95    }
96}
97
98impl Error for SbeEncodeError {}
99
100/// SBE decode error.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum SbeDecodeError {
103    /// Buffer too short to decode expected data.
104    BufferTooShort {
105        /// Expected minimum bytes.
106        expected: usize,
107        /// Actual bytes available.
108        actual: usize,
109    },
110    /// Schema ID mismatch.
111    SchemaMismatch {
112        /// Expected schema ID.
113        expected: u16,
114        /// Actual schema ID.
115        actual: u16,
116    },
117    /// Schema version mismatch.
118    VersionMismatch {
119        /// Expected schema version.
120        expected: u16,
121        /// Actual schema version.
122        actual: u16,
123    },
124    /// Unknown template ID.
125    UnknownTemplateId(u16),
126    /// Group count exceeds safety limit.
127    GroupSizeTooLarge {
128        /// Actual count.
129        count: u32,
130        /// Maximum allowed.
131        max: u32,
132    },
133    /// Invalid block length.
134    InvalidBlockLength {
135        /// Expected block length.
136        expected: u16,
137        /// Actual block length.
138        actual: u16,
139    },
140    /// Invalid UTF-8 in string field.
141    InvalidUtf8,
142    /// Invalid enum discriminant.
143    InvalidEnumValue {
144        /// The enum type name.
145        type_name: &'static str,
146        /// The invalid encoded value.
147        value: u16,
148    },
149    /// Numeric value cannot fit the target type.
150    NumericOverflow {
151        /// The target type name.
152        type_name: &'static str,
153    },
154    /// Encoded field value is invalid.
155    InvalidValue {
156        /// The field name or description.
157        field: &'static str,
158    },
159}
160
161impl Display for SbeDecodeError {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        match self {
164            Self::BufferTooShort { expected, actual } => {
165                write!(
166                    f,
167                    "Buffer too short: expected {expected} bytes, was {actual}"
168                )
169            }
170            Self::SchemaMismatch { expected, actual } => {
171                write!(f, "Schema ID mismatch: expected {expected}, was {actual}")
172            }
173            Self::VersionMismatch { expected, actual } => {
174                write!(
175                    f,
176                    "Schema version mismatch: expected {expected}, was {actual}"
177                )
178            }
179            Self::UnknownTemplateId(id) => write!(f, "Unknown template ID: {id}"),
180            Self::GroupSizeTooLarge { count, max } => {
181                write!(f, "Group size {count} exceeds maximum {max}")
182            }
183            Self::InvalidBlockLength { expected, actual } => {
184                write!(f, "Invalid block length: expected {expected}, was {actual}")
185            }
186            Self::InvalidUtf8 => write!(f, "Invalid UTF-8 in string field"),
187            Self::InvalidEnumValue { type_name, value } => {
188                write!(f, "Invalid enum value {value} for {type_name}")
189            }
190            Self::NumericOverflow { type_name } => {
191                write!(f, "Numeric value overflows target type {type_name}")
192            }
193            Self::InvalidValue { field } => write!(f, "Invalid value for {field}"),
194        }
195    }
196}
197
198impl Error for SbeDecodeError {}
199
200#[cfg(test)]
201mod tests {
202    use rstest::rstest;
203
204    use super::*;
205
206    #[rstest]
207    fn test_string_too_long_display() {
208        let err = SbeEncodeError::StringTooLong {
209            field: "symbol",
210            len: 300,
211            max: 65535,
212        };
213        assert_eq!(
214            err.to_string(),
215            "String field `symbol` length 300 exceeds maximum 65535"
216        );
217    }
218
219    #[rstest]
220    fn test_numeric_overflow_display() {
221        let err = SbeEncodeError::NumericOverflow {
222            field: "BarSpecification.step",
223        };
224        assert_eq!(
225            err.to_string(),
226            "Numeric value overflows encoded field BarSpecification.step"
227        );
228    }
229
230    #[rstest]
231    fn test_reserved_value_display() {
232        let err = SbeEncodeError::ReservedValue {
233            field: "FundingRateUpdate.interval",
234        };
235        assert_eq!(
236            err.to_string(),
237            "Value for FundingRateUpdate.interval is reserved by the wire encoding"
238        );
239    }
240
241    #[rstest]
242    fn test_buffer_too_short_display() {
243        let err = SbeDecodeError::BufferTooShort {
244            expected: 100,
245            actual: 50,
246        };
247        assert_eq!(
248            err.to_string(),
249            "Buffer too short: expected 100 bytes, was 50"
250        );
251    }
252
253    #[rstest]
254    fn test_schema_mismatch_display() {
255        let err = SbeDecodeError::SchemaMismatch {
256            expected: 3,
257            actual: 1,
258        };
259        assert_eq!(err.to_string(), "Schema ID mismatch: expected 3, was 1");
260    }
261
262    #[rstest]
263    fn test_group_size_too_large_display() {
264        let err = SbeDecodeError::GroupSizeTooLarge {
265            count: 50000,
266            max: 10000,
267        };
268        assert_eq!(err.to_string(), "Group size 50000 exceeds maximum 10000");
269    }
270
271    #[rstest]
272    fn test_error_equality() {
273        let err1 = SbeDecodeError::InvalidUtf8;
274        let err2 = SbeDecodeError::InvalidUtf8;
275        assert_eq!(err1, err2);
276    }
277
278    #[rstest]
279    fn test_invalid_enum_value_display() {
280        let err = SbeDecodeError::InvalidEnumValue {
281            type_name: "OrderSide",
282            value: 99,
283        };
284        assert_eq!(err.to_string(), "Invalid enum value 99 for OrderSide");
285    }
286}