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}