melvin_ob/http_handler/http_response/
response_common.rs

1use strum_macros::Display;
2
3/// Trait representing types that define how to parse HTTP responses.
4pub(crate) trait HTTPResponseType {
5    /// The resulting type returned after parsing the HTTP response body.
6    type ParsedResponseType;
7
8    /// Reads and parses the response body.
9    ///
10    /// # Arguments
11    /// * `response` – A `reqwest::Response` object received from the HTTP client.
12    ///
13    /// # Returns
14    /// * `Result<Self::ParsedResponseType, ResponseError>` – Parsed value or error.
15    async fn read_response(
16        response: reqwest::Response,
17    ) -> Result<Self::ParsedResponseType, ResponseError>;
18
19    /// Unwraps and checks the HTTP status code.
20    ///
21    /// Returns the original response if the status is a success.
22    /// Otherwise, returns an appropriate [`ResponseError`] based on the code.
23    ///
24    /// # Arguments
25    /// * `response` – A `reqwest::Response` object.
26    ///
27    /// # Returns
28    /// * `Ok(response)` if status is 2xx.
29    /// * `Err(ResponseError)` if status is 4xx/5xx
30    async fn unwrap_return_code(
31        response: reqwest::Response,
32    ) -> Result<reqwest::Response, ResponseError> {
33        if response.status().is_success() {
34            Ok(response)
35        } else if response.status().is_server_error() {
36            Err(ResponseError::InternalServer)
37        } else if response.status().is_client_error() {
38            Err(ResponseError::BadRequest(response.json().await?))
39        } else {
40            Err(ResponseError::Unknown)
41        }
42    }
43}
44
45/// Extension trait for response types that can be deserialized from JSON.
46///
47/// This trait provides a default `parse_json_body` implementation.
48pub(crate) trait JSONBodyHTTPResponseType: HTTPResponseType {
49    /// Parses a JSON response body into the target type.
50    ///
51    /// # Type Bounds
52    /// * `Self::ParsedResponseType`: must implement `serde::Deserialize`.
53    ///
54    /// # Arguments
55    /// * `response` – HTTP response object.
56    ///
57    /// # Returns
58    /// * `Result<Self::ParsedResponseType, ResponseError>` – Parsed deserialized response.
59    async fn parse_json_body(
60        response: reqwest::Response,
61    ) -> Result<Self::ParsedResponseType, ResponseError>
62    where Self::ParsedResponseType: for<'de> serde::Deserialize<'de> {
63        Ok(response.json::<Self::ParsedResponseType>().await?)
64    }
65}
66
67impl<T> JSONBodyHTTPResponseType for T
68where
69    T: SerdeJSONBodyHTTPResponseType,
70    for<'de> T: serde::Deserialize<'de>,
71{
72}
73
74/// Marker trait for types that expect JSON as an HTTP response body and can be deserialized.
75///
76/// Implementors must also implement `serde::Deserialize`.
77pub(crate) trait SerdeJSONBodyHTTPResponseType {}
78
79impl<T> HTTPResponseType for T
80where
81    T: SerdeJSONBodyHTTPResponseType,
82    for<'de> T: serde::Deserialize<'de>,
83{
84    type ParsedResponseType = T;
85
86    async fn read_response(
87        response: reqwest::Response,
88    ) -> Result<Self::ParsedResponseType, ResponseError> {
89        let resp = Self::unwrap_return_code(response).await?;
90        Self::parse_json_body(resp).await
91    }
92}
93
94/// Marker trait for types whose HTTP responses are raw byte streams
95/// instead of JSON or structured data.
96pub(crate) trait ByteStreamResponseType: HTTPResponseType {}
97
98/// Top-level error type for handling all HTTP response-related failures.
99#[derive(Debug, Display)]
100pub enum ResponseError {
101    /// A server-side error (HTTP 5xx or timeout).
102    InternalServer,
103    /// A client-side error (HTTP 4xx), parsed into a structured response.
104    BadRequest(BadRequestReturn),
105    /// A connection could not be established.
106    NoConnection,
107    /// Any other unexpected or unclassified error.
108    Unknown,
109}
110
111impl std::error::Error for ResponseError {}
112impl From<reqwest::Error> for ResponseError {
113    /// Converts a `reqwest::Error` into a more specific `ResponseError` variant.
114    fn from(value: reqwest::Error) -> Self {
115        if value.is_request() {
116            ResponseError::BadRequest(BadRequestReturn { detail: value.to_string() })
117        } else if value.is_timeout() || value.is_redirect() {
118            ResponseError::InternalServer
119        } else if value.is_connect() {
120            ResponseError::NoConnection
121        } else {
122            ResponseError::Unknown
123        }
124    }
125}
126
127/// Error detail returned when the backend responds with a client error (HTTP 4xx).
128#[derive(Debug, serde::Deserialize)]
129pub(crate) struct BadRequestReturn {
130    /// Human-readable error explanation.
131    detail: String,
132}
133
134/// Low-level error structure containing granular details about the failed request.
135///
136/// Usually used internally in `BadRequestReturn`.
137#[derive(Debug, serde::Deserialize)]
138struct BadRequestDetail {
139    /// Type of validation or decoding error.
140    error_type: String,
141    /// Location of the error in the request body.
142    loc: Vec<String>,
143    /// Human-readable error message.
144    msg: String,
145    /// Input value that failed validation (if applicable).
146    input: Option<String>,
147    /// Additional error context provided by the backend.
148    ctx: Option<BadRequestDetailContext>,
149}
150
151/// Additional context information for decoding/parsing failures.
152#[derive(Debug, serde::Deserialize)]
153struct BadRequestDetailContext {
154    /// Expected type or format of the input value.
155    expected: String,
156}