use serde::{Deserialize, Serialize};
pub mod class {
pub const CF_1042: &str = "cf_1042";
pub const DNS: &str = "dns";
pub const TLS: &str = "tls";
pub const CONNECT_TIMEOUT: &str = "connect_timeout";
}
pub fn of_answer(status: u16, body: &str) -> Option<&'static str> {
(status == 404 && body.to_ascii_lowercase().contains("error code: 1042"))
.then_some(class::CF_1042)
}
pub fn of_transport(message: &str) -> Option<&'static str> {
let m = message.to_ascii_lowercase();
let any = |words: &[&str]| words.iter().any(|w| m.contains(w));
if any(&[
"dns",
"getaddrinfo",
"enotfound",
"name not resolved",
"could not resolve",
]) {
Some(class::DNS)
} else if any(&["tls", "ssl", "certificate", "handshake"]) {
Some(class::TLS)
} else if any(&["etimedout", "connect timeout", "connection timed out"])
|| (m.contains("connect") && any(&["timed out", "timeout"]))
{
Some(class::CONNECT_TIMEOUT)
} else {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RouteFaultSeen {
pub peer: String,
pub class: String,
}
impl RouteFaultSeen {
pub fn label(&self) -> String {
format!("{} route_fault:{}", self.peer, self.class)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn route_057_the_1042_shape_is_the_platforms_and_nothing_else_is() {
assert_eq!(of_answer(404, "error code: 1042"), Some(class::CF_1042));
assert_eq!(of_answer(404, "Error Code: 1042\n"), Some(class::CF_1042));
for (status, body) in [
(404, "Not Found"),
(404, r#"{"status":"error","message":"Route not found"}"#),
(500, "error code: 1042"),
(502, "Bad Gateway"),
(503, "error code: 1102"),
(400, "error code: 1042"),
] {
assert_eq!(of_answer(status, body), None, "{status} {body}");
}
}
#[test]
fn route_057_a_transport_failure_is_named_only_by_a_route_word() {
for (text, want) in [
("Fetch to https://p/x failed: DNS lookup failed", class::DNS),
("getaddrinfo ENOTFOUND peer.example", class::DNS),
("TLS handshake failed", class::TLS),
("Fetch failed: certificate has expired", class::TLS),
("connect ETIMEDOUT 10.0.0.1:443", class::CONNECT_TIMEOUT),
("Fetch failed: connection timed out", class::CONNECT_TIMEOUT),
] {
assert_eq!(of_transport(text), Some(want), "{text}");
}
for text in [
"Fetch to https://p/x failed: Network connection lost",
"connection reset by peer",
"internal error",
] {
assert_eq!(of_transport(text), None, "{text}");
}
}
}