1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/// PostgreSQL archive result type
pub type Result<T, E = Error> = core::result::Result<T, E>;

/// PostgreSQL archive errors
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// Asset not found
    #[error("asset not found")]
    AssetNotFound,
    /// Asset hash not found
    #[error("asset hash not found for asset '{0}'")]
    AssetHashNotFound(String),
    /// Error when the hash of the archive does not match the expected hash
    #[error("Archive hash [{archive_hash}] does not match expected hash [{hash}]")]
    ArchiveHashMismatch { archive_hash: String, hash: String },
    /// Invalid version
    #[error("version '{0}' is invalid")]
    InvalidVersion(String),
    /// IO error
    #[error(transparent)]
    IoError(anyhow::Error),
    /// Parse error
    #[error(transparent)]
    ParseError(anyhow::Error),
    /// Poisoned lock
    #[error("poisoned lock '{0}'")]
    PoisonedLock(String),
    /// Repository failure
    #[error("{0}")]
    RepositoryFailure(String),
    /// Unexpected error
    #[error("{0}")]
    Unexpected(String),
    /// Unsupported extractor
    #[error("unsupported extractor for '{0}'")]
    UnsupportedExtractor(String),
    /// Unsupported hasher
    #[error("unsupported hasher for '{0}'")]
    UnsupportedHasher(String),
    /// Unsupported hasher
    #[error("unsupported matcher for '{0}'")]
    UnsupportedMatcher(String),
    /// Unsupported repository
    #[error("unsupported repository for '{0}'")]
    UnsupportedRepository(String),
    /// Version not found
    #[error("version not found for '{0}'")]
    VersionNotFound(String),
}

/// Converts a [`regex::Error`] into an [`ParseError`](Error::ParseError)
impl From<regex::Error> for Error {
    fn from(error: regex::Error) -> Self {
        Error::ParseError(error.into())
    }
}

/// Converts a [`reqwest::Error`] into an [`IoError`](Error::IoError)
impl From<reqwest::Error> for Error {
    fn from(error: reqwest::Error) -> Self {
        Error::IoError(error.into())
    }
}

/// Converts a [`reqwest_middleware::Error`] into an [`IoError`](Error::IoError)
impl From<reqwest_middleware::Error> for Error {
    fn from(error: reqwest_middleware::Error) -> Self {
        Error::IoError(error.into())
    }
}

/// Converts a [`std::io::Error`] into an [`IoError`](Error::IoError)
impl From<std::io::Error> for Error {
    fn from(error: std::io::Error) -> Self {
        Error::IoError(error.into())
    }
}

/// Converts a [`std::time::SystemTimeError`] into an [`IoError`](Error::IoError)
impl From<std::time::SystemTimeError> for Error {
    fn from(error: std::time::SystemTimeError) -> Self {
        Error::IoError(error.into())
    }
}

/// Converts a [`std::num::ParseIntError`] into an [`ParseError`](Error::ParseError)
impl From<std::num::ParseIntError> for Error {
    fn from(error: std::num::ParseIntError) -> Self {
        Error::ParseError(error.into())
    }
}

/// Converts a [`semver::Error`] into an [`ParseError`](Error::ParseError)
impl From<semver::Error> for Error {
    fn from(error: semver::Error) -> Self {
        Error::IoError(error.into())
    }
}

/// Converts a [`std::path::StripPrefixError`] into an [`ParseError`](Error::ParseError)
impl From<std::path::StripPrefixError> for Error {
    fn from(error: std::path::StripPrefixError) -> Self {
        Error::ParseError(error.into())
    }
}

/// Converts a [`anyhow::Error`] into an [`Unexpected`](Error::Unexpected)
impl From<anyhow::Error> for Error {
    fn from(error: anyhow::Error) -> Self {
        Error::Unexpected(error.to_string())
    }
}

/// Converts a [`url::ParseError`] into an [`ParseError`](Error::ParseError)
impl From<url::ParseError> for Error {
    fn from(error: url::ParseError) -> Self {
        Error::ParseError(error.into())
    }
}

/// These are relatively low value tests; they are here to reduce the coverage gap and
/// ensure that the error conversions are working as expected.
#[cfg(test)]
mod test {
    use super::*;
    use anyhow::anyhow;
    use semver::VersionReq;
    use std::ops::Add;
    use std::path::PathBuf;
    use std::str::FromStr;
    use std::time::{Duration, SystemTime};

    #[test]
    fn test_from_regex_error() {
        let regex_error = regex::Error::Syntax("test".to_string());
        let error = Error::from(regex_error);
        assert_eq!(error.to_string(), "test");
    }

    #[tokio::test]
    async fn test_from_reqwest_error() {
        let result = reqwest::get("https://a.com").await;
        assert!(result.is_err());
        if let Err(error) = result {
            let error = Error::from(error);
            assert!(error.to_string().contains("https://a.com"));
        }
    }

    #[tokio::test]
    async fn test_from_reqwest_middeleware_error() {
        let reqwest_middleware_error =
            reqwest_middleware::Error::Middleware(anyhow!("middleware error: test"));
        let error = Error::from(reqwest_middleware_error);
        assert!(error.to_string().contains("middleware error: test"));
    }

    #[test]
    fn test_from_io_error() {
        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
        let error = Error::from(io_error);
        assert_eq!(error.to_string(), "test");
    }

    #[test]
    fn test_from_parse_int_error() {
        let parse_int_error = u64::from_str("test").expect_err("parse int error");
        let error = Error::from(parse_int_error);
        assert_eq!(error.to_string(), "invalid digit found in string");
    }

    #[test]
    fn test_from_semver_error() {
        let semver_error = VersionReq::parse("foo").expect_err("semver error");
        let error = Error::from(semver_error);
        assert_eq!(
            error.to_string(),
            "unexpected character 'f' while parsing major version number"
        );
    }

    #[test]
    fn test_from_strip_prefix_error() {
        let path = PathBuf::from("test");
        let stip_prefix_error = path.strip_prefix("foo").expect_err("strip prefix error");
        let error = Error::from(stip_prefix_error);
        assert_eq!(error.to_string(), "prefix not found");
    }

    #[test]
    fn test_from_system_time_error() {
        let future_time = SystemTime::now().add(Duration::from_secs(300));
        let system_time_error = SystemTime::now()
            .duration_since(future_time)
            .expect_err("system time error");
        let error = Error::from(system_time_error);
        assert_eq!(
            error.to_string(),
            "second time provided was later than self"
        );
    }

    #[test]
    fn test_from_anyhow_error() {
        let anyhow_error = anyhow::Error::msg("test");
        let error = Error::from(anyhow_error);
        assert_eq!(error.to_string(), "test");
    }

    #[test]
    fn test_from_url_parse_error() {
        let parse_error = url::ParseError::EmptyHost;
        let error = Error::from(parse_error);
        assert_eq!(error.to_string(), "empty host");
    }
}