1use std::{error::Error, fmt, io, ops::Deref, str, string};
5
6use miette::{Diagnostic, NamedSource, SourceSpan};
7use topiary_tree_sitter_facade::Range;
8
9use crate::tree_sitter::NodeSpan;
10
11#[derive(Debug)]
13pub enum FormatterError {
14 Idempotence,
18
19 IdempotenceParsing(Box<FormatterError>),
23
24 Internal(String, Option<Box<dyn Error>>),
26
27 Parsing(Box<NodeSpan>),
29 PatternDoesNotMatch,
31
32 Query(String, Option<topiary_tree_sitter_facade::QueryError>),
35
36 Io(IoError),
38}
39
40#[derive(Debug)]
42pub enum IoError {
43 Filesystem(String, io::Error),
49
50 Generic(String, Option<Box<dyn Error>>),
53}
54
55impl FormatterError {
56 fn get_span(&mut self) -> Option<&mut NodeSpan> {
57 match self {
58 Self::Parsing(span) => Some(span),
59 Self::IdempotenceParsing(err) => err.get_span(),
60 _ => None,
61 }
62 }
63 pub fn with_content(mut self, content: String) -> Self {
64 if let Some(span) = self.get_span() {
65 span.set_content(content);
66 }
67 self
68 }
69
70 pub fn with_location(mut self, location: String) -> Self {
71 if let Some(span) = self.get_span() {
72 span.set_location(location);
73 }
74 self
75 }
76}
77
78impl fmt::Display for FormatterError {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 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";
81 match self {
82 Self::Idempotence => {
83 write!(
84 f,
85 "The formatter did not produce the same\nresult when invoked twice (idempotence check).\n\n{please_log_message}"
86 )
87 }
88
89 Self::IdempotenceParsing(_) => {
90 write!(
91 f,
92 "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."
93 )
94 }
95
96 Self::Parsing(span) => {
97 let report = miette::Report::new(ErrorSpan::from(span));
98 write!(f, "{report:?}")
99 }
100
101 Self::PatternDoesNotMatch => {
102 write!(
103 f,
104 "The query contains a pattern that does not match the input"
105 )
106 }
107
108 Self::Internal(message, _)
109 | Self::Query(message, _)
110 | Self::Io(IoError::Filesystem(message, _) | IoError::Generic(message, _)) => {
111 write!(f, "{message}")
112 }
113 }
114 }
115}
116
117impl Error for FormatterError {
118 fn source(&self) -> Option<&(dyn Error + 'static)> {
119 match self {
120 Self::Idempotence
121 | Self::Parsing(_)
122 | Self::PatternDoesNotMatch
123 | Self::Io(IoError::Generic(_, None)) => None,
124 Self::Internal(_, source) => source.as_ref().map(Deref::deref),
125 Self::Query(_, source) => source.as_ref().map(|e| e as &dyn Error),
126 Self::Io(IoError::Filesystem(_, source)) => Some(source),
127 Self::Io(IoError::Generic(_, Some(source))) => Some(source.as_ref()),
128 Self::IdempotenceParsing(source) => Some(source),
129 }
130 }
131}
132
133impl From<io::Error> for FormatterError {
136 fn from(e: io::Error) -> Self {
137 IoError::from(e).into()
138 }
139}
140
141impl From<io::Error> for IoError {
142 fn from(e: io::Error) -> Self {
143 match e.kind() {
144 io::ErrorKind::NotFound => IoError::Filesystem("File not found".into(), e),
145 _ => IoError::Filesystem("Could not read or write to file".into(), e),
146 }
147 }
148}
149impl From<IoError> for FormatterError {
150 fn from(e: IoError) -> Self {
151 Self::Io(e)
152 }
153}
154
155impl From<str::Utf8Error> for FormatterError {
156 fn from(e: str::Utf8Error) -> Self {
157 Self::Io(IoError::Generic(
158 "Input is not valid UTF-8".into(),
159 Some(Box::new(e)),
160 ))
161 }
162}
163
164impl From<string::FromUtf8Error> for FormatterError {
165 fn from(e: string::FromUtf8Error) -> Self {
166 Self::Io(IoError::Generic(
167 "Input is not valid UTF-8".into(),
168 Some(Box::new(e)),
169 ))
170 }
171}
172
173impl From<fmt::Error> for FormatterError {
174 fn from(e: fmt::Error) -> Self {
175 Self::Io(IoError::Generic(
176 "Failed to format output".into(),
177 Some(Box::new(e)),
178 ))
179 }
180}
181
182impl<W> From<io::IntoInnerError<W>> for FormatterError
185where
186 W: io::Write + fmt::Debug + Send + 'static,
187{
188 fn from(e: io::IntoInnerError<W>) -> Self {
189 Self::Io(IoError::Generic(
190 "Cannot flush internal buffer".into(),
191 Some(Box::new(e)),
192 ))
193 }
194}
195
196impl From<serde_json::Error> for FormatterError {
197 fn from(e: serde_json::Error) -> Self {
198 Self::Internal("Could not serialise JSON output".into(), Some(Box::new(e)))
199 }
200}
201
202impl From<topiary_tree_sitter_facade::LanguageError> for FormatterError {
203 fn from(e: topiary_tree_sitter_facade::LanguageError) -> Self {
204 Self::Internal(
205 "Error while loading language grammar".into(),
206 Some(Box::new(e)),
207 )
208 }
209}
210
211impl From<topiary_tree_sitter_facade::ParserError> for FormatterError {
212 fn from(e: topiary_tree_sitter_facade::ParserError) -> Self {
213 Self::Internal("Error while parsing".into(), Some(Box::new(e)))
214 }
215}
216
217impl From<NodeSpan> for FormatterError {
218 fn from(span: NodeSpan) -> Self {
219 Self::Parsing(Box::new(span))
220 }
221}
222
223#[derive(Diagnostic, Debug)]
224struct ErrorSpan {
225 #[source_code]
226 src: NamedSource<String>,
227 #[label("(ERROR) node")]
228 span: SourceSpan,
229 range: Range,
230}
231
232impl std::fmt::Display for ErrorSpan {
233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234 let start = self.range.start_point();
235 let end = self.range.end_point();
236 write!(
237 f,
238 "Parsing error between line {}, column {} and line {}, column {}",
239 start.row(),
240 start.column(),
241 end.row(),
242 end.column()
243 )
244 }
245}
246
247impl std::error::Error for ErrorSpan {}
248
249impl From<&Box<NodeSpan>> for ErrorSpan {
250 fn from(span: &Box<NodeSpan>) -> Self {
251 Self {
252 src: NamedSource::new(
253 span.location.clone().unwrap_or_default(),
254 span.content.clone().unwrap_or_default(),
255 )
256 .with_language(span.language),
257 span: span.source_span(),
258 range: span.range,
259 }
260 }
261}