topiary_core/
error.rs

1//! This module defines all errors that might be propagated out of the library,
2//! including all of the trait implementations one might expect for Errors.
3
4use std::{error::Error, fmt, io, ops::Deref, str, string};
5
6/// The various errors the formatter may return.
7#[derive(Debug)]
8pub enum FormatterError {
9    /// The input produced output that isn't idempotent, i.e. formatting the
10    /// output again made further changes. If this happened using our provided
11    /// query files, it is a bug. Please log an issue.
12    Idempotence,
13
14    /// The input produced invalid output, i.e. formatting the output again led
15    /// to a parsing error. If this happened using our provided query files, it
16    /// is a bug. Please log an issue.
17    IdempotenceParsing(Box<FormatterError>),
18
19    /// An internal error occurred. This is a bug. Please log an issue.
20    Internal(String, Option<Box<dyn Error>>),
21
22    /// Tree-sitter could not parse the input without errors.
23    Parsing {
24        start_line: u32,
25        start_column: u32,
26        end_line: u32,
27        end_column: u32,
28    },
29
30    /// The query contains a pattern that had no match in the input file.
31    PatternDoesNotMatch,
32
33    /// There was an error in the query file. If this happened using our
34    /// provided query files, it is a bug. Please log an issue.
35    Query(String, Option<topiary_tree_sitter_facade::QueryError>),
36
37    /// I/O-related errors
38    Io(IoError),
39}
40
41/// A subtype of `FormatterError::Io`
42#[derive(Debug)]
43pub enum IoError {
44    // NOTE: Filesystem-based IO errors _ought_ to become a thing of the past,
45    // once the library and binary code have been completely separated (see
46    // Issue #303).
47    /// A filesystem based IO error, with an additional owned string to provide
48    /// Topiary specific information
49    Filesystem(String, io::Error),
50
51    /// Any other Error with an additional owned string to provide Topiary
52    /// specific information
53    Generic(String, Option<Box<dyn Error>>),
54}
55
56impl fmt::Display for FormatterError {
57    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
58        let please_log_message = "If this happened with the built-in query files, it is a bug. It would be\nhelpful if you logged this error at\nhttps://github.com/tweag/topiary/issues/new?assignees=&labels=type%3A+bug&template=bug_report.md";
59        match self {
60            Self::Idempotence => {
61                write!(
62                    f,
63                    "The formatter did not produce the same\nresult when invoked twice (idempotence check).\n\n{please_log_message}"
64                )
65            }
66
67            Self::IdempotenceParsing(_) => {
68                write!(
69                    f,
70                    "The formatter produced invalid output and\nfailed when trying to format twice (idempotence check).\n\n{please_log_message}\n\nThe following is the error received when running the second time, but note\nthat any line and column numbers refer to the formatted code, not the\noriginal input. Run Topiary with the --skip-idempotence flag to see this\ninvalid formatted code."
71                )
72            }
73
74            Self::Parsing {
75                start_line,
76                start_column,
77                end_line,
78                end_column,
79            } => {
80                write!(f, "Parsing error between line {start_line}, column {start_column} and line {end_line}, column {end_column}")
81            }
82
83            Self::PatternDoesNotMatch => {
84                write!(
85                    f,
86                    "The query contains a pattern that does not match the input"
87                )
88            }
89
90            Self::Internal(message, _)
91            | Self::Query(message, _)
92            | Self::Io(IoError::Filesystem(message, _) | IoError::Generic(message, _)) => {
93                write!(f, "{message}")
94            }
95        }
96    }
97}
98
99impl Error for FormatterError {
100    fn source(&self) -> Option<&(dyn Error + 'static)> {
101        match self {
102            Self::Idempotence
103            | Self::Parsing { .. }
104            | Self::PatternDoesNotMatch
105            | Self::Io(IoError::Generic(_, None)) => None,
106            Self::Internal(_, source) => source.as_ref().map(Deref::deref),
107            Self::Query(_, source) => source.as_ref().map(|e| e as &dyn Error),
108            Self::Io(IoError::Filesystem(_, source)) => Some(source),
109            Self::Io(IoError::Generic(_, Some(source))) => Some(source.as_ref()),
110            Self::IdempotenceParsing(source) => Some(source),
111        }
112    }
113}
114
115// NOTE: Filesystem-based IO errors _ought_ to become a thing of the past, once
116// the library and binary code have been completely separated (see Issue #303).
117impl From<io::Error> for FormatterError {
118    fn from(e: io::Error) -> Self {
119        match e.kind() {
120            io::ErrorKind::NotFound => Self::Io(IoError::Filesystem("File not found".into(), e)),
121
122            _ => Self::Io(IoError::Filesystem(
123                "Could not read or write to file".into(),
124                e,
125            )),
126        }
127    }
128}
129
130impl From<str::Utf8Error> for FormatterError {
131    fn from(e: str::Utf8Error) -> Self {
132        Self::Io(IoError::Generic(
133            "Input is not valid UTF-8".into(),
134            Some(Box::new(e)),
135        ))
136    }
137}
138
139impl From<string::FromUtf8Error> for FormatterError {
140    fn from(e: string::FromUtf8Error) -> Self {
141        Self::Io(IoError::Generic(
142            "Input is not valid UTF-8".into(),
143            Some(Box::new(e)),
144        ))
145    }
146}
147
148impl From<fmt::Error> for FormatterError {
149    fn from(e: fmt::Error) -> Self {
150        Self::Io(IoError::Generic(
151            "Failed to format output".into(),
152            Some(Box::new(e)),
153        ))
154    }
155}
156
157// We only have to deal with io::BufWriter<Vec<u8>>, but the genericised code is
158// clearer
159impl<W> From<io::IntoInnerError<W>> for FormatterError
160where
161    W: io::Write + fmt::Debug + Send + 'static,
162{
163    fn from(e: io::IntoInnerError<W>) -> Self {
164        Self::Io(IoError::Generic(
165            "Cannot flush internal buffer".into(),
166            Some(Box::new(e)),
167        ))
168    }
169}
170
171impl From<serde_json::Error> for FormatterError {
172    fn from(e: serde_json::Error) -> Self {
173        Self::Internal("Could not serialise JSON output".into(), Some(Box::new(e)))
174    }
175}
176
177impl From<topiary_tree_sitter_facade::LanguageError> for FormatterError {
178    fn from(e: topiary_tree_sitter_facade::LanguageError) -> Self {
179        Self::Internal(
180            "Error while loading language grammar".into(),
181            Some(Box::new(e)),
182        )
183    }
184}
185
186impl From<topiary_tree_sitter_facade::ParserError> for FormatterError {
187    fn from(e: topiary_tree_sitter_facade::ParserError) -> Self {
188        Self::Internal("Error while parsing".into(), Some(Box::new(e)))
189    }
190}