use std::error::Error;
use std::time::Duration;
use http::{
Method,
StatusCode,
};
use thiserror::Error;
use url::Url;
use super::RetryHint;
use qubit_error::BoxError;
use super::HttpErrorKind;
#[derive(Debug, 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>,
}
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,
}
}
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 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 {
Self::build_client(format!("Failed to build reqwest client: {}", error)).with_source(error)
}
}