use std::fmt;
use reqwest::header::HeaderMap;
use serde_json::Value;
use crate::JsonObject;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Api(#[from] ApiError),
#[error("transport error: {0}")]
Http(#[from] reqwest::Error),
#[error("invalid JSON response: {0}")]
Json(#[from] serde_json::Error),
#[error("{0}")]
RunFailed(String),
#[error("timeout waiting for run result")]
Timeout,
#[error("{0}")]
Protocol(String),
}
#[derive(Debug, Clone)]
pub struct ApiError {
pub status_code: u16,
pub message: String,
pub code: Option<String>,
pub request_id: Option<String>,
pub retry_after: Option<u64>,
pub error: Option<JsonObject>,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (status {})", self.message, self.status_code)
}
}
impl std::error::Error for ApiError {}
pub(crate) fn parse_api_error(status_code: u16, text: &str, headers: &HeaderMap) -> ApiError {
let parsed: Value = serde_json::from_str(text).unwrap_or(Value::Null);
let error_object = parsed.get("error").and_then(Value::as_object);
match error_object {
Some(error) => {
let message = error
.get("message")
.and_then(Value::as_str)
.map(str::to_string)
.unwrap_or_else(|| format!("Hot API error ({status_code})"));
ApiError {
status_code,
message,
code: stringified(error.get("code")),
request_id: stringified(error.get("request_id")),
retry_after: retry_after(error.get("retry_after"), headers),
error: Some(error.clone()),
}
}
None => {
let message = if text.is_empty() {
format!("Hot API error ({status_code})")
} else {
text.to_string()
};
ApiError {
status_code,
message,
code: None,
request_id: None,
retry_after: retry_after(None, headers),
error: None,
}
}
}
}
fn stringified(value: Option<&Value>) -> Option<String> {
match value {
None | Some(Value::Null) => None,
Some(Value::String(text)) => Some(text.clone()),
Some(other) => Some(other.to_string()),
}
}
fn retry_after(value: Option<&Value>, headers: &HeaderMap) -> Option<u64> {
if let Some(seconds) = value.and_then(Value::as_u64) {
return Some(seconds);
}
headers
.get("retry-after")
.and_then(|header| header.to_str().ok())
.and_then(|header| header.parse().ok())
}