use std::error::Error;
use std::fmt;
use std::time::Duration;
use http::{
Method,
StatusCode,
};
use thiserror::Error;
use url::Url;
use super::RetryHint;
use crate::sanitize::SanitizedDebugger;
use crate::LogSanitizePolicy;
use qubit_error::BoxError;
use super::HttpErrorKind;
#[derive(Error)]
#[error("{message}")]
pub struct HttpError {
pub kind: HttpErrorKind,
pub method: Option<Method>,
pub url: Option<Url>,
pub status: Option<StatusCode>,
pub message: String,
pub response_body_preview: Option<String>,
pub retry_after: Option<Duration>,
#[source]
pub source: Option<BoxError>,
pub log_sanitize_policy: Box<LogSanitizePolicy>,
}
impl fmt::Debug for HttpError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let debugger = SanitizedDebugger::new(&self.log_sanitize_policy);
let url = debugger.optional_url(self.url.as_ref());
let message = debugger.diagnostic_text(&self.message);
let response_body_preview_len = self.response_body_preview.as_ref().map(String::len);
formatter
.debug_struct("HttpError")
.field("kind", &self.kind)
.field("method", &self.method)
.field("url", &url)
.field("status", &self.status)
.field("message", &message)
.field("response_body_preview_len", &response_body_preview_len)
.field("retry_after", &self.retry_after)
.field("source_present", &self.source.is_some())
.finish()
}
}
impl HttpError {
pub fn new(kind: HttpErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
method: None,
url: None,
status: None,
message: message.into(),
response_body_preview: None,
retry_after: None,
source: None,
log_sanitize_policy: Box::new(LogSanitizePolicy::default()),
}
}
pub fn with_method(mut self, method: &Method) -> Self {
self.method = Some(method.clone());
self
}
pub fn with_url(mut self, url: &Url) -> Self {
self.url = Some(url.clone());
self
}
pub fn with_status(mut self, status: StatusCode) -> Self {
self.status = Some(status);
self
}
pub fn with_source<E>(mut self, source: E) -> Self
where
E: Error + Send + Sync + 'static,
{
self.source = Some(Box::new(source));
self
}
pub fn with_response_body_preview(mut self, preview: impl Into<String>) -> Self {
self.response_body_preview = Some(preview.into());
self
}
pub fn with_retry_after(mut self, retry_after: Duration) -> Self {
self.retry_after = Some(retry_after);
self
}
pub fn with_log_sanitize_policy(mut self, policy: LogSanitizePolicy) -> Self {
self.log_sanitize_policy = Box::new(policy);
self
}
pub fn invalid_url(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::InvalidUrl, message)
}
pub fn build_client(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::BuildClient, message)
}
pub fn proxy_config(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::ProxyConfig, message)
}
pub fn connect_timeout(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::ConnectTimeout, message)
}
pub fn read_timeout(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::ReadTimeout, message)
}
pub fn write_timeout(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::WriteTimeout, message)
}
pub fn request_timeout(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::RequestTimeout, message)
}
pub fn transport(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::Transport, message)
}
pub fn status(status: StatusCode, message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::Status, message).with_status(status)
}
pub fn decode(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::Decode, message)
}
pub fn sse_protocol(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::SseProtocol, message)
}
pub fn sse_decode(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::SseDecode, message)
}
pub fn cancelled(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::Cancelled, message)
}
pub fn retry_attempt_timeout(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::RetryAttemptTimeout, message)
}
pub fn retry_max_elapsed_exceeded(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::RetryMaxElapsedExceeded, message)
}
pub fn retry_aborted(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::RetryAborted, message)
}
pub fn other(message: impl Into<String>) -> Self {
Self::new(HttpErrorKind::Other, message)
}
pub fn retry_hint(&self) -> RetryHint {
match self.kind {
HttpErrorKind::ConnectTimeout
| HttpErrorKind::ReadTimeout
| HttpErrorKind::WriteTimeout
| HttpErrorKind::RequestTimeout
| HttpErrorKind::Transport => RetryHint::Retryable,
HttpErrorKind::Status => {
if let Some(status) = self.status {
if status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error() {
RetryHint::Retryable
} else {
RetryHint::NonRetryable
}
} else {
RetryHint::NonRetryable
}
}
_ => RetryHint::NonRetryable,
}
}
}
impl From<std::io::Error> for HttpError {
fn from(error: std::io::Error) -> Self {
Self::transport(error.to_string()).with_source(error)
}
}
impl From<reqwest::Error> for HttpError {
fn from(error: reqwest::Error) -> Self {
let error = error.without_url();
Self::build_client(format!("Failed to build reqwest client: {}", error)).with_source(error)
}
}