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}