impl From<&reqwest::Error> for super::Error {
fn from(err: &reqwest::Error) -> Self {
use std::error::Error;
let mut source = err.source();
while let Some(err) = source {
if let Some(hyper_err) = err.downcast_ref::<hyper::Error>() {
return hyper_err.into();
}
source = err.source();
}
super::Error::new("unknown", err)
}
}
impl From<&hyper::Error> for super::Error {
fn from(err: &hyper::Error) -> Self {
use std::error::Error;
let mut source = err.source();
while let Some(err) = source {
if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
return io_err.into();
}
source = err.source();
}
if err.is_connect() {
if let Some(source) = err.source() {
match source.to_string() {
s if s.contains("Hostname mismatch") => {
super::Error::new("tls", "cert.name_invalid")
}
s if s.contains("certificate has expired") => {
super::Error::new("tls", "cert.date_invalid")
}
s if s.contains("self signed certificate in certificate chain") => {
super::Error::new("tls", "cert.authority_invalid")
}
_ => super::Error::new("tcp", "failed"),
}
} else {
super::Error::new("tcp", "failed")
}
} else if err.is_parse() {
super::Error::new("http", "response.invalid")
} else if err.is_user() {
super::Error::new("http", "protocol.error")
} else if err.is_incomplete_message() {
super::Error::new("tcp", "closed")
} else if err.is_body_write_aborted() {
super::Error::new("abandoned", "")
} else if err.is_timeout() {
super::Error::new("tcp", "timed_out")
} else if err.is_closed() {
super::Error::new("tcp", "reset")
} else if err.is_canceled() {
super::Error::new("tcp", "aborted")
} else {
super::Error::new("unknown", err)
}
}
}
#[cfg(test)]
mod tests {
fn clients() -> Vec<reqwest::Client> {
vec![
reqwest::ClientBuilder::new()
.use_native_tls()
.build()
.unwrap(),
reqwest::ClientBuilder::new()
.use_rustls_tls()
.build()
.unwrap(),
]
}
#[tokio::test]
async fn no_dns() {
for client in clients() {
let response = client.get("http://invalid.").send().await.unwrap_err();
let nel_err: crate::error::Error = (&response).into();
assert_eq!(nel_err.to_string(), "dns.name_not_resolved");
assert_eq!(nel_err.phase(), "dns");
}
}
#[tokio::test]
async fn expired_cert() {
for client in clients() {
let response = client
.get("https://expired.badssl.com/")
.send()
.await
.unwrap_err();
let nel_err: crate::error::Error = (&response).into();
assert_eq!(nel_err.to_string(), "tls.cert.date_invalid");
assert_eq!(nel_err.phase(), "connection");
}
}
#[tokio::test]
async fn untrusted_cert() {
for client in clients() {
let response = client
.get("https://untrusted-root.badssl.com/")
.send()
.await
.unwrap_err();
let nel_err: crate::error::Error = (&response).into();
assert_eq!(nel_err.to_string(), "tls.cert.authority_invalid");
assert_eq!(nel_err.phase(), "connection");
}
}
#[tokio::test]
async fn invalid_name() {
for client in clients() {
let response = client
.get("https://wrong.host.badssl.com/")
.send()
.await
.unwrap_err();
let nel_err: crate::error::Error = (&response).into();
assert_eq!(nel_err.to_string(), "tls.cert.name_invalid");
assert_eq!(nel_err.phase(), "connection");
}
}
}