dbc_rs/error/
mod.rs

1use core::{convert::From, fmt};
2
3// ParseIntError is used in From<ParseIntError> implementations
4// The warning appears when default features (std/alloc) are enabled with kernel
5// because the kernel-specific implementation isn't compiled in that case
6#[cfg(any(feature = "alloc", feature = "kernel"))]
7#[allow(unused_imports)]
8use core::num::ParseIntError;
9
10// Type alias for String based on feature flags
11#[cfg(all(feature = "kernel", not(feature = "alloc")))]
12use crate::kernel::alloc::string::String as ErrorString;
13#[cfg(all(feature = "alloc", not(feature = "kernel")))]
14use alloc::string::String as ErrorString;
15
16// Helper function to convert &str to ErrorString
17#[cfg(all(feature = "kernel", not(feature = "alloc")))]
18pub(crate) fn str_to_error_string(s: &str) -> ErrorString {
19    ErrorString::from_str(s)
20}
21
22#[cfg(all(feature = "alloc", not(feature = "kernel")))]
23pub(crate) fn str_to_error_string(s: &str) -> ErrorString {
24    ErrorString::from(s)
25}
26
27pub mod lang;
28pub(crate) mod messages;
29
30/// Error type for DBC parsing and validation operations.
31///
32/// This enum represents all possible errors that can occur when working with DBC files.
33/// Most variants require the `alloc` feature to be enabled.
34#[derive(Debug, PartialEq)]
35pub enum Error {
36    /// Invalid data error (e.g., parse failures, invalid formats).
37    #[cfg(any(feature = "alloc", feature = "kernel"))]
38    InvalidData(ErrorString),
39
40    /// Signal-related error (e.g., invalid signal definition).
41    #[cfg(any(feature = "alloc", feature = "kernel"))]
42    Signal(ErrorString),
43
44    /// Message-related error (e.g., invalid message definition).
45    #[cfg(any(feature = "alloc", feature = "kernel"))]
46    Message(ErrorString),
47
48    /// DBC file-level error (e.g., missing required sections).
49    #[cfg(any(feature = "alloc", feature = "kernel"))]
50    Dbc(ErrorString),
51
52    /// Version parsing error.
53    #[cfg(any(feature = "alloc", feature = "kernel"))]
54    Version(ErrorString),
55
56    /// Node-related error (e.g., duplicate node names).
57    #[cfg(any(feature = "alloc", feature = "kernel"))]
58    Nodes(ErrorString),
59
60    /// Low-level parse error (available in `no_std` builds).
61    ParseError(ParseError),
62}
63
64/// Low-level parsing error that can occur during DBC file parsing.
65///
66/// This error type is available in both `std` and `no_std` builds.
67#[derive(Debug, PartialEq, Clone, Copy)]
68pub enum ParseError {
69    /// Unexpected end of input encountered.
70    UnexpectedEof,
71
72    /// Expected a specific token or value.
73    Expected(&'static str),
74
75    /// Invalid character encountered.
76    InvalidChar(char),
77
78    /// String length exceeds the maximum allowed length.
79    MaxStrLength(u16),
80
81    /// Version-related parse error.
82    Version(&'static str),
83}
84
85impl fmt::Display for ParseError {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            ParseError::UnexpectedEof => write!(f, "Unexpected end of input"),
89            ParseError::Expected(msg) => write!(f, "Expected {}", msg),
90            ParseError::InvalidChar(c) => write!(f, "Invalid character: {}", c),
91            ParseError::MaxStrLength(max) => write!(f, "String length exceeds maximum: {}", max),
92            ParseError::Version(msg) => write!(f, "Version error: {}", msg),
93        }
94    }
95}
96
97/// Result type alias for operations that can return an `Error`.
98pub type Result<T> = core::result::Result<T, Error>;
99
100/// Result type alias for low-level parsing operations that can return a `ParseError`.
101pub type ParseResult<T> = core::result::Result<T, ParseError>;
102
103// ============================================================================
104// Helper function to convert String to ParseError::Version(&'static str)
105// ============================================================================
106
107/// Converts a String error message to a `ParseError::Version` with a static lifetime.
108///
109/// This function takes ownership of the String, boxes it, and leaks it to create
110/// a `&'static str` that can be used in `ParseError::Version`.
111///
112/// # Safety
113///
114/// This function leaks memory. The leaked memory will live for the duration of
115/// the program. This is acceptable for error messages that are typically displayed
116/// once and then the program exits or handles the error.
117///
118/// # Examples
119///
120/// ```rust,ignore
121/// use crate::error::{messages, version_error_from_string};
122///
123/// let msg = messages::message_id_out_of_range(0x20000000);
124/// return Err(version_error_from_string(msg));
125/// ```
126#[cfg(feature = "alloc")]
127pub(crate) fn version_error_from_string(msg: ErrorString) -> ParseError {
128    use alloc::boxed::Box;
129    ParseError::Version(Box::leak(msg.into_boxed_str()))
130}
131
132// Separate Display implementations for alloc and kernel (Strategy 4)
133#[cfg(all(feature = "alloc", not(feature = "kernel")))]
134impl fmt::Display for Error {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match self {
137            Error::InvalidData(msg) => write!(f, "{}", messages::format_invalid_data(msg)),
138            Error::Signal(msg) => write!(f, "{}", messages::format_signal_error(msg)),
139            Error::Message(msg) => write!(f, "{}", messages::format_message_error(msg)),
140            Error::Dbc(msg) => write!(f, "{}", messages::format_dbc_error(msg)),
141            Error::Version(msg) => write!(f, "{}", messages::format_version_error(msg)),
142            Error::Nodes(msg) => write!(f, "{}", messages::format_nodes_error(msg)),
143            Error::ParseError(msg) => write!(f, "Parse Error: {}", msg),
144        }
145    }
146}
147
148#[cfg(all(feature = "kernel", not(feature = "alloc")))]
149impl fmt::Display for Error {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match self {
152            Error::InvalidData(msg) => {
153                let formatted = messages::format_invalid_data(msg);
154                write!(f, "{}", formatted)
155            }
156            Error::Signal(msg) => {
157                let formatted = messages::format_signal_error(msg);
158                write!(f, "{}", formatted)
159            }
160            Error::Message(msg) => {
161                let formatted = messages::format_message_error(msg);
162                write!(f, "{}", formatted)
163            }
164            Error::Dbc(msg) => {
165                let formatted = messages::format_dbc_error(msg);
166                write!(f, "{}", formatted)
167            }
168            Error::Version(msg) => {
169                let formatted = messages::format_version_error(msg);
170                write!(f, "{}", formatted)
171            }
172            Error::Nodes(msg) => {
173                let formatted = messages::format_nodes_error(msg);
174                write!(f, "{}", formatted)
175            }
176            Error::ParseError(msg) => write!(f, "Parse Error: {}", msg),
177        }
178    }
179}
180
181// Separate From<ParseIntError> implementations for alloc vs kernel
182#[cfg(all(feature = "alloc", not(feature = "kernel")))]
183impl From<ParseIntError> for Error {
184    fn from(err: ParseIntError) -> Self {
185        Error::InvalidData(messages::parse_number_failed(err))
186    }
187}
188
189#[cfg(all(feature = "kernel", not(feature = "alloc")))]
190impl From<ParseIntError> for Error {
191    fn from(err: ParseIntError) -> Self {
192        // In kernel mode, parse_number_failed returns String (infallible)
193        Error::InvalidData(messages::parse_number_failed(err))
194    }
195}
196
197#[cfg(not(any(feature = "alloc", feature = "kernel")))]
198impl From<core::num::ParseIntError> for Error {
199    fn from(_err: core::num::ParseIntError) -> Self {
200        // In no_std, we can only return ParseError
201        // ParseIntError conversion is not fully supported in no_std
202        Error::ParseError(ParseError::Expected("Invalid number format"))
203    }
204}
205
206impl From<ParseError> for Error {
207    fn from(err: ParseError) -> Self {
208        Error::ParseError(err)
209    }
210}
211
212// std::error::Error is only available with std feature (which requires alloc)
213// Display is already implemented for alloc feature, so this should work
214#[cfg(all(feature = "std", feature = "alloc", not(feature = "kernel")))]
215impl std::error::Error for Error {
216    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
217        None
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    #![allow(clippy::float_cmp)]
224
225    // Tests that require alloc or kernel feature (for Display/ToString)
226    #[cfg(any(feature = "alloc", feature = "kernel"))]
227    mod tests_with_alloc {
228        use crate::error::{Error, lang};
229
230        #[test]
231        fn test_from_parse_int_error() {
232            // Create a ParseIntError by trying to parse an invalid string
233            let parse_error = "invalid".parse::<u32>().unwrap_err();
234            let error: Error = parse_error.into();
235
236            match error {
237                Error::InvalidData(msg) => {
238                    assert!(
239                        msg.contains(lang::FORMAT_PARSE_NUMBER_FAILED.split(':').next().unwrap())
240                    );
241                }
242                _ => panic!("Expected InvalidData error"),
243            }
244        }
245
246        #[test]
247        fn test_display_invalid_data() {
248            use crate::compat::{display_to_string, str_to_string};
249            let error = Error::InvalidData(str_to_string("Test error message"));
250            let display = display_to_string(error);
251            assert!(display.starts_with(lang::INVALID_DATA_CATEGORY));
252            assert!(display.contains("Test error message"));
253        }
254
255        #[test]
256        fn test_display_signal_error() {
257            use crate::compat::{display_to_string, str_to_string};
258            let error = Error::Signal(str_to_string("Test signal error"));
259            let display = display_to_string(error);
260            assert!(display.starts_with(lang::SIGNAL_ERROR_CATEGORY));
261            assert!(display.contains("Test signal error"));
262        }
263
264        #[test]
265        fn test_display_formatting() {
266            use crate::compat::{display_to_string, str_to_string};
267            // Test that Display properly formats complex error messages
268            let error = Error::InvalidData(str_to_string(
269                "Duplicate message ID: 256 (messages 'EngineData' and 'BrakeData')",
270            ));
271            let display = display_to_string(error);
272            assert!(display.starts_with(lang::INVALID_DATA_CATEGORY));
273            assert!(display.contains("256"));
274            assert!(display.contains("EngineData"));
275            assert!(display.contains("BrakeData"));
276        }
277
278        #[test]
279        fn test_display_parse_error() {
280            use crate::compat::display_to_string;
281            let parse_error = "not_a_number".parse::<u32>().unwrap_err();
282            let error: Error = parse_error.into();
283            let display = display_to_string(error);
284
285            assert!(display.starts_with(lang::INVALID_DATA_CATEGORY));
286            assert!(display.contains(lang::FORMAT_PARSE_NUMBER_FAILED.split(':').next().unwrap()));
287        }
288    }
289
290    // Tests that require std feature (for std::error::Error trait)
291    #[cfg(feature = "std")]
292    mod tests_std {
293        use crate::error::Error;
294        use std::error::Error as StdError;
295
296        #[test]
297        fn test_std_error_trait() {
298            use crate::compat::str_to_string;
299            let error = Error::InvalidData(str_to_string("Test"));
300            // Verify it implements std::error::Error
301            let _: &dyn StdError = &error;
302
303            // Verify source() returns None (no underlying error)
304            assert!(error.source().is_none());
305        }
306    }
307}