use std::error::Error;
struct ErrorRule {
patterns: &'static [&'static str],
message: &'static str,
}
impl ErrorRule {
const fn new(patterns: &'static [&'static str], message: &'static str) -> Self {
Self { patterns, message }
}
fn matches(&self, text: &str) -> bool {
self.patterns.iter().any(|pattern| text.contains(pattern))
}
const fn message(&self) -> &'static str {
self.message
}
}
struct ErrorRules {
rules: Vec<ErrorRule>,
fallback: Option<String>,
}
impl ErrorRules {
const fn new() -> Self {
Self {
rules: Vec::new(),
fallback: None,
}
}
fn rule(mut self, patterns: &'static [&'static str], message: &'static str) -> Self {
self.rules.push(ErrorRule::new(patterns, message));
self
}
fn fallback(mut self, message: impl Into<String>) -> Self {
self.fallback = Some(message.into());
self
}
fn match_error(self, error_msg: &str) -> String {
for rule in &self.rules {
if rule.matches(error_msg) {
return rule.message().to_string();
}
}
self.fallback
.unwrap_or_else(|| format!("Unhandled error: {error_msg}"))
}
}
pub(crate) fn analyze_error_chain(error: &reqwest::Error) -> String {
if let Some(basic_message) = analyze_basic_reqwest_error(error) {
return basic_message;
}
if let Some(chain_message) = analyze_error_source_chain(error) {
return chain_message;
}
fallback_reqwest_analysis(error)
}
fn analyze_basic_reqwest_error(error: &reqwest::Error) -> Option<String> {
if error.is_timeout() {
return Some(
"Request timed out. Try increasing timeout or check server status".to_string(),
);
}
if error.is_redirect() {
return Some("Too many redirects - check for redirect loops".to_string());
}
if let Some(status) = error.status() {
let reason = status.canonical_reason().unwrap_or("Unknown");
return Some(format!(
"HTTP {status}: {reason} - check URL and server status",
));
}
None
}
fn analyze_error_source_chain(error: &reqwest::Error) -> Option<String> {
let mut source = error.source();
while let Some(err) = source {
if let Some(io_error) = err.downcast_ref::<std::io::Error>() {
return Some(analyze_io_error(io_error));
}
if let Some(hyper_error) = err.downcast_ref::<hyper::Error>() {
return Some(analyze_hyper_error(hyper_error));
}
if let Some(url_error) = err.downcast_ref::<url::ParseError>() {
return Some(analyze_url_parse_error(*url_error));
}
if let Some(generic_message) = analyze_generic_error_string(&err.to_string()) {
return Some(generic_message);
}
source = err.source();
}
None
}
fn analyze_io_error(io_error: &std::io::Error) -> String {
match io_error.kind() {
std::io::ErrorKind::ConnectionRefused => {
"Connection refused - server may be down or port blocked".to_string()
}
std::io::ErrorKind::TimedOut => {
"Request timed out. Try increasing timeout or check server status".to_string()
}
std::io::ErrorKind::NotFound => {
"DNS resolution failed - check hostname spelling".to_string()
}
std::io::ErrorKind::PermissionDenied => {
"Permission denied - check firewall or proxy settings".to_string()
}
std::io::ErrorKind::Other => analyze_io_other_error(io_error),
std::io::ErrorKind::NetworkUnreachable => {
"Network unreachable. Check internet connection or VPN settings".to_string()
}
std::io::ErrorKind::AddrNotAvailable => {
"Address not available. Check network interface configuration".to_string()
}
std::io::ErrorKind::AddrInUse => {
"Address already in use. Port conflict or service already running".to_string()
}
std::io::ErrorKind::BrokenPipe => {
"Connection broken. Server closed connection unexpectedly".to_string()
}
std::io::ErrorKind::InvalidData => {
"Invalid response data. Server sent malformed response".to_string()
}
std::io::ErrorKind::UnexpectedEof => {
"Connection closed unexpectedly. Server terminated early".to_string()
}
std::io::ErrorKind::Interrupted => {
"Request interrupted. Try again or check for system issues".to_string()
}
std::io::ErrorKind::Unsupported => {
"Operation not supported. Check protocol or server capabilities".to_string()
}
_ => {
let kind_name = format!("{:?}", io_error.kind());
match kind_name.as_str() {
"Uncategorized" => {
"Connection failed. Check network connectivity and firewall settings"
.to_string()
}
_ => {
format!("I/O error ({kind_name}). Check network connectivity and server status",)
}
}
}
}
}
fn analyze_io_other_error(io_error: &std::io::Error) -> String {
if let Some(inner) = io_error.get_ref() {
let inner_msg = inner.to_string();
if inner_msg.contains("certificate") {
return analyze_certificate_error(&inner_msg);
}
ErrorRules::new()
.rule(
&["failed to lookup address", "nodename nor servname"],
"DNS resolution failed. Check hostname and DNS settings",
)
.rule(
&["Temporary failure in name resolution"],
"DNS temporarily unavailable. Try again later",
)
.rule(
&["handshake"],
"TLS handshake failed. Check SSL/TLS configuration",
)
.fallback(format!("Network error: {inner_msg}"))
.match_error(&inner_msg)
} else {
"Connection failed. Check network connectivity and firewall settings".to_string()
}
}
fn analyze_certificate_error(error_msg: &str) -> String {
ErrorRules::new()
.rule(
&[
"expired",
"NotValidAtThisTime",
"certificate has expired",
"certificate is not valid on",
],
"SSL certificate expired. Site needs to renew certificate",
)
.rule(
&["hostname", "NotValidForName"],
"SSL certificate hostname mismatch. Check URL spelling",
)
.rule(
&["self signed", "UnknownIssuer", "not trusted"],
"SSL certificate not trusted. Use --insecure if site is trusted",
)
.rule(
&["verify failed"],
"SSL certificate verification failed. Check certificate validity",
)
.fallback("SSL certificate error. Check certificate validity")
.match_error(error_msg)
}
fn analyze_hyper_error(hyper_error: &hyper::Error) -> String {
if hyper_error.is_parse() {
if hyper_error.is_parse_status() {
return "Invalid HTTP status code from server".to_string();
}
return "Invalid HTTP response format. Server may be misconfigured".to_string();
}
if hyper_error.is_timeout() {
return "Request timed out. Try increasing timeout or check server status".to_string();
}
if hyper_error.is_user() {
if hyper_error.is_body_write_aborted() {
return "Request body upload was aborted".to_string();
}
return "Invalid request format. Check request parameters".to_string();
}
if hyper_error.is_canceled() {
return "Request was canceled".to_string();
}
if hyper_error.is_closed() {
return "Connection was closed unexpectedly".to_string();
}
if hyper_error.is_incomplete_message() {
return "Connection closed before response completed".to_string();
}
let hyper_msg = hyper_error.to_string();
ErrorRules::new()
.rule(
&["connection error"],
"Connection failed. Check network connectivity and firewall settings",
)
.rule(
&["http2 error"],
"HTTP/2 protocol error. Server may not support HTTP/2 properly",
)
.rule(
&["channel closed"],
"HTTP connection channel closed unexpectedly",
)
.rule(
&["operation was canceled"],
"HTTP operation was canceled before completion",
)
.rule(
&["message head is too large"],
"HTTP headers too large. Server response headers exceed limits",
)
.rule(
&["invalid content-length"],
"Invalid Content-Length header from server",
)
.fallback(format!("HTTP protocol error: {hyper_error}"))
.match_error(&hyper_msg)
}
fn analyze_url_parse_error(url_error: url::ParseError) -> String {
match url_error {
url::ParseError::EmptyHost => "Invalid URL: empty hostname".to_string(),
url::ParseError::InvalidDomainCharacter => {
"Invalid URL: invalid characters in domain".to_string()
}
url::ParseError::InvalidPort => "Invalid URL: invalid port number".to_string(),
url::ParseError::RelativeUrlWithoutBase => {
"Invalid URL: relative URL without base".to_string()
}
_ => format!("Invalid URL format: {url_error}"),
}
}
fn analyze_generic_error_string(error_msg: &str) -> Option<String> {
if error_msg.contains("certificate") {
return Some(analyze_certificate_error(error_msg));
}
if error_msg.contains("protocol") && error_msg.contains("not supported") {
return Some("Protocol not supported. Check URL scheme (http/https)".to_string());
}
let result = ErrorRules::new()
.rule(
&["protocol version"],
"TLS protocol version mismatch. The client and server cannot agree on a TLS version. This is often due to outdated system TLS libraries or the server TLS settings.",
)
.rule(
&["handshake", "TLS", "SSL"],
"TLS handshake failed. Check SSL/TLS configuration",
)
.rule(
&["name resolution", "hostname"],
"DNS resolution failed. Check hostname and DNS settings",
)
.rule(
&["Connection refused", "connection refused"],
"Connection refused. Server is not accepting connections (check if service is running)",
)
.rule(
&["Connection reset", "connection reset"],
"Connection reset by server. Server forcibly closed connection",
)
.rule(
&["No route to host", "no route"],
"No route to host. Check network routing or firewall configuration",
)
.rule(
&["Network is unreachable", "network unreachable"],
"Network unreachable. Check internet connection or VPN settings",
)
.rule(
&["timed out", "timeout"],
"Request timed out. Try increasing timeout or check server status",
)
.match_error(error_msg);
if result.starts_with("Unhandled error:") {
None
} else {
Some(result)
}
}
fn fallback_reqwest_analysis(error: &reqwest::Error) -> String {
if error.is_connect() {
"Connection failed. Check network connectivity and firewall settings".to_string()
} else if error.is_request() {
"Request failed. Check URL format and parameters".to_string()
} else if error.is_decode() {
"Response decoding failed. Server returned invalid data".to_string()
} else {
format!("Request failed: {error}")
}
}