use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NetworkKind {
Connect,
Body,
Request,
Unknown,
}
impl std::fmt::Display for NetworkKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Connect => "connect",
Self::Body => "body",
Self::Request => "request",
Self::Unknown => "unknown",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseKind {
Json,
Xml,
}
impl std::fmt::Display for ParseKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Json => "json",
Self::Xml => "xml",
})
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("request timed out")]
Timeout,
#[error("network error: {0}")]
Network(NetworkKind),
#[error("http status {0}")]
HttpStatus(u16),
#[error("parse error: {0}")]
Parse(ParseKind),
#[error("failed to build HTTP client: {0}")]
HttpClient(String),
#[error("no results")]
NoResults { query: String },
#[error("location detection failed")]
LocationDetection,
#[error("D-Bus error: {0}")]
Dbus(String),
}
impl From<reqwest::Error> for Error {
fn from(e: reqwest::Error) -> Self {
if e.is_timeout() {
return Error::Timeout;
}
if let Some(status) = e.status() {
return Error::HttpStatus(status.as_u16());
}
if e.is_decode() {
return Error::Parse(ParseKind::Json);
}
if e.is_connect() {
return Error::Network(NetworkKind::Connect);
}
if e.is_body() {
return Error::Network(NetworkKind::Body);
}
if e.is_request() {
return Error::Network(NetworkKind::Request);
}
Error::Network(NetworkKind::Unknown)
}
}
impl From<quick_xml::DeError> for Error {
fn from(_: quick_xml::DeError) -> Self {
Error::Parse(ParseKind::Xml)
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
use crate::wire::WireError;
#[test]
fn network_kind_display_connect() {
assert_eq!(format!("{}", NetworkKind::Connect), "connect");
}
#[test]
fn network_kind_display_body() {
assert_eq!(format!("{}", NetworkKind::Body), "body");
}
#[test]
fn network_kind_display_request() {
assert_eq!(format!("{}", NetworkKind::Request), "request");
}
#[test]
fn network_kind_display_unknown() {
assert_eq!(format!("{}", NetworkKind::Unknown), "unknown");
}
#[test]
fn parse_kind_display_json() {
assert_eq!(format!("{}", ParseKind::Json), "json");
}
#[test]
fn parse_kind_display_xml() {
assert_eq!(format!("{}", ParseKind::Xml), "xml");
}
#[test]
fn error_display_timeout() {
assert_eq!(format!("{}", Error::Timeout), "request timed out");
}
#[test]
fn error_display_network_connect() {
assert_eq!(
format!("{}", Error::Network(NetworkKind::Connect)),
"network error: connect"
);
}
#[test]
fn error_display_http_status() {
assert_eq!(format!("{}", Error::HttpStatus(429)), "http status 429");
}
#[test]
fn error_display_parse_xml() {
assert_eq!(
format!("{}", Error::Parse(ParseKind::Xml)),
"parse error: xml"
);
}
#[test]
fn error_display_http_client() {
assert_eq!(
format!(
"{}",
Error::HttpClient("tls backend not initialized".to_string())
),
"failed to build HTTP client: tls backend not initialized"
);
}
#[test]
fn error_display_no_results() {
assert_eq!(
format!(
"{}",
Error::NoResults {
query: "Portlandia".to_string()
}
),
"no results"
);
}
#[test]
fn error_display_location_detection() {
assert_eq!(
format!("{}", Error::LocationDetection),
"location detection failed"
);
}
#[test]
fn error_display_dbus() {
assert_eq!(
format!("{}", Error::Dbus("name lost".to_string())),
"D-Bus error: name lost"
);
}
#[test]
fn error_no_results_display_and_wire_error_never_leak_query() {
let query = "SENTINEL_QUERY_04_04_DO_NOT_LEAK";
let e = Error::NoResults {
query: query.to_string(),
};
assert!(!format!("{}", e).contains(query));
assert!(!WireError::from(&e).message.contains(query));
}
#[test]
fn from_quick_xml_de_error_maps_to_parse_xml() {
let de_err = quick_xml::de::from_str::<i32>("<not valid xml").unwrap_err();
let e: Error = de_err.into();
assert!(matches!(e, Error::Parse(ParseKind::Xml)));
}
#[test]
fn from_reqwest_error_empty_url_maps_to_network_request() {
let req_err = reqwest::Client::new().get("").build().unwrap_err();
let e: Error = req_err.into();
assert!(matches!(
e,
Error::Network(NetworkKind::Request) | Error::Network(NetworkKind::Unknown)
));
}
}