1use std::{error::Error, fmt, io, ops::Deref, str, string};
5
6#[derive(Debug)]
8pub enum FormatterError {
9 Idempotence,
13
14 IdempotenceParsing(Box<FormatterError>),
18
19 Internal(String, Option<Box<dyn Error>>),
21
22 Parsing {
24 start_line: u32,
25 start_column: u32,
26 end_line: u32,
27 end_column: u32,
28 },
29
30 PatternDoesNotMatch,
32
33 Query(String, Option<topiary_tree_sitter_facade::QueryError>),
36
37 Io(IoError),
39}
40
41#[derive(Debug)]
43pub enum IoError {
44 Filesystem(String, io::Error),
50
51 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
115impl 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
157impl<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}