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}
50
51impl Display for SbeEncodeError {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Self::StringTooLong { field, len, max } => {
55                write!(
56                    f,
57                    "String field `{field}` length {len} exceeds maximum {max}"
58                )
59            }
60            Self::GroupSizeTooLarge { group, count, max } => {
61                write!(f, "Group `{group}` size {count} exceeds maximum {max}")
62            }
63            Self::NumericOverflow { field } => {
64                write!(f, "Numeric value overflows encoded field {field}")
65            }
66        }
67    }
68}
69
70impl Error for SbeEncodeError {}
71
72/// SBE decode error.
73#[derive(Debug, Clone, PartialEq, Eq)]
74pub enum SbeDecodeError {
75    /// Buffer too short to decode expected data.
76    BufferTooShort {
77        /// Expected minimum bytes.
78        expected: usize,
79        /// Actual bytes available.
80        actual: usize,
81    },
82    /// Schema ID mismatch.
83    SchemaMismatch {
84        /// Expected schema ID.
85        expected: u16,
86        /// Actual schema ID.
87        actual: u16,
88    },
89    /// Schema version mismatch.
90    VersionMismatch {
91        /// Expected schema version.
92        expected: u16,
93        /// Actual schema version.
94        actual: u16,
95    },
96    /// Unknown template ID.
97    UnknownTemplateId(u16),
98    /// Group count exceeds safety limit.
99    GroupSizeTooLarge {
100        /// Actual count.
101        count: u32,
102        /// Maximum allowed.
103        max: u32,
104    },
105    /// Invalid block length.
106    InvalidBlockLength {
107        /// Expected block length.
108        expected: u16,
109        /// Actual block length.
110        actual: u16,
111    },
112    /// Invalid UTF-8 in string field.
113    InvalidUtf8,
114    /// Invalid enum discriminant.
115    InvalidEnumValue {
116        /// The enum type name.
117        type_name: &'static str,
118        /// The invalid encoded value.
119        value: u16,
120    },
121    /// Numeric value cannot fit the target type.
122    NumericOverflow {
123        /// The target type name.
124        type_name: &'static str,
125    },
126    /// Encoded field value is invalid.
127    InvalidValue {
128        /// The field name or description.
129        field: &'static str,
130    },
131}
132
133impl Display for SbeDecodeError {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        match self {
136            Self::BufferTooShort { expected, actual } => {
137                write!(
138                    f,
139                    "Buffer too short: expected {expected} bytes, was {actual}"
140                )
141            }
142            Self::SchemaMismatch { expected, actual } => {
143                write!(f, "Schema ID mismatch: expected {expected}, was {actual}")
144            }
145            Self::VersionMismatch { expected, actual } => {
146                write!(
147                    f,
148                    "Schema version mismatch: expected {expected}, was {actual}"
149                )
150            }
151            Self::UnknownTemplateId(id) => write!(f, "Unknown template ID: {id}"),
152            Self::GroupSizeTooLarge { count, max } => {
153                write!(f, "Group size {count} exceeds maximum {max}")
154            }
155            Self::InvalidBlockLength { expected, actual } => {
156                write!(f, "Invalid block length: expected {expected}, was {actual}")
157            }
158            Self::InvalidUtf8 => write!(f, "Invalid UTF-8 in string field"),
159            Self::InvalidEnumValue { type_name, value } => {
160                write!(f, "Invalid enum value {value} for {type_name}")
161            }
162            Self::NumericOverflow { type_name } => {
163                write!(f, "Numeric value overflows target type {type_name}")
164            }
165            Self::InvalidValue { field } => write!(f, "Invalid value for {field}"),
166        }
167    }
168}
169
170impl Error for SbeDecodeError {}
171
172#[cfg(test)]
173mod tests {
174    use rstest::rstest;
175
176    use super::*;
177
178    #[rstest]
179    fn test_string_too_long_display() {
180        let err = SbeEncodeError::StringTooLong {
181            field: "symbol",
182            len: 300,
183            max: 65535,
184        };
185        assert_eq!(
186            err.to_string(),
187            "String field `symbol` length 300 exceeds maximum 65535"
188        );
189    }
190
191    #[rstest]
192    fn test_numeric_overflow_display() {
193        let err = SbeEncodeError::NumericOverflow {
194            field: "BarSpecification.step",
195        };
196        assert_eq!(
197            err.to_string(),
198            "Numeric value overflows encoded field BarSpecification.step"
199        );
200    }
201
202    #[rstest]
203    fn test_buffer_too_short_display() {
204        let err = SbeDecodeError::BufferTooShort {
205            expected: 100,
206            actual: 50,
207        };
208        assert_eq!(
209            err.to_string(),
210            "Buffer too short: expected 100 bytes, was 50"
211        );
212    }
213
214    #[rstest]
215    fn test_schema_mismatch_display() {
216        let err = SbeDecodeError::SchemaMismatch {
217            expected: 3,
218            actual: 1,
219        };
220        assert_eq!(err.to_string(), "Schema ID mismatch: expected 3, was 1");
221    }
222
223    #[rstest]
224    fn test_group_size_too_large_display() {
225        let err = SbeDecodeError::GroupSizeTooLarge {
226            count: 50000,
227            max: 10000,
228        };
229        assert_eq!(err.to_string(), "Group size 50000 exceeds maximum 10000");
230    }
231
232    #[rstest]
233    fn test_error_equality() {
234        let err1 = SbeDecodeError::InvalidUtf8;
235        let err2 = SbeDecodeError::InvalidUtf8;
236        assert_eq!(err1, err2);
237    }
238
239    #[rstest]
240    fn test_invalid_enum_value_display() {
241        let err = SbeDecodeError::InvalidEnumValue {
242            type_name: "OrderSide",
243            value: 99,
244        };
245        assert_eq!(err.to_string(), "Invalid enum value 99 for OrderSide");
246    }
247}