ron_reboot/
error.rs

1use std::{
2    fmt::{Display, Formatter},
3    io::stderr,
4};
5
6use crate::location::Location;
7
8#[derive(Clone, Debug, Default, PartialEq)]
9pub struct ErrorContext {
10    pub start_end: Option<(Location, Location)>,
11    pub file_name: Option<String>,
12    pub file_content: Option<String>,
13}
14
15#[derive(Clone, Debug, PartialEq)]
16pub struct Error {
17    pub kind: ErrorKind,
18    pub context: Option<Box<ErrorContext>>,
19}
20
21impl Error {
22    /// Set locations for this error, if they are `None`.
23    /// Keeps already set locations.
24    pub fn context_loc(self, start: Location, end: Location) -> Self {
25        let mut context = self.context.unwrap_or_default();
26        context.start_end.get_or_insert((start, end));
27
28        Error {
29            kind: self.kind,
30            context: Some(context),
31        }
32    }
33
34    /// Set file name for this error, if they are `None`.
35    /// Keeps already set file name.
36    pub fn context_file_name(self, file_name: String) -> Self {
37        let mut context = self.context.unwrap_or_default();
38        context.file_name.get_or_insert(file_name);
39
40        Error {
41            kind: self.kind,
42            context: Some(context),
43        }
44    }
45
46    /// Set file content for this error, if they are `None`.
47    /// Keeps already set file contents.
48    pub fn context_file_content(self, file_content: String) -> Self {
49        let mut context = self.context.unwrap_or_default();
50        context.file_content.get_or_insert(file_content);
51
52        Error {
53            kind: self.kind,
54            context: Some(context),
55        }
56    }
57
58    /// Set locations for this error, if they are `None`.
59    /// Keeps already set locations.
60    pub fn start(&self) -> Option<Location> {
61        self.context
62            .as_ref()
63            .and_then(|c| c.start_end)
64            .map(|se| se.0)
65    }
66
67    /// Set locations for this error, if they are `None`.
68    /// Keeps already set locations.
69    pub fn end(&self) -> Option<Location> {
70        self.context
71            .as_ref()
72            .and_then(|c| c.start_end)
73            .map(|se| se.1)
74    }
75}
76
77impl From<std::io::Error> for Error {
78    fn from(e: std::io::Error) -> Self {
79        Error {
80            kind: ErrorKind::IoError(e.to_string()),
81            context: None,
82        }
83    }
84}
85
86#[cfg(feature = "serde")]
87impl serde::de::Error for Error {
88    fn custom<T>(msg: T) -> Self
89    where
90        T: Display,
91    {
92        Error {
93            kind: ErrorKind::Custom(msg.to_string()),
94            context: None,
95        }
96    }
97}
98
99#[cfg(feature = "serde")]
100impl serde::ser::Error for Error {
101    fn custom<T>(msg: T) -> Self
102        where
103            T: Display,
104    {
105        Error {
106            kind: ErrorKind::Custom(msg.to_string()),
107            context: None,
108        }
109    }
110}
111
112impl Display for Error {
113    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114        // TODO: any way to do this more elegantly?
115        match self.context.as_ref() {
116            Some(context) => match (
117                context.start_end.as_ref(),
118                context.file_name.as_ref(),
119                context.file_content.as_ref(),
120            ) {
121                (Some((start, _)), _, _) => {
122                    write!(f, "error at {}: {}", start, self.kind)
123                }
124                (_, _, _) => {
125                    write!(f, "error: {}", self.kind)
126                }
127            },
128            None => write!(f, "error: {}", self.kind),
129        }
130    }
131}
132
133impl std::error::Error for Error {}
134
135pub fn print_error(e: &Error) -> std::io::Result<()> {
136    use std::io::Write;
137
138    let f = stderr();
139    let mut f = f.lock();
140    match e.context.as_ref() {
141        Some(context) => match (
142            context.start_end.as_ref(),
143            context.file_name.as_ref(),
144            context.file_content.as_ref(),
145        ) {
146            (Some((start, end)), file_name, Some(file_content)) => {
147                let max_line_col_width = start.line.max(end.line).to_string().len();
148                let col_ws_rep = " ".repeat(max_line_col_width);
149                writeln!(f, "error: {}", e.kind)?;
150                writeln!(
151                    f,
152                    "{}--> {}:{}:{}",
153                    col_ws_rep,
154                    file_name.map(AsRef::as_ref).unwrap_or("string"),
155                    start.line,
156                    start.column
157                )?;
158
159                writeln!(f, "{} |", col_ws_rep)?;
160                let mut lines = file_content.lines().skip(start.line as usize - 1);
161                let start_line_string = start.line.to_string();
162                let start_line_padding = " ".repeat(max_line_col_width - start_line_string.len());
163
164                if start.line == end.line {
165                    // The first line
166                    writeln!(
167                        f,
168                        "{}{} | {}",
169                        start_line_padding,
170                        start.line,
171                        lines.next().unwrap_or_default()
172                    )?;
173                    // it's just one line, mark the whole span with ^
174                    writeln!(
175                        f,
176                        "{} | {}{}",
177                        col_ws_rep,
178                        " ".repeat(start.column as usize - 1),
179                        "^".repeat((end.column - start.column) as usize)
180                    )?;
181                } else {
182                    // The first line
183                    writeln!(
184                        f,
185                        "{}{} |   {}",
186                        start_line_padding,
187                        start.line,
188                        lines.next().unwrap_or_default()
189                    )?;
190                    writeln!(
191                        f,
192                        "{} |  {}^",
193                        col_ws_rep,
194                        "_".repeat((start.column - 1) as usize),
195                    )?;
196                    for line_number in start.line + 1..=end.line {
197                        let line_nr_string = line_number.to_string();
198                        let line_padding = " ".repeat(max_line_col_width - line_nr_string.len());
199                        writeln!(
200                            f,
201                            "{}{} | | {}",
202                            line_padding,
203                            line_nr_string,
204                            lines.next().unwrap_or_default()
205                        )?;
206                    }
207
208                    writeln!(
209                        f,
210                        "{} | |{}^",
211                        col_ws_rep,
212                        "_".repeat((end.column - 1) as usize)
213                    )?;
214                }
215
216                writeln!(f, "{} |", col_ws_rep)
217            }
218            (_, Some(file_name), _) => writeln!(f, "file \"{}\": {}", file_name, e),
219            _ => writeln!(f, "{}", e),
220        },
221        _ => writeln!(f, "{}", e),
222    }
223}
224
225#[derive(Clone, Debug, PartialEq)]
226#[non_exhaustive]
227pub enum ErrorKind {
228    ExpectedBool,
229    ExpectedString,
230    ExpectedStrGotEscapes,
231    ExpectedList,
232
233    ParseError(String),
234
235    IoError(String),
236    Custom(String),
237}
238
239impl Display for ErrorKind {
240    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
241        match self {
242            ErrorKind::ExpectedBool => write!(f, "expected bool"),
243            ErrorKind::ExpectedStrGotEscapes => {
244                write!(f, "expected zero-copy string which doesn't support escapes")
245            }
246            ErrorKind::ExpectedString => write!(f, "expected string"),
247            ErrorKind::ExpectedList => write!(f, "expected list"),
248            ErrorKind::ParseError(e) => write!(f, "parsing error: {}", e),
249            ErrorKind::IoError(e) => write!(f, "io error: {}", e),
250            ErrorKind::Custom(s) => write!(f, "{}", s),
251        }
252    }
253}