Skip to main content

alpm_types/
error.rs

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