alpm_types/
error.rs

1use std::path::PathBuf;
2
3/// The library's error type
4///
5/// These errors are usually parsing errors and they each contain a context
6/// about why the error has occurred and the value that caused the error.
7///
8/// The original error is also included in the variants that have the `source` field.
9/// You can access it using the `source()` method.
10/// See [Error::source](https://doc.rust-lang.org/std/error/trait.Error.html#method.source) for
11/// more information.
12#[derive(Debug, thiserror::Error, PartialEq)]
13#[allow(missing_docs)]
14pub enum Error {
15    /// An invalid integer
16    #[error("Invalid integer (caused by {kind:?})")]
17    InvalidInteger { kind: std::num::IntErrorKind },
18
19    /// An invalid enum variant
20    #[error("Invalid variant ({0})")]
21    InvalidVariant(#[from] strum::ParseError),
22
23    /// An invalid email address
24    #[error("Invalid e-mail ({0})")]
25    InvalidEmail(#[from] email_address::Error),
26
27    /// An invalid URL
28    #[error("Invalid URL ({0})")]
29    InvalidUrl(#[from] url::ParseError),
30
31    /// An invalid license
32    #[error("Invalid license ({0})")]
33    InvalidLicense(#[from] spdx::ParseError),
34
35    /// An invalid semantic version string
36    ///
37    /// This error occurs when a semantic version cannot be parsed from a string
38    /// We cannot use `#[source] semver::Error` here because it does not implement `PartialEq`.
39    /// See: <https://github.com/dtolnay/semver/issues/326>
40    ///
41    /// TODO: Use the error source when the issue above is resolved.
42    #[error("Invalid semver ({kind})")]
43    InvalidSemver { kind: String },
44
45    /// Value contains invalid characters
46    #[error("Value contains invalid characters: {invalid_char:?}")]
47    ValueContainsInvalidChars { invalid_char: char },
48
49    /// Value length is incorrect
50    #[error("Incorrect length, got {length} expected {expected}")]
51    IncorrectLength { length: usize, expected: usize },
52
53    /// Value is missing a delimiter character
54    #[error("Value is missing the required delimiter: {delimiter}")]
55    DelimiterNotFound { delimiter: char },
56
57    /// Value does not match the restrictions
58    #[error("Does not match the restrictions ({restrictions:?})")]
59    ValueDoesNotMatchRestrictions { restrictions: Vec<String> },
60
61    /// A validation regex does not match the value
62    #[error("Value '{value}' does not match the '{regex_type}' regex: {regex}")]
63    RegexDoesNotMatch {
64        value: String,
65        regex_type: String,
66        regex: String,
67    },
68
69    /// A winnow parser for a type didn't work and produced an error.
70    #[error("Parser failed with the following error:\n{0}")]
71    ParseError(String),
72
73    /// Missing field in a value
74    #[error("Missing component: {component}")]
75    MissingComponent { component: &'static str },
76
77    /// An invalid absolute path (i.e. does not start with a `/`)
78    #[error("The path is not absolute: {0}")]
79    PathNotAbsolute(PathBuf),
80
81    /// An invalid relative path (i.e. starts with a `/`)
82    #[error("The path is not relative: {0}")]
83    PathNotRelative(PathBuf),
84
85    /// File name contains invalid characters
86    #[error("File name ({0}) contains invalid characters: {1:?}")]
87    FileNameContainsInvalidChars(PathBuf, char),
88
89    /// File name is empty
90    #[error("File name is empty")]
91    FileNameIsEmpty,
92
93    /// A deprecated license
94    #[error("Deprecated license: {0}")]
95    DeprecatedLicense(String),
96
97    /// An invalid OpenPGP v4 fingerprint
98    #[error("Invalid OpenPGP v4 fingerprint, only 40 uppercase hexadecimal characters are allowed")]
99    InvalidOpenPGPv4Fingerprint,
100
101    /// An invalid OpenPGP key ID
102    #[error("The string is not a valid OpenPGP key ID: {0}, must be 16 hexadecimal characters")]
103    InvalidOpenPGPKeyId(String),
104
105    /// An invalid shared object name (v1)
106    #[error("Invalid shared object name (v1): {0}")]
107    InvalidSonameV1(&'static str),
108
109    /// A package data error.
110    #[error("Package error: {0}")]
111    Package(#[from] crate::PackageError),
112
113    /// A string represents an unknown compression algorithm file extension.
114    #[error("Unknown compression algorithm file extension: {value:?}")]
115    UnknownCompressionAlgorithmFileExtension {
116        /// A String representing an unknown compression algorithm file extension.
117        value: String,
118    },
119
120    /// A string represents an unknown file type identifier.
121    #[error("Unknown file type identifier: {value:?}")]
122    UnknownFileTypeIdentifier {
123        /// A String representing an unknown file type identifier.
124        value: String,
125    },
126}
127
128impl From<std::num::ParseIntError> for crate::error::Error {
129    /// Converts a [`std::num::ParseIntError`] into an [`Error::InvalidInteger`].
130    fn from(e: std::num::ParseIntError) -> Self {
131        Self::InvalidInteger {
132            kind: e.kind().clone(),
133        }
134    }
135}
136
137impl<'a> From<winnow::error::ParseError<&'a str, winnow::error::ContextError>>
138    for crate::error::Error
139{
140    /// Converts a [`winnow::error::ParseError`] into an [`Error::ParseError`].
141    fn from(value: winnow::error::ParseError<&'a str, winnow::error::ContextError>) -> Self {
142        Self::ParseError(value.to_string())
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use std::num::IntErrorKind;
149
150    use rstest::rstest;
151
152    use super::*;
153    use crate::openpgp::PACKAGER_REGEX;
154
155    #[rstest]
156    #[case(
157        "Invalid integer (caused by InvalidDigit)",
158        Error::InvalidInteger {
159            kind: IntErrorKind::InvalidDigit
160        }
161    )]
162    #[case(
163        "Invalid integer (caused by InvalidDigit)",
164        Error::InvalidInteger {
165            kind: IntErrorKind::InvalidDigit
166        }
167    )]
168    #[case(
169        "Invalid integer (caused by PosOverflow)",
170        Error::InvalidInteger {
171            kind: IntErrorKind::PosOverflow
172        }
173    )]
174    #[allow(deprecated)]
175    #[case(
176        "Invalid integer (caused by InvalidDigit)",
177        Error::InvalidInteger {
178            kind: IntErrorKind::InvalidDigit
179        }
180    )]
181    #[case(
182        "Value '€i²' does not match the 'packager' regex: ^(?P<name>[\\w\\s\\-().]+) <(?P<email>.*)>$",
183        Error::RegexDoesNotMatch {
184            value: "€i²".to_string(),
185            regex_type: "packager".to_string(),
186            regex: PACKAGER_REGEX.to_string(),
187        }
188    )]
189    #[case(
190        "Invalid e-mail (Missing separator character '@'.)",
191        email_address::Error::MissingSeparator.into()
192    )]
193    #[case(
194        "Invalid integer (caused by InvalidDigit)",
195        Error::InvalidInteger {
196            kind: IntErrorKind::InvalidDigit
197        }
198    )]
199    fn error_format_string(#[case] error_str: &str, #[case] error: Error) {
200        assert_eq!(error_str, format!("{}", error));
201    }
202}