alpm_srcinfo/error.rs
1//! All error types that are exposed by this crate.
2use std::{fmt::Display, path::PathBuf, string::FromUtf8Error};
3
4use colored::Colorize;
5use thiserror::Error;
6
7#[cfg(doc)]
8use crate::{parser::SourceInfoContent, source_info::SourceInfo};
9
10/// The high-level error that can occur when using this crate.
11///
12/// Notably, it contains two important enums in the context of parsing:
13/// - `ParseError` is a already formatted error generated by the `winnow` parser. This effectively
14/// means that some invalid data has been encountered.
15/// - `SourceInfoErrors` is a list of all logical or lint errors that're encountered in the final
16/// step. This error also contains the original file on which the errors occurred.
17#[derive(Debug, Error)]
18#[non_exhaustive]
19pub enum Error {
20 /// ALPM type error
21 #[error("ALPM type parse error: {0}")]
22 AlpmType(#[from] alpm_types::Error),
23
24 /// IO error
25 #[error("I/O error while {0}:\n{1}")]
26 Io(&'static str, std::io::Error),
27
28 /// IO error with additional path info for more context.
29 #[error("I/O error at path {0:?} while {1}:\n{2}")]
30 IoPath(PathBuf, &'static str, std::io::Error),
31
32 /// UTF-8 parse error when reading the input file.
33 #[error(transparent)]
34 InvalidUTF8(#[from] FromUtf8Error),
35
36 /// No input file given.
37 ///
38 /// This error only occurs when running the [`crate::commands`] functions.
39 #[error("No input file given.")]
40 NoInputFile,
41
42 /// A parsing error that occurred during winnow file parsing.
43 #[error("File parsing error:\n{0}")]
44 ParseError(String),
45
46 /// A list of errors that occurred during the final SRCINFO data parsing step.
47 ///
48 /// These may contain any combination of [`SourceInfoError`].
49 #[error("Errors while parsing SRCINFO data:\n\n{0}")]
50 SourceInfoErrors(SourceInfoErrors),
51
52 /// JSON error while creating JSON formatted output.
53 ///
54 /// This error only occurs when running the [`crate::commands`] functions.
55 #[error("JSON error: {0}")]
56 Json(#[from] serde_json::Error),
57}
58
59/// A helper struct to provide proper line based error/linting messages.
60///
61/// Provides a list of [`SourceInfoError`]s and the SRCINFO data in which the errors occurred.
62#[derive(Debug, Clone)]
63pub struct SourceInfoErrors {
64 inner: Vec<SourceInfoError>,
65 file_content: String,
66}
67
68impl Display for SourceInfoErrors {
69 /// Display all errors in one big well-formatted error message.
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 // We go through all errors and print them out one after another.
72 let mut error_iter = self.inner.iter().enumerate().peekable();
73 while let Some((index, error)) = error_iter.next() {
74 // Line and message are generic and the same for every error message.
75 let line_nr = error.line;
76 let message = &error.message;
77
78 // Build the the headline based on the error type.
79 let specific_line =
80 line_nr.map_or("".to_string(), |line| format!(" on line {}", line + 1));
81 let headline = match error.error_type {
82 SourceInfoErrorType::LintWarning => {
83 format!("{}{specific_line}:", "Linter Warning".yellow())
84 }
85 SourceInfoErrorType::DeprecationWarning => {
86 format!("{}{specific_line}:", "Deprecation Warning".yellow())
87 }
88 SourceInfoErrorType::Unrecoverable => {
89 format!("{}{specific_line}:", "Logical Error".red())
90 }
91 };
92
93 // Write the headline
94 let error_index = format!("[{index}]").bold().red();
95 // Print the error details slightly indented based on the length of the error index
96 // prefix.
97 let indentation = " ".repeat(error_index.len() + 1);
98 write!(f, "{error_index} {headline}")?;
99 // Add the line, if it exists.
100 // Prefix it with a bold line number for better visibility.
101 if let Some(line_nr) = line_nr {
102 let content_line = self
103 .file_content
104 .lines()
105 .nth(line_nr)
106 .expect("Error: Couldn't seek to line. Please report bug upstream.");
107 // Lines aren't 0 indexed.
108 let human_line_nr = line_nr + 1;
109 write!(
110 f,
111 "\n{indentation}{} {content_line }\n",
112 format!("{human_line_nr}: |").to_string().bold()
113 )?;
114 }
115
116 // Write the message with some spacing
117 write!(f, "\n{indentation}{message}")?;
118
119 // Write two newlines with a red separator between this and the next error
120 if error_iter.peek().is_some() {
121 write!(f, "\n\n{}", "──────────────────────────────\n".bold())?;
122 }
123 }
124
125 Ok(())
126 }
127}
128
129impl SourceInfoErrors {
130 /// Creates a new [`SourceInfoErrors`].
131 pub fn new(errors: Vec<SourceInfoError>, file_content: String) -> Self {
132 Self {
133 inner: errors,
134 file_content,
135 }
136 }
137
138 /// Filters the inner errors based on a given closure.
139 pub fn filter<F>(&mut self, filter: F)
140 where
141 F: Fn(&SourceInfoError) -> bool,
142 {
143 self.inner.retain(filter);
144 }
145
146 /// Returns a reference to the list of errors.
147 pub fn errors(&self) -> &Vec<SourceInfoError> {
148 &self.inner
149 }
150
151 /// Filters for and errors on unrecoverable errors.
152 ///
153 /// Consumes `self` and simply returns if `self` contains no [`SourceInfoError`] of type
154 /// [`SourceInfoErrorType::Unrecoverable`].
155 ///
156 /// # Errors
157 ///
158 /// Returns an error if `self` contains any [`SourceInfoError`] of type
159 /// [`SourceInfoErrorType::Unrecoverable`].
160 pub fn check_unrecoverable_errors(mut self) -> Result<(), Error> {
161 // Filter only for errors that're unrecoverable, i.e. critical.
162 self.filter(|err| matches!(err.error_type, SourceInfoErrorType::Unrecoverable));
163
164 if !self.inner.is_empty() {
165 self.sort_errors();
166 return Err(Error::SourceInfoErrors(self));
167 }
168
169 Ok(())
170 }
171
172 /// Sorts the errors.
173 ///
174 /// The following order is applied:
175 ///
176 /// - Hard errors without line numbers
177 /// - Hard errors with line numbers, by ascending line number
178 /// - Deprecation warnings without line numbers
179 /// - Deprecation warnings with line numbers, by ascending line number
180 /// - Linter warnings without line numbers
181 /// - Linter warnings with line numbers, by ascending line number
182 fn sort_errors(&mut self) {
183 self.inner.sort_by(|a, b| {
184 use std::cmp::Ordering;
185
186 let prio = |error: &SourceInfoError| match error.error_type {
187 SourceInfoErrorType::Unrecoverable => 0,
188 SourceInfoErrorType::DeprecationWarning => 1,
189 SourceInfoErrorType::LintWarning => 2,
190 };
191
192 // Extract ordering criteria based on error type.
193 let a_prio = prio(a);
194 let b_prio = prio(b);
195
196 // Compare by error severity first.
197 match a_prio.cmp(&b_prio) {
198 // If it's the same error, do a comparison on a line basis.
199 // Unspecific errors should come first!
200 Ordering::Equal => match (a.line, b.line) {
201 (Some(a), Some(b)) => a.cmp(&b),
202 (Some(_), None) => Ordering::Less,
203 (None, Some(_)) => Ordering::Greater,
204 (None, None) => Ordering::Equal,
205 },
206 // If it's not the same error, the ordering is clear.
207 other => other,
208 }
209 });
210 }
211}
212
213/// Errors that may occur when converting [`SourceInfoContent`] into a [`SourceInfo`].
214///
215/// The severity of an error is defined by its [`SourceInfoErrorType`], which may range from linting
216/// errors, deprecation warnings to hard unrecoverable errors.
217#[derive(Debug, Clone)]
218pub struct SourceInfoError {
219 pub error_type: SourceInfoErrorType,
220 pub line: Option<usize>,
221 pub message: String,
222}
223
224/// A [`SourceInfoError`] type.
225///
226/// Provides context for the severity of a [`SourceInfoError`].
227/// The type of "error" that has occurred.
228#[derive(Debug, Clone)]
229pub enum SourceInfoErrorType {
230 /// A simple linter error type. Can be ignored but should be fixed.
231 LintWarning,
232 /// Something changed in the SRCINFO format and this should be removed for future
233 /// compatibility.
234 DeprecationWarning,
235 /// A hard unrecoverable logic error has been detected.
236 /// The returned [SourceInfo] representation is faulty and should not be used.
237 Unrecoverable,
238}
239
240/// Creates a [`SourceInfoError`] for unrecoverable issues.
241///
242/// Takes an optional `line` on which the issue occurred and a `message`.
243pub fn unrecoverable(line: Option<usize>, message: impl ToString) -> SourceInfoError {
244 SourceInfoError {
245 error_type: SourceInfoErrorType::Unrecoverable,
246 line,
247 message: message.to_string(),
248 }
249}
250
251/// Creates a [`SourceInfoError`] for linting issues.
252///
253/// Takes an optional `line` on which the issue occurred and a `message`.
254pub fn lint(line: Option<usize>, message: impl ToString) -> SourceInfoError {
255 SourceInfoError {
256 error_type: SourceInfoErrorType::LintWarning,
257 line,
258 message: message.to_string(),
259 }
260}
261
262/// Creates a [`SourceInfoError`] for deprecation warnings.
263///
264/// Takes an optional `line` on which the issue occurred and a `message`.
265pub fn deprecation(line: Option<usize>, message: impl ToString) -> SourceInfoError {
266 SourceInfoError {
267 error_type: SourceInfoErrorType::DeprecationWarning,
268 line,
269 message: message.to_string(),
270 }
271}