Skip to main content

sova_http/
error.rs

1//! Outbound HTTP errors mapped to gateway-style responses.
2
3use sova_core::extend::ErrorResponse;
4use sova_core::{Error, IntoResponse, Response};
5use std::fmt;
6
7/// Failure of an outbound HTTP call (not an upstream 4xx body).
8#[derive(Debug)]
9pub enum HttpError {
10    Timeout,
11    Connect(String),
12    ResponseTooLarge,
13    Ssrf(String),
14    CircuitOpen(String),
15    Status(u16),
16    Other(String),
17}
18
19impl fmt::Display for HttpError {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Self::Timeout => write!(f, "upstream timeout"),
23            Self::Connect(m) => write!(f, "upstream connect: {m}"),
24            Self::ResponseTooLarge => write!(f, "upstream response too large"),
25            Self::Ssrf(m) => write!(f, "ssrf blocked: {m}"),
26            Self::CircuitOpen(host) => write!(f, "circuit open for {host}"),
27            Self::Status(c) => write!(f, "upstream status {c}"),
28            Self::Other(m) => write!(f, "{m}"),
29        }
30    }
31}
32
33impl std::error::Error for HttpError {}
34
35impl HttpError {
36    pub fn status_code(&self) -> u16 {
37        match self {
38            Self::Timeout => 504,
39            Self::Connect(_)
40            | Self::ResponseTooLarge
41            | Self::Ssrf(_)
42            | Self::CircuitOpen(_)
43            | Self::Status(_)
44            | Self::Other(_) => 502,
45        }
46    }
47}
48
49impl IntoResponse for HttpError {
50    fn into_response(self) -> Response {
51        let status = self.status_code();
52        let msg = match status {
53            504 => "Gateway Timeout",
54            _ => "Bad Gateway",
55        };
56        Response::text(msg).status(status)
57    }
58}
59
60impl From<HttpError> for Error {
61    fn from(err: HttpError) -> Self {
62        Error::Response(Box::new(err.into_response()))
63    }
64}
65
66impl ErrorResponse for HttpError {}
67
68impl From<reqwest::Error> for HttpError {
69    fn from(err: reqwest::Error) -> Self {
70        if err.is_timeout() {
71            return Self::Timeout;
72        }
73        if err.is_connect() {
74            return Self::Connect(err.to_string());
75        }
76        Self::Other(err.to_string())
77    }
78}