Skip to main content

apt_sources/
error.rs

1//! A module for handling errors in `apt-sources` crate of `deb822-rs` project.
2//! It intends to address error handling in meaningful manner, less vague than just passing
3//! `String` as error.
4
5/// Errors for APT sources parsing and conversion to/from [`super::Repository`] or [`super::legacy::LegacyRepository`]
6#[derive(Debug)]
7pub enum RepositoryError {
8    /// Invalid repository format
9    InvalidFormat,
10    /// Invalid repository URI
11    InvalidUri,
12    /// Missing repository URI - mandatory
13    MissingUri,
14    /// Unrecognized repository type
15    InvalidType,
16    /// The `Signed-By` field is incorrect
17    InvalidSignature,
18    /// The Yes/No/Force field has invalid/unexpected value
19    YesNoForceFieldInvalid,
20    /// The Yes/No field has invalid/unexpected value
21    YesNoFieldInvalid,
22    /// The field in the parsed data is not recognized (check `man sources.list`)
23    UnrecognizedFieldName(String),
24    /// Errors in lossy serializer or deserializer
25    Lossy(deb822_fast::Error),
26    /// I/O Error
27    Io(std::io::Error),
28    /// URL Error
29    Url(url::ParseError),
30}
31
32/// Errors that can occur when loading repositories from directories
33#[derive(Debug)]
34pub enum LoadError {
35    /// Failed to read a file
36    Io {
37        /// The path that failed to be read
38        path: std::path::PathBuf,
39        /// The underlying I/O error
40        error: std::io::Error,
41    },
42    /// Failed to parse a file
43    Parse {
44        /// The path that failed to be parsed
45        path: std::path::PathBuf,
46        /// The parsing error message
47        error: String,
48    },
49    /// Failed to read directory entries
50    DirectoryRead {
51        /// The directory path that failed to be read
52        path: std::path::PathBuf,
53        /// The underlying I/O error
54        error: std::io::Error,
55    },
56    #[cfg(not(feature = "legacy"))]
57    /// The support for `legacy` format hadn't been enabled at build time
58    UnsupportedLegacyFormat,
59}
60
61impl From<std::io::Error> for RepositoryError {
62    fn from(e: std::io::Error) -> Self {
63        Self::Io(e)
64    }
65}
66
67impl From<url::ParseError> for RepositoryError {
68    fn from(e: url::ParseError) -> Self {
69        Self::Url(e)
70    }
71}
72
73impl std::fmt::Display for RepositoryError {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
75        // Spare longer messages to split lines by `rustfmt`
76        const YNFERRMSG: &str = "The field requiring only `Yes`/`No`/`Force` values is incorrect";
77        const YNERRMSG: &str = "The field requiring only `Yes`/`No` values is incorrect";
78        match self {
79            Self::InvalidFormat => write!(f, "Invalid repository format"),
80            Self::InvalidUri => write!(f, "Invalid repository URI"),
81            Self::MissingUri => write!(f, "Missing repository URI"),
82            Self::InvalidType => write!(f, "Invalid repository type"),
83            Self::InvalidSignature => write!(f, "The field `Signed-By` is incorrect"),
84            Self::YesNoForceFieldInvalid => f.write_str(YNFERRMSG),
85            Self::YesNoFieldInvalid => f.write_str(YNERRMSG),
86            Self::UnrecognizedFieldName(name) => write!(
87                f,
88                "Unrecognized field name: {name} (check `man sources.list`)"
89            ),
90            Self::Lossy(e) => write!(f, "Lossy parser error: {e}"),
91            Self::Io(e) => write!(f, "IO error: {e}"),
92            Self::Url(e) => write!(f, "URL parse error: {e}"),
93        }
94    }
95}
96
97impl std::fmt::Display for LoadError {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
99        match self {
100            Self::Io { path, error } => write!(f, "Failed to read {}: {}", path.display(), error),
101            Self::Parse { path, error } => {
102                write!(f, "Failed to parse {}: {}", path.display(), error)
103            }
104            Self::DirectoryRead { path, error } => {
105                write!(f, "Failed to read directory {}: {}", path.display(), error)
106            }
107            #[cfg(not(feature = "legacy"))]
108            Self::UnsupportedLegacyFormat => {
109                write!(
110                    f,
111                    "The support for `legacy` format hadn't been enabled at build time"
112                )
113            }
114        }
115    }
116}
117
118impl std::error::Error for RepositoryError {}
119impl std::error::Error for LoadError {}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_repository_error_display() {
127        // Test each error variant
128        assert_eq!(
129            RepositoryError::InvalidFormat.to_string(),
130            "Invalid repository format"
131        );
132        assert_eq!(
133            RepositoryError::InvalidUri.to_string(),
134            "Invalid repository URI"
135        );
136        assert_eq!(
137            RepositoryError::MissingUri.to_string(),
138            "Missing repository URI"
139        );
140        assert_eq!(
141            RepositoryError::InvalidType.to_string(),
142            "Invalid repository type"
143        );
144        assert_eq!(
145            RepositoryError::InvalidSignature.to_string(),
146            "The field `Signed-By` is incorrect"
147        );
148
149        // Test unrecognized field name includes the field name
150        assert_eq!(
151            RepositoryError::UnrecognizedFieldName("foo-bar".to_string()).to_string(),
152            "Unrecognized field name: foo-bar (check `man sources.list`)"
153        );
154
155        // Test lossy error
156        let lossy_err = deb822_fast::Error::UnexpectedEof;
157        let repo_err = RepositoryError::Lossy(lossy_err);
158        assert!(repo_err.to_string().contains("Lossy parser error:"));
159
160        // Test IO error
161        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
162        let repo_err = RepositoryError::from(io_err);
163        assert!(repo_err.to_string().contains("IO error:"));
164    }
165
166    #[test]
167    fn test_load_error_display() {
168        use std::path::PathBuf;
169
170        // Test IO error
171        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
172        let load_err = LoadError::Io {
173            path: PathBuf::from("/test/path"),
174            error: io_err,
175        };
176        assert!(load_err.to_string().contains("Failed to read /test/path"));
177        assert!(load_err.to_string().contains("file not found"));
178
179        // Test Parse error
180        let parse_err = LoadError::Parse {
181            path: PathBuf::from("/test/file.list"),
182            error: "Invalid format".to_string(),
183        };
184        assert!(parse_err
185            .to_string()
186            .contains("Failed to parse /test/file.list"));
187        assert!(parse_err.to_string().contains("Invalid format"));
188
189        // Test DirectoryRead error
190        let dir_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
191        let load_err = LoadError::DirectoryRead {
192            path: PathBuf::from("/test/dir"),
193            error: dir_err,
194        };
195        assert!(load_err
196            .to_string()
197            .contains("Failed to read directory /test/dir"));
198        assert!(load_err.to_string().contains("access denied"));
199    }
200}