Skip to main content

opening_hours_syntax/
error.rs

1use alloc::boxed::Box;
2use core::fmt;
3use core::ops::RangeInclusive;
4
5use crate::parser::Rule;
6use crate::rules::day::{WeekNum, Year};
7
8pub type Result<T> = core::result::Result<T, Error>;
9
10const REPORT_ISSUE_LINK: &str = "https://github.com/remi-dupre/opening-hours-rs/issues";
11
12#[derive(Clone, Debug)]
13pub enum Error {
14    /// Could not parse the expression. It is not conformant with the grammar defined in the wiki:
15    /// https://wiki.openstreetmap.org/wiki/Key:opening_hours/specification
16    Syntax(Box<pest::error::Error<Rule>>),
17    /// Use of an unsupported feature.
18    Unsupported(&'static str),
19    /// Some number has overflowed.
20    Overflow {
21        value: i16,
22        expected_bounds: RangeInclusive<i16>,
23    },
24    /// Extended time has overflowed.
25    InvalidExtendedTime { hour: u8, minutes: u8 },
26    /// A year range that starts after it ends.
27    InvertedYearRange { start: Year, end: Year, step: u16 },
28    /// A week range that starts after it ends.
29    InvertedWeekRange {
30        start: WeekNum,
31        end: WeekNum,
32        step: u8,
33    },
34    /// This should never be built at runtime if the grammar implementation is sound.
35    GrammarUnexpectedToken { rule: Rule, unexpected: Rule },
36    /// This should never be built at runtime if the grammar implementation is sound.
37    GrammarLogic { rule: Rule, invariant: &'static str },
38}
39
40impl Error {
41    /// If this is true, this is an error that should not be raised as long as the implementation is
42    /// sound. If this kind of error occurs, you can report it here :
43    /// https://github.com/remi-dupre/opening-hours-rs/issues
44    pub fn is_implementation_error(&self) -> bool {
45        matches!(
46            self,
47            Self::GrammarUnexpectedToken { .. } | Self::GrammarLogic { .. }
48        )
49    }
50}
51
52impl From<pest::error::Error<Rule>> for Error {
53    fn from(pest_err: pest::error::Error<Rule>) -> Self {
54        Self::Syntax(pest_err.into())
55    }
56}
57
58impl fmt::Display for Error {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            Self::Syntax(pest_err) => write!(f, "{pest_err}"),
62            Self::Unsupported(desc) => write!(f, "using an unsupported feature: {desc}"),
63            Self::InvalidExtendedTime { hour, minutes: minute } => {
64                write!(f, "invalid extended time for {hour:02}:{minute:02}")
65            }
66            Self::Overflow { value, expected_bounds } => {
67                write!(
68                    f,
69                    "{value} is too large: expected a value between {} and {}",
70                    expected_bounds.start(),
71                    expected_bounds.end(),
72                )
73            }
74            &Self::InvertedYearRange { start, end, .. } => {
75                write!(
76                    f,
77                    "Inverted year ranges are ambiguous, do you mean '{}-{}'?",
78                    *end, *start
79                )
80            }
81            &Self::InvertedWeekRange { start, end, .. } => {
82                write!(
83                    f,
84                    "Inverted week ranges are ambiguous, do you mean '{}-{}'?",
85                    *end, *start
86                )
87            }
88            Error::GrammarUnexpectedToken { rule, unexpected } => {
89                write!(
90                    f,
91                    "Library implementation error in {rule:?}: found unexpected child {unexpected:?}. "
92                )?;
93
94                write!(f, "Please report an issue at {REPORT_ISSUE_LINK}.")
95            }
96            Self::GrammarLogic { rule, invariant: detail } => {
97                write!(f, "Library implementation error in {rule:?}: {detail}. ")?;
98                write!(f, "Please report an issue at {REPORT_ISSUE_LINK}.")
99            }
100        }
101    }
102}
103
104#[cfg(feature = "std")]
105impl std::error::Error for Error {
106    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
107        match self {
108            Self::Syntax(err) => Some(err as _),
109            _ => None,
110        }
111    }
112}
113
114// --
115// -- Helpers
116// --
117
118/// Commonly built errors
119pub(crate) fn err_empty(rule: Rule) -> Error {
120    Error::GrammarLogic { rule, invariant: "cannot be empty" }
121}