Skip to main content

ics_core/
error.rs

1//! Typed parser / formatter errors for `ics-core`.
2//!
3//! The `Parse` variant carries optional `line` and `property` fields.
4//! `line` is the 1-based logical line number within the input document
5//! (post-unfold). `property` is the offending property name when
6//! identifiable.
7
8use std::fmt;
9
10#[derive(Debug, PartialEq, Eq)]
11pub enum Error {
12    Parse {
13        message: String,
14        line: Option<u32>,
15        property: Option<String>,
16    },
17}
18
19impl fmt::Display for Error {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Self::Parse {
23                message,
24                line,
25                property,
26            } => {
27                f.write_str("parse error")?;
28                if let Some(l) = line {
29                    write!(f, " at line {l}")?;
30                }
31                if let Some(p) = property {
32                    write!(f, " [{p}]")?;
33                }
34                write!(f, ": {message}")
35            }
36        }
37    }
38}
39
40impl std::error::Error for Error {}
41
42impl Error {
43    /// Construct a `Parse` error from a plain message with no line / property
44    /// context. Use `parse_at_line` when a logical line number is available.
45    pub fn parse(message: impl Into<String>) -> Self {
46        Self::Parse {
47            message: message.into(),
48            line: None,
49            property: None,
50        }
51    }
52
53    /// Construct a `Parse` error attached to a specific 1-based logical
54    /// line number.
55    pub fn parse_at_line(line: u32, message: impl Into<String>) -> Self {
56        Self::Parse {
57            message: message.into(),
58            line: Some(line),
59            property: None,
60        }
61    }
62
63    /// Like `parse_at_line` but also records the offending property name.
64    pub fn parse_at(line: u32, property: impl Into<String>, message: impl Into<String>) -> Self {
65        Self::Parse {
66            message: message.into(),
67            line: Some(line),
68            property: Some(property.into()),
69        }
70    }
71}
72
73pub type Result<T> = std::result::Result<T, Error>;
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn display_without_line_or_property() {
81        let err = Error::parse("VEVENT missing DTSTAMP");
82        assert_eq!(err.to_string(), "parse error: VEVENT missing DTSTAMP");
83    }
84
85    #[test]
86    fn display_with_line_only() {
87        let err = Error::parse_at_line(42, "VEVENT missing DTSTAMP");
88        assert_eq!(
89            err.to_string(),
90            "parse error at line 42: VEVENT missing DTSTAMP"
91        );
92    }
93
94    #[test]
95    fn display_with_line_and_property() {
96        let err = Error::parse_at(42, "DTSTAMP", "Invalid value");
97        assert_eq!(
98            err.to_string(),
99            "parse error at line 42 [DTSTAMP]: Invalid value"
100        );
101    }
102}