use reqwest::StatusCode;
use serde::Deserialize;
use thiserror::Error as ThisError;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, ThisError)]
pub enum Error {
#[error("invalid API key header: {0}")]
InvalidApiKey(#[source] reqwest::header::InvalidHeaderValue),
#[error("failed to build HTTP client: {0}")]
ClientBuild(#[source] reqwest::Error),
#[error("HTTP request failed: {0}")]
Request(#[from] reqwest::Error),
#[error("authentication failed ({status}): {message}")]
Auth {
status: StatusCode,
message: String,
},
#[error("rate limit exceeded: {message}")]
RateLimit {
message: String,
},
#[error("latlng service unavailable: {message}")]
Unavailable {
message: String,
},
#[error("API error ({status}): {message}")]
Api {
status: StatusCode,
message: String,
},
}
#[derive(Debug, Deserialize)]
struct ErrorBody {
error: Option<String>,
message: Option<String>,
}
pub(crate) async fn error_from_response(response: reqwest::Response) -> Error {
let status = response.status();
let body = response.text().await.unwrap_or_default();
let message = parse_error_message(&body).unwrap_or_else(|| {
status
.canonical_reason()
.unwrap_or("request failed")
.to_string()
});
match status {
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => Error::Auth { status, message },
StatusCode::TOO_MANY_REQUESTS => Error::RateLimit { message },
StatusCode::SERVICE_UNAVAILABLE => Error::Unavailable { message },
_ => Error::Api { status, message },
}
}
fn parse_error_message(body: &str) -> Option<String> {
if body.trim().is_empty() {
return None;
}
if let Ok(parsed) = serde_json::from_str::<ErrorBody>(body) {
return parsed
.error
.or(parsed.message)
.filter(|message| !message.trim().is_empty());
}
Some(body.to_string())
}