Skip to main content

alpm_types/
error.rs

1use std::path::PathBuf;
2
3use alpm_parsers::error::{Input, ParseStack};
4use fluent_i18n::t;
5use winnow::error::ParseError;
6
7use crate::Architecture;
8
9/// The library's error type
10///
11/// These errors are usually parsing errors and they each contain a context
12/// about why the error has occurred and the value that caused the error.
13///
14/// The original error is also included in the variants that have the `source` field.
15/// You can access it using the `source()` method.
16/// See [Error::source](https://doc.rust-lang.org/std/error/trait.Error.html#method.source) for
17/// more information.
18#[derive(Debug, thiserror::Error, PartialEq)]
19pub enum Error {
20    /// Combination of architectures that is invalid.
21    #[error("{msg}", msg = t!("error-invalid-architectures", {
22        "architectures" => format!("{architectures:?}"),
23        "context" => context
24    }))]
25    InvalidArchitectures {
26        /// The invalid architectures combination.
27        architectures: Vec<Architecture>,
28        /// The reason why the architectures are invalid.
29        context: &'static str,
30    },
31
32    /// An invalid integer
33    #[error("{msg}", msg = t!("error-invalid-integer", { "kind" => format!("{kind:?}") }))]
34    InvalidInteger {
35        /// The reason for the invalid integer.
36        kind: std::num::IntErrorKind,
37    },
38
39    /// An invalid enum variant
40    #[error("{msg}", msg = t!("error-invalid-variant", { "error" => .0.to_string() }))]
41    InvalidVariant(#[from] strum::ParseError),
42
43    /// An invalid email address
44    #[error("{msg}", msg = t!("error-invalid-email", { "error" => .0.to_string() }))]
45    InvalidEmail(#[from] email_address::Error),
46
47    /// An invalid URL
48    #[error("{msg}", msg = t!("error-invalid-url", { "error" => .0.to_string() }))]
49    InvalidUrl(#[from] url::ParseError),
50
51    /// An invalid license
52    #[error("{msg}", msg = t!("error-invalid-license", { "error" => .0.to_string() }))]
53    InvalidLicense(#[from] spdx::ParseError),
54
55    /// An invalid semantic version string
56    ///
57    /// This error occurs when a semantic version cannot be parsed from a string.
58    /// We cannot use `#[source] semver::Error` here because it does not implement `PartialEq`.
59    /// See: <https://github.com/dtolnay/semver/issues/326>
60    ///
61    /// TODO: Use the error source when the issue above is resolved.
62    #[error("{msg}", msg = t!("error-invalid-semver", { "kind" => kind }))]
63    InvalidSemver {
64        /// The reason for the invalid semantic version.
65        kind: String,
66    },
67
68    /// Value contains invalid characters
69    #[error("{msg}", msg = t!("error-invalid-chars", { "invalid_char" => invalid_char.to_string() }))]
70    ValueContainsInvalidChars {
71        /// The invalid character
72        invalid_char: char,
73    },
74
75    /// Value length is incorrect
76    #[error("{msg}", msg = t!("error-incorrect-length", { "length" => length, "expected" => expected }))]
77    IncorrectLength {
78        /// The incorrect length.
79        length: usize,
80        /// The expected length.
81        expected: usize,
82    },
83
84    /// Value is missing a delimiter character
85    #[error("{msg}", msg = t!("error-delimiter-not-found", { "delimiter" => delimiter.to_string() }))]
86    DelimiterNotFound {
87        /// The required delimiter.
88        delimiter: char,
89    },
90
91    /// Value does not match the restrictions
92    #[error("{msg}", msg = t!("error-restrictions-not-met", {
93        "restrictions" => format!("{restrictions:?}")
94    }))]
95    ValueDoesNotMatchRestrictions {
96        /// The list of restrictions that cannot be met.
97        restrictions: Vec<String>,
98    },
99
100    /// A validation regex does not match the value
101    #[error("{msg}", msg = t!("error-regex-mismatch", {
102        "value" => value,
103        "regex_type" => regex_type,
104        "regex" => regex
105    }))]
106    RegexDoesNotMatch {
107        /// The value that does not match.
108        value: String,
109        /// The type of regular expression applied to the `value`.
110        regex_type: String,
111        /// The regular expression applied to the `value`.
112        regex: String,
113    },
114
115    /// A winnow parser for a type didn't work and produced an error.
116    #[error("{msg}", msg = t!("error-parse", { "error" => .0 }))]
117    ParseError(String),
118
119    /// Missing field in a value
120    #[error("{msg}", msg = t!("error-missing-component", { "component" => component }))]
121    MissingComponent {
122        /// The component that is missing.
123        component: &'static str,
124    },
125
126    /// An invalid absolute path (i.e. does not start with a `/`)
127    #[error("{msg}", msg = t!("error-path-not-absolute", { "path" => .0 }))]
128    PathNotAbsolute(PathBuf),
129
130    /// An invalid relative path (i.e. starts with a `/`)
131    #[error("{msg}", msg = t!("error-path-not-relative", { "path" => .0 }))]
132    PathNotRelative(PathBuf),
133
134    /// An invalid relative sibling path (i.e. starts with a `./` or contains directory)
135    #[error("{msg}", msg = t!("error-path-not-sibling", { "path" => .0 }))]
136    PathNotSibling(PathBuf),
137
138    /// Expected a file, but got a directory
139    #[error("{msg}", msg = t!("error-path-not-file", { "path" => .0 }))]
140    PathIsNotAFile(PathBuf),
141
142    /// File name contains invalid characters
143    #[error("{msg}", msg = t!("error-filename-invalid-chars", {
144        "path" => .0,
145        "invalid_char" => .1.to_string()
146    }))]
147    FileNameContainsInvalidChars(PathBuf, char),
148
149    /// File name is empty
150    #[error("{msg}", msg = t!("error-filename-empty"))]
151    FileNameIsEmpty,
152
153    /// A deprecated license
154    #[error("{msg}", msg = t!("error-deprecated-license", { "license" => .0 }))]
155    DeprecatedLicense(String),
156
157    /// A component is invalid and cannot be used.
158    #[error("{msg}", msg = t!("error-invalid-component", {
159        "component" => component,
160        "context" => context
161    }))]
162    InvalidComponent {
163        /// The invalid component.
164        component: &'static str,
165        /// The context in which the error occurs.
166        ///
167        /// This is meant to complete the sentence
168        /// "Invalid component {component} encountered while ".
169        context: String,
170    },
171
172    /// An invalid OpenPGP v4 fingerprint
173    #[error("{msg}", msg = t!("error-invalid-pgp-fingerprint"))]
174    InvalidOpenPGPv4Fingerprint,
175
176    /// An invalid OpenPGP key ID
177    #[error("{msg}", msg = t!("error-invalid-pgp-keyid", { "keyid" => .0 }))]
178    InvalidOpenPGPKeyId(String),
179
180    /// An invalid OpenPGP signature
181    #[error("{msg}", msg = t!("error-invalid-base64-encoding", { "expected_item" => expected_item }))]
182    InvalidBase64Encoding {
183        /// The expected item that could not be decoded.
184        expected_item: String,
185    },
186
187    /// An invalid shared object name (v1)
188    #[error("{msg}", msg = t!("error-invalid-soname-v1", { "name" => .0 }))]
189    InvalidSonameV1(&'static str),
190
191    /// A package data error.
192    #[error("{msg}", msg = t!("error-package", { "error" => .0.to_string() }))]
193    Package(#[from] crate::PackageError),
194
195    /// A string represents an unknown compression algorithm file extension.
196    #[error("{msg}", msg = t!("error-unknown-compression", { "value" => value }))]
197    UnknownCompressionAlgorithmFileExtension {
198        /// A string representing an unknown compression algorithm file extension.
199        value: String,
200    },
201
202    /// A string represents an unknown file type identifier.
203    #[error("{msg}", msg = t!("error-unknown-filetype", { "value" => value }))]
204    UnknownFileTypeIdentifier {
205        /// A string representing an unknown file type identifier.
206        value: String,
207    },
208}
209
210impl From<std::num::ParseIntError> for crate::error::Error {
211    /// Converts a [`std::num::ParseIntError`] into an [`Error::InvalidInteger`].
212    fn from(e: std::num::ParseIntError) -> Self {
213        Self::InvalidInteger { kind: *e.kind() }
214    }
215}
216
217impl<'a> From<ParseError<Input<'a>, ParseStack<'a>>> for crate::error::Error {
218    /// Converts a [`ParseError`] into an [`Error::ParseError`].
219    fn from(value: ParseError<Input<'a>, ParseStack<'a>>) -> Self {
220        // Only take the **inner** parser error of our own ParseStack error type.
221        // We don't want to hit the `Display` imple of `ParseError`.
222        Self::ParseError(value.into_inner().to_string())
223    }
224}
225
226#[cfg(test)]
227mod tests {
228    use std::num::IntErrorKind;
229
230    use rstest::rstest;
231
232    use super::*;
233
234    #[rstest]
235    #[case(
236        "Invalid integer (caused by InvalidDigit)",
237        Error::InvalidInteger {
238            kind: IntErrorKind::InvalidDigit
239        }
240    )]
241    #[case(
242        "Invalid integer (caused by InvalidDigit)",
243        Error::InvalidInteger {
244            kind: IntErrorKind::InvalidDigit
245        }
246    )]
247    #[case(
248        "Invalid integer (caused by PosOverflow)",
249        Error::InvalidInteger {
250            kind: IntErrorKind::PosOverflow
251        }
252    )]
253    #[allow(deprecated)]
254    #[case(
255        "Invalid integer (caused by InvalidDigit)",
256        Error::InvalidInteger {
257            kind: IntErrorKind::InvalidDigit
258        }
259    )]
260    #[case(
261        "Invalid e-mail (Missing separator character '@'.)",
262        email_address::Error::MissingSeparator.into()
263    )]
264    #[case(
265        "Invalid integer (caused by InvalidDigit)",
266        Error::InvalidInteger {
267            kind: IntErrorKind::InvalidDigit
268        }
269    )]
270    fn error_format_string(#[case] error_str: &str, #[case] error: Error) {
271        assert_eq!(error_str, format!("{error}"));
272    }
273}