use std::{error::Error, fmt::Display};
use reqwest::StatusCode;
use url::Url;
use crate::contracts::ProblemDetails;
pub type HttpResult<R> = Result<R, HttpError>;
#[derive(Debug)]
pub enum HttpError {
ConnectionError(reqwest::Error),
ResponseError(ResponseError),
AuthorizationError(String),
}
impl Error for HttpError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
HttpError::ConnectionError(e) => Some(e),
_ => None,
}
}
}
impl Display for HttpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HttpError::ConnectionError(e) => write!(f, "connection error: {}", e),
HttpError::ResponseError(e) => write!(f, "response error: {}", e),
HttpError::AuthorizationError(e) => write!(f, "authorization error: {}", e),
}
}
}
#[derive(Debug)]
pub struct ResponseError {
pub url: Url,
pub status_code: StatusCode,
pub data: Option<String>,
pub request_id: Option<String>,
}
impl ResponseError {
pub fn get_details(&self) -> Option<ProblemDetails> {
self.data
.as_deref()
.and_then(|d| serde_json::from_str(d).ok())
}
}
impl Display for ResponseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"HTTP status {} from {} (request ID {}): {}",
self.status_code,
self.url,
self.request_id.as_deref().unwrap_or("<none>"),
self.data.as_deref().unwrap_or("(empty body)")
)
}
}