dbc_rs/error/
mod.rs

1use core::{convert::From, fmt, num::ParseIntError};
2
3pub mod lang;
4pub(crate) mod messages;
5
6/// Error type for DBC parsing and validation operations.
7///
8/// This enum represents all possible errors that can occur when working with DBC files.
9/// Most variants require the `alloc` feature to be enabled.
10#[derive(Debug, PartialEq)]
11pub enum Error {
12    /// Invalid data error (e.g., parse failures, invalid formats).
13    #[cfg(feature = "alloc")]
14    InvalidData(String),
15
16    /// Signal-related error (e.g., invalid signal definition).
17    #[cfg(feature = "alloc")]
18    Signal(String),
19
20    /// Message-related error (e.g., invalid message definition).
21    #[cfg(feature = "alloc")]
22    Message(String),
23
24    /// DBC file-level error (e.g., missing required sections).
25    #[cfg(feature = "alloc")]
26    Dbc(String),
27
28    /// Version parsing error.
29    #[cfg(feature = "alloc")]
30    Version(String),
31
32    /// Node-related error (e.g., duplicate node names).
33    #[cfg(feature = "alloc")]
34    Nodes(String),
35
36    /// Low-level parse error (available in `no_std` builds).
37    ParseError(ParseError),
38}
39
40/// Low-level parsing error that can occur during DBC file parsing.
41///
42/// This error type is available in both `std` and `no_std` builds.
43#[derive(Debug, PartialEq, Clone, Copy)]
44pub enum ParseError {
45    /// Unexpected end of input encountered.
46    UnexpectedEof,
47
48    /// Expected a specific token or value.
49    Expected(&'static str),
50
51    /// Invalid character encountered.
52    InvalidChar(char),
53
54    /// String length exceeds the maximum allowed length.
55    MaxStrLength(u16),
56
57    /// Version-related parse error.
58    Version(&'static str),
59}
60
61impl fmt::Display for ParseError {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            ParseError::UnexpectedEof => write!(f, "Unexpected end of input"),
65            ParseError::Expected(msg) => write!(f, "Expected {}", msg),
66            ParseError::InvalidChar(c) => write!(f, "Invalid character: {}", c),
67            ParseError::MaxStrLength(max) => write!(f, "String length exceeds maximum: {}", max),
68            ParseError::Version(msg) => write!(f, "Version error: {}", msg),
69        }
70    }
71}
72
73/// Result type alias for operations that can return an `Error`.
74pub type Result<T> = core::result::Result<T, Error>;
75
76/// Result type alias for low-level parsing operations that can return a `ParseError`.
77pub type ParseResult<T> = core::result::Result<T, ParseError>;
78
79#[cfg(feature = "alloc")]
80impl fmt::Display for Error {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        match self {
83            Error::InvalidData(msg) => {
84                // Display the message with category prefix for better readability
85                write!(f, "{}", messages::format_invalid_data(msg))
86            }
87            Error::Signal(msg) => {
88                // Display the message with category prefix for better readability
89                write!(f, "{}", messages::format_signal_error(msg))
90            }
91            Error::Message(msg) => {
92                write!(f, "{}", messages::format_message_error(msg))
93            }
94            Error::Dbc(msg) => {
95                write!(f, "{}", messages::format_dbc_error(msg))
96            }
97            Error::Version(msg) => {
98                write!(f, "{}", messages::format_version_error(msg))
99            }
100            Error::Nodes(msg) => {
101                write!(f, "{}", messages::format_nodes_error(msg))
102            }
103            Error::ParseError(msg) => {
104                write!(f, "Parse Error: {}", msg)
105            }
106        }
107    }
108}
109
110#[cfg(feature = "alloc")]
111impl From<ParseIntError> for Error {
112    fn from(err: ParseIntError) -> Self {
113        Error::InvalidData(messages::parse_number_failed(err))
114    }
115}
116
117#[cfg(not(feature = "alloc"))]
118impl From<ParseIntError> for Error {
119    fn from(_err: ParseIntError) -> Self {
120        // In no_std, we can only return ParseError
121        // ParseIntError conversion is not fully supported in no_std
122        Error::ParseError(ParseError::Expected("Invalid number format"))
123    }
124}
125
126impl From<ParseError> for Error {
127    fn from(err: ParseError) -> Self {
128        Error::ParseError(err)
129    }
130}
131
132#[cfg(feature = "std")]
133impl std::error::Error for Error {
134    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
135        None
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    #![allow(clippy::float_cmp)]
142    use super::Error;
143    use crate::error::lang;
144    use alloc::string::ToString;
145
146    #[test]
147    fn test_from_parse_int_error() {
148        // Create a ParseIntError by trying to parse an invalid string
149        let parse_error = "invalid".parse::<u32>().unwrap_err();
150        let error: Error = parse_error.into();
151
152        match error {
153            Error::InvalidData(msg) => {
154                assert!(msg.contains(lang::FORMAT_PARSE_NUMBER_FAILED.split(':').next().unwrap()));
155            }
156            _ => panic!("Expected InvalidData error"),
157        }
158    }
159
160    #[test]
161    fn test_display_invalid_data() {
162        let error = Error::InvalidData("Test error message".to_string());
163        let display = error.to_string();
164        assert!(display.starts_with(lang::INVALID_DATA_CATEGORY));
165        assert!(display.contains("Test error message"));
166    }
167
168    #[test]
169    fn test_display_signal_error() {
170        let error = Error::Signal(lang::SIGNAL_NAME_EMPTY.to_string());
171        let display = error.to_string();
172        assert!(display.starts_with(lang::SIGNAL_ERROR_CATEGORY));
173        assert!(display.contains(lang::SIGNAL_NAME_EMPTY));
174    }
175
176    #[test]
177    fn test_display_formatting() {
178        // Test that Display properly formats complex error messages
179        let error = Error::InvalidData(
180            "Duplicate message ID: 256 (messages 'EngineData' and 'BrakeData')".to_string(),
181        );
182        let display = error.to_string();
183        assert!(display.starts_with(lang::INVALID_DATA_CATEGORY));
184        assert!(display.contains("256"));
185        assert!(display.contains("EngineData"));
186        assert!(display.contains("BrakeData"));
187    }
188
189    #[test]
190    fn test_display_parse_error() {
191        let parse_error = "not_a_number".parse::<u32>().unwrap_err();
192        let error: Error = parse_error.into();
193        let display = error.to_string();
194
195        assert!(display.starts_with(lang::INVALID_DATA_CATEGORY));
196        assert!(display.contains(lang::FORMAT_PARSE_NUMBER_FAILED.split(':').next().unwrap()));
197    }
198
199    #[cfg(feature = "std")]
200    #[test]
201    fn test_std_error_trait() {
202        use std::error::Error as StdError;
203
204        let error = Error::InvalidData("Test".to_string());
205        // Verify it implements std::error::Error
206        let _: &dyn StdError = &error;
207
208        // Verify source() returns None (no underlying error)
209        assert!(error.source().is_none());
210    }
211}