Skip to main content

flyr/
error.rs

1use std::fmt;
2
3#[derive(Debug)]
4pub enum FlightError {
5    Timeout,
6    ConnectionFailed(String),
7    DnsResolution(String),
8    ProxyError(String),
9    RateLimited,
10    Blocked(u16),
11    HttpStatus(u16),
12    TlsError(String),
13    ScriptTagNotFound,
14    JsParse(String),
15    NoResults,
16    InvalidAirport(String),
17    InvalidDate(String),
18    Validation(String),
19}
20
21impl fmt::Display for FlightError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            Self::Timeout => write!(
25                f,
26                "request timed out — Google may be slow or unreachable. \
27                 Try increasing --timeout or check your connection"
28            ),
29            Self::ConnectionFailed(detail) => write!(
30                f,
31                "connection failed — check your internet connection ({detail})"
32            ),
33            Self::DnsResolution(host) => write!(
34                f,
35                "DNS resolution failed for {host} — check your internet connection"
36            ),
37            Self::ProxyError(detail) => write!(
38                f,
39                "proxy error — check your --proxy URL is correct ({detail})"
40            ),
41            Self::RateLimited => write!(
42                f,
43                "rate limited by Google (HTTP 429) — wait a few minutes before retrying, \
44                 or use --proxy to route through a different IP"
45            ),
46            Self::Blocked(status) => write!(
47                f,
48                "request blocked by Google (HTTP {status}) — this usually means \
49                 rate limiting or bot detection. Try again later or use --proxy"
50            ),
51            Self::HttpStatus(status) => write!(
52                f,
53                "unexpected HTTP status {status} from Google Flights"
54            ),
55            Self::TlsError(detail) => write!(
56                f,
57                "TLS/SSL error — connection to Google failed ({detail})"
58            ),
59            Self::ScriptTagNotFound => write!(
60                f,
61                "failed to parse Google Flights response — the page structure may have changed, \
62                 or Google returned a CAPTCHA/consent page. \
63                 Try again, use --proxy, or file an issue if this persists"
64            ),
65            Self::JsParse(detail) => write!(
66                f,
67                "failed to parse flight data from response — {detail}. \
68                 This may indicate a Google Flights format change"
69            ),
70            Self::NoResults => write!(f, "no flights found for this search"),
71            Self::InvalidAirport(code) => write!(
72                f,
73                "invalid airport code \"{code}\" — must be exactly 3 letters (e.g. JFK, HEL, NRT)"
74            ),
75            Self::InvalidDate(date) => write!(
76                f,
77                "invalid date \"{date}\" — must be YYYY-MM-DD format (e.g. 2026-03-01)"
78            ),
79            Self::Validation(msg) => write!(f, "{msg}"),
80        }
81    }
82}
83
84impl std::error::Error for FlightError {}
85
86pub fn from_http_error(err: wreq::Error) -> FlightError {
87    let msg = err.to_string();
88    let lower = msg.to_lowercase();
89
90    if err.is_timeout() {
91        return FlightError::Timeout;
92    }
93
94    if err.is_connect() {
95        if lower.contains("dns") || lower.contains("resolve") || lower.contains("getaddrinfo") {
96            return FlightError::DnsResolution(msg);
97        }
98        return FlightError::ConnectionFailed(msg);
99    }
100
101    if lower.contains("proxy") || lower.contains("socks") {
102        return FlightError::ProxyError(msg);
103    }
104
105    if lower.contains("tls") || lower.contains("ssl") || lower.contains("certificate") {
106        return FlightError::TlsError(msg);
107    }
108
109    if lower.contains("builder error") && lower.contains("uri") {
110        return FlightError::ProxyError(msg);
111    }
112
113    FlightError::ConnectionFailed(msg)
114}