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