alpm_types/
error.rs

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