simple_html_template/
errors.rs

1use std::error::Error as ErrorTrait;
2use std::fmt;
3
4/// Errors that occured while finding and replacing
5///
6/// This implements the [`Display`](std::fmt::Display) trait and can be printed
7/// nicely that way. There is also [`Self::into_inner`](Errors::into_inner) if
8/// you need more control.
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
10pub struct Errors {
11    pub(crate) inner: Vec<Error>,
12}
13
14impl Errors {
15    /// Get error list
16    pub fn into_inner(self) -> Vec<Error> {
17        self.inner
18    }
19}
20
21impl ErrorTrait for Errors {}
22
23// This is awful but the results are pretty
24impl fmt::Display for Errors {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        let uncloseds = self.inner.iter().filter_map(|e| {
27            if let Error::Unclosed(i) = e {
28                Some(i)
29            } else {
30                None
31            }
32        });
33
34        let missings = self.inner.iter().filter_map(|e| {
35            if let Error::Missing(k) = e {
36                Some(k)
37            } else {
38                None
39            }
40        });
41
42        let extras = self.inner.iter().filter_map(|e| {
43            if let Error::Extra(k) = e {
44                Some(k)
45            } else {
46                None
47            }
48        });
49
50        let uncloseds_count = uncloseds.clone().count();
51        let missings_count = missings.clone().count();
52        let extras_count = extras.clone().count();
53
54        format_error(
55            f,
56            "unclosed delimiter opened at byte",
57            uncloseds,
58            uncloseds_count,
59            false,
60        )?;
61
62        if uncloseds_count > 0 && missings_count > 0 {
63            write!(f, "; ")?;
64        }
65
66        format_error(f, "missing key", missings, missings_count, true)?;
67
68        if (uncloseds_count > 0 || missings_count > 0) && extras_count > 0 {
69            write!(f, "; ")?;
70        }
71
72        format_error(f, "extraneous key", extras, extras_count, true)?;
73
74        Ok(())
75    }
76}
77
78// This is also awful but I don't think it can really be better
79fn format_error(
80    f: &mut fmt::Formatter<'_>,
81    problem: &str,
82    iter: impl Iterator<Item = impl fmt::Display>,
83    count: usize,
84    quotes: bool,
85) -> fmt::Result {
86    if count > 0 {
87        let s = if count != 1 {
88            "s"
89        } else {
90            ""
91        };
92
93        write!(f, "{}{}: ", problem, s)?;
94        for (i, error) in iter.enumerate() {
95            let sep = if i + 1 == count {
96                ""
97            } else if i + 2 == count {
98                if count == 2 {
99                    " and "
100                } else {
101                    ", and "
102                }
103            } else {
104                ", "
105            };
106
107            if quotes {
108                write!(f, "\"{}\"{}", error, sep)?;
109            } else {
110                write!(f, "{}{}", error, sep)?;
111            }
112        }
113    }
114
115    Ok(())
116}
117
118/// A single specific error
119#[derive(Clone, Debug, PartialEq, Eq, Hash)]
120pub enum Error {
121    /// A key was found in the template but no value was provided
122    ///
123    /// Holds the name of the offending key
124    Missing(String),
125
126    /// A key-value pair was given but never used in the template
127    ///
128    /// Holds the name of the offending key
129    Extra(String),
130
131    /// A key-begin delimeter was found but there was no matching key-close
132    /// delimiter
133    ///
134    /// Holds the zero-indexed byte position of the beginning of the opening
135    /// delimiter
136    Unclosed(usize),
137}
138
139impl fmt::Display for Error {
140    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141        match self {
142            Self::Missing(k) => write!(f, "missing key \"{}\"", k),
143            Self::Extra(k) => write!(f, "extraneous key \"{}\"", k),
144            Self::Unclosed(i) => {
145                write!(f, "unclosed delimitor opened at byte {}", i)
146            }
147        }
148    }
149}