dbc_rs/error/
impls.rs

1use core::fmt;
2
3use super::Error;
4
5impl Error {
6    // ============================================================================
7    // Constructor helpers (for creating errors without line info)
8    // ============================================================================
9
10    /// Create an UnexpectedEof error without line info.
11    #[inline]
12    pub fn unexpected_eof() -> Self {
13        Error::UnexpectedEof { line: None }
14    }
15
16    /// Create an UnexpectedEof error with line info.
17    #[inline]
18    pub fn unexpected_eof_at(line: usize) -> Self {
19        Error::UnexpectedEof { line: Some(line) }
20    }
21
22    /// Create an Expected error without line info.
23    #[inline]
24    pub fn expected(msg: &'static str) -> Self {
25        Error::Expected { msg, line: None }
26    }
27
28    /// Create an Expected error with line info.
29    #[inline]
30    pub fn expected_at(msg: &'static str, line: usize) -> Self {
31        Error::Expected {
32            msg,
33            line: Some(line),
34        }
35    }
36
37    /// Create an InvalidChar error without line info.
38    #[inline]
39    pub fn invalid_char(char: char) -> Self {
40        Error::InvalidChar { char, line: None }
41    }
42
43    /// Create an InvalidChar error with line info.
44    #[inline]
45    pub fn invalid_char_at(char: char, line: usize) -> Self {
46        Error::InvalidChar {
47            char,
48            line: Some(line),
49        }
50    }
51
52    /// Create a MaxStrLength error without line info.
53    #[inline]
54    pub fn max_str_length(max: usize) -> Self {
55        Error::MaxStrLength { max, line: None }
56    }
57
58    /// Create a MaxStrLength error with line info.
59    #[inline]
60    pub fn max_str_length_at(max: usize, line: usize) -> Self {
61        Error::MaxStrLength {
62            max,
63            line: Some(line),
64        }
65    }
66
67    /// Create a Version error without line info.
68    #[inline]
69    pub fn version(msg: &'static str) -> Self {
70        Error::Version { msg, line: None }
71    }
72
73    /// Create a Version error with line info.
74    #[inline]
75    pub fn version_at(msg: &'static str, line: usize) -> Self {
76        Error::Version {
77            msg,
78            line: Some(line),
79        }
80    }
81
82    /// Create a Message error without line info.
83    #[inline]
84    pub fn message(msg: &'static str) -> Self {
85        Error::Message { msg, line: None }
86    }
87
88    /// Create a Message error with line info.
89    #[inline]
90    pub fn message_at(msg: &'static str, line: usize) -> Self {
91        Error::Message {
92            msg,
93            line: Some(line),
94        }
95    }
96
97    /// Create a Receivers error without line info.
98    #[inline]
99    pub fn receivers(msg: &'static str) -> Self {
100        Error::Receivers { msg, line: None }
101    }
102
103    /// Create a Receivers error with line info.
104    #[inline]
105    pub fn receivers_at(msg: &'static str, line: usize) -> Self {
106        Error::Receivers {
107            msg,
108            line: Some(line),
109        }
110    }
111
112    /// Create a Nodes error without line info.
113    #[inline]
114    pub fn nodes(msg: &'static str) -> Self {
115        Error::Nodes { msg, line: None }
116    }
117
118    /// Create a Nodes error with line info.
119    #[inline]
120    pub fn nodes_at(msg: &'static str, line: usize) -> Self {
121        Error::Nodes {
122            msg,
123            line: Some(line),
124        }
125    }
126
127    /// Create a Signal error without line info.
128    #[inline]
129    pub fn signal(msg: &'static str) -> Self {
130        Error::Signal { msg, line: None }
131    }
132
133    /// Create a Signal error with line info.
134    #[inline]
135    pub fn signal_at(msg: &'static str, line: usize) -> Self {
136        Error::Signal {
137            msg,
138            line: Some(line),
139        }
140    }
141
142    // ============================================================================
143    // Line number helpers
144    // ============================================================================
145
146    /// Get the line number associated with this error, if any.
147    #[inline]
148    pub fn line(&self) -> Option<usize> {
149        match self {
150            Error::UnexpectedEof { line } => *line,
151            Error::Expected { line, .. } => *line,
152            Error::InvalidChar { line, .. } => *line,
153            Error::MaxStrLength { line, .. } => *line,
154            Error::Version { line, .. } => *line,
155            Error::Message { line, .. } => *line,
156            Error::Receivers { line, .. } => *line,
157            Error::Nodes { line, .. } => *line,
158            Error::Signal { line, .. } => *line,
159            Error::Decoding(_) | Error::Encoding(_) | Error::Validation(_) => None,
160            #[cfg(feature = "std")]
161            Error::Io(_) => None,
162        }
163    }
164
165    /// Add line number information to an error.
166    /// If the error already has line info, it is preserved.
167    #[inline]
168    pub fn with_line(self, line: usize) -> Self {
169        match self {
170            Error::UnexpectedEof { line: None } => Error::UnexpectedEof { line: Some(line) },
171            Error::Expected { msg, line: None } => Error::Expected {
172                msg,
173                line: Some(line),
174            },
175            Error::InvalidChar { char, line: None } => Error::InvalidChar {
176                char,
177                line: Some(line),
178            },
179            Error::MaxStrLength { max, line: None } => Error::MaxStrLength {
180                max,
181                line: Some(line),
182            },
183            Error::Version { msg, line: None } => Error::Version {
184                msg,
185                line: Some(line),
186            },
187            Error::Message { msg, line: None } => Error::Message {
188                msg,
189                line: Some(line),
190            },
191            Error::Receivers { msg, line: None } => Error::Receivers {
192                msg,
193                line: Some(line),
194            },
195            Error::Nodes { msg, line: None } => Error::Nodes {
196                msg,
197                line: Some(line),
198            },
199            Error::Signal { msg, line: None } => Error::Signal {
200                msg,
201                line: Some(line),
202            },
203            // Already has line info or doesn't support it - return unchanged
204            other => other,
205        }
206    }
207}
208
209impl fmt::Display for Error {
210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211        match self {
212            Error::UnexpectedEof { line } => {
213                if let Some(line) = line {
214                    write!(f, "line {}: {}", line, Error::UNEXPECTED_EOF)
215                } else {
216                    write!(f, "{}", Error::UNEXPECTED_EOF)
217                }
218            }
219            Error::Expected { msg, line } => {
220                if let Some(line) = line {
221                    write!(f, "line {}: {}", line, msg)
222                } else {
223                    write!(f, "{}", msg)
224                }
225            }
226            Error::InvalidChar { char, line } => {
227                if let Some(line) = line {
228                    write!(
229                        f,
230                        "line {}: {}: {}",
231                        line,
232                        Error::INVALID_CHARACTER,
233                        char.escape_debug()
234                    )
235                } else {
236                    write!(f, "{}: {}", Error::INVALID_CHARACTER, char.escape_debug())
237                }
238            }
239            Error::MaxStrLength { max, line } => {
240                if let Some(line) = line {
241                    write!(
242                        f,
243                        "line {}: {}: {}",
244                        line,
245                        Error::STRING_LENGTH_EXCEEDS_MAX,
246                        max
247                    )
248                } else {
249                    write!(f, "{}: {}", Error::STRING_LENGTH_EXCEEDS_MAX, max)
250                }
251            }
252            Error::Version { msg, line } => {
253                if let Some(line) = line {
254                    write!(f, "line {}: {}: {}", line, Error::VERSION_ERROR_PREFIX, msg)
255                } else {
256                    write!(f, "{}: {}", Error::VERSION_ERROR_PREFIX, msg)
257                }
258            }
259            Error::Message { msg, line } => {
260                if let Some(line) = line {
261                    write!(f, "line {}: {}: {}", line, Error::MESSAGE_ERROR_PREFIX, msg)
262                } else {
263                    write!(f, "{}: {}", Error::MESSAGE_ERROR_PREFIX, msg)
264                }
265            }
266            Error::Receivers { msg, line } => {
267                if let Some(line) = line {
268                    write!(
269                        f,
270                        "line {}: {}: {}",
271                        line,
272                        Error::RECEIVERS_ERROR_PREFIX,
273                        msg
274                    )
275                } else {
276                    write!(f, "{}: {}", Error::RECEIVERS_ERROR_PREFIX, msg)
277                }
278            }
279            Error::Nodes { msg, line } => {
280                if let Some(line) = line {
281                    write!(f, "line {}: {}: {}", line, Error::NODES_ERROR_PREFIX, msg)
282                } else {
283                    write!(f, "{}: {}", Error::NODES_ERROR_PREFIX, msg)
284                }
285            }
286            Error::Signal { msg, line } => {
287                if let Some(line) = line {
288                    write!(f, "line {}: {}: {}", line, Error::SIGNAL_ERROR_PREFIX, msg)
289                } else {
290                    write!(f, "{}: {}", Error::SIGNAL_ERROR_PREFIX, msg)
291                }
292            }
293            Error::Decoding(msg) => {
294                write!(f, "{}: {}", Error::DECODING_ERROR_PREFIX, msg)
295            }
296            Error::Encoding(msg) => {
297                write!(f, "{}: {}", Error::ENCODING_ERROR_PREFIX, msg)
298            }
299            Error::Validation(msg) => {
300                write!(f, "{}: {}", Error::VALIDATION_ERROR_PREFIX, msg)
301            }
302            #[cfg(feature = "std")]
303            Error::Io(msg) => {
304                write!(f, "I/O error: {}", msg)
305            }
306        }
307    }
308}
309
310#[cfg(feature = "std")]
311impl From<std::io::Error> for Error {
312    fn from(err: std::io::Error) -> Self {
313        Error::Io(err.to_string())
314    }
315}
316
317impl From<core::num::ParseIntError> for Error {
318    fn from(_err: core::num::ParseIntError) -> Self {
319        Error::expected(Error::PARSE_NUMBER_FAILED)
320    }
321}
322
323// std::error::Error is only available with std feature
324#[cfg(feature = "std")]
325impl std::error::Error for Error {}
326
327#[cfg(test)]
328mod tests {
329    #![allow(clippy::float_cmp)]
330
331    // Tests that require std feature (for Display/ToString)
332    #[cfg(feature = "std")]
333    mod tests_with_std {
334        use crate::Error;
335
336        #[test]
337        fn test_from_parse_int_error() {
338            // Create a ParseIntError by trying to parse an invalid string
339            let parse_error = "invalid".parse::<u32>().unwrap_err();
340            let error: Error = parse_error.into();
341
342            match error {
343                Error::Expected { msg, line } => {
344                    assert_eq!(msg, Error::PARSE_NUMBER_FAILED);
345                    assert_eq!(line, None);
346                }
347                _ => panic!("Expected Error::Expected"),
348            }
349        }
350
351        #[test]
352        fn test_display_decoding_error() {
353            let error = Error::Decoding("Test error message");
354            let display = error.to_string();
355            assert!(display.starts_with(Error::DECODING_ERROR_PREFIX));
356            assert!(display.contains("Test error message"));
357        }
358
359        #[test]
360        fn test_display_signal_error() {
361            let error = Error::signal("Test signal error");
362            let display = error.to_string();
363            assert!(display.starts_with(Error::SIGNAL_ERROR_PREFIX));
364            assert!(display.contains("Test signal error"));
365        }
366
367        #[test]
368        fn test_display_formatting() {
369            // Test that Display properly formats complex error messages
370            let error = Error::Decoding(
371                "Duplicate message ID: 256 (messages 'EngineData' and 'BrakeData')",
372            );
373            let display = error.to_string();
374            assert!(display.starts_with(Error::DECODING_ERROR_PREFIX));
375            assert!(display.contains("256"));
376            assert!(display.contains("EngineData"));
377            assert!(display.contains("BrakeData"));
378        }
379
380        #[test]
381        fn test_display_from_parse_int_error() {
382            let int_error = "not_a_number".parse::<u32>().unwrap_err();
383            let error: Error = int_error.into();
384            let display = error.to_string();
385
386            assert!(display.contains(Error::PARSE_NUMBER_FAILED));
387        }
388
389        #[test]
390        fn test_error_with_line_number() {
391            let error = Error::expected_at("Expected identifier", 42);
392            let display = error.to_string();
393            assert!(display.contains("line 42"));
394            assert!(display.contains("Expected identifier"));
395        }
396
397        #[test]
398        fn test_error_without_line_number() {
399            let error = Error::expected("Expected identifier");
400            let display = error.to_string();
401            assert!(!display.contains("line"));
402            assert!(display.contains("Expected identifier"));
403        }
404
405        #[test]
406        fn test_with_line_adds_line_info() {
407            let error = Error::expected("Expected whitespace");
408            assert_eq!(error.line(), None);
409
410            let error_with_line = error.with_line(10);
411            assert_eq!(error_with_line.line(), Some(10));
412        }
413
414        #[test]
415        fn test_with_line_preserves_existing_line() {
416            let error = Error::expected_at("Expected whitespace", 5);
417            let error_with_line = error.with_line(10);
418            // Should preserve the original line (5), not overwrite with 10
419            assert_eq!(error_with_line.line(), Some(5));
420        }
421
422        #[test]
423        fn test_invalid_char_display() {
424            let error = Error::invalid_char_at('\t', 15);
425            let display = error.to_string();
426            assert!(display.contains("line 15"));
427            assert!(display.contains("\\t"));
428        }
429
430        #[test]
431        fn test_max_str_length_display() {
432            let error = Error::max_str_length_at(256, 20);
433            let display = error.to_string();
434            assert!(display.contains("line 20"));
435            assert!(display.contains("256"));
436        }
437    }
438
439    // Tests that require std feature (for std::error::Error trait)
440    // Only available when std is enabled
441    #[cfg(feature = "std")]
442    mod tests_std {
443        use super::super::Error;
444        use std::error::Error as StdError;
445
446        #[test]
447        fn test_std_error_trait() {
448            let error = Error::Decoding("Test");
449            // Verify it implements std::error::Error
450            let _: &dyn StdError = &error;
451
452            // Verify source() returns None (no underlying error)
453            assert!(error.source().is_none());
454        }
455    }
456}