http_client_vcr/
serializable.rs1use base64::{engine::general_purpose, Engine as _};
2use http_client::{Error, Request, Response};
3use http_types::{Method, StatusCode, Url};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct SerializableRequest {
9 pub method: String,
10 pub url: String,
11 pub headers: HashMap<String, Vec<String>>,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub body: Option<String>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub body_base64: Option<String>,
16 pub version: String,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct SerializableResponse {
21 pub status: u16,
22 pub headers: HashMap<String, Vec<String>>,
23 #[serde(skip_serializing_if = "Option::is_none")]
24 pub body: Option<String>,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub body_base64: Option<String>,
27 pub version: String,
28}
29
30impl SerializableRequest {
31 pub async fn from_request(mut req: Request) -> Result<Self, Error> {
32 let method = req.method().to_string();
33 let url = req.url().to_string();
34 let version = format!("{:?}", req.version());
35
36 let mut headers = HashMap::new();
37 for (name, values) in req.iter() {
38 let header_values: Vec<String> =
39 values.iter().map(|v| v.as_str().to_string()).collect();
40 headers.insert(name.as_str().to_string(), header_values);
41 }
42
43 let (body, body_base64) = if req.len().is_some() {
44 let body_string = req
45 .body_string()
46 .await
47 .map_err(|e| Error::from_str(500, format!("Failed to read request body: {e}")))?;
48
49 if Self::should_base64_encode(&body_string) {
51 (None, Some(general_purpose::STANDARD.encode(&body_string)))
52 } else {
53 (Some(body_string), None)
54 }
55 } else {
56 (None, None)
57 };
58
59 Ok(Self {
60 method,
61 url,
62 headers,
63 body,
64 body_base64,
65 version,
66 })
67 }
68
69 pub async fn to_request(&self) -> Result<Request, Error> {
70 let method: Method = self
71 .method
72 .parse()
73 .map_err(|e| Error::from_str(400, format!("Invalid method: {e}")))?;
74
75 let url: Url = self
76 .url
77 .parse()
78 .map_err(|e| Error::from_str(400, format!("Invalid URL: {e}")))?;
79
80 let mut req = Request::new(method, url);
81
82 for (name, values) in &self.headers {
83 for value in values {
84 let _ = req.append_header(name.as_str(), value.as_str());
85 }
86 }
87
88 if let Some(body) = &self.body {
89 req.set_body(body.clone());
90 } else if let Some(body_base64) = &self.body_base64 {
91 let decoded = general_purpose::STANDARD
92 .decode(body_base64)
93 .map_err(|e| Error::from_str(500, format!("Failed to decode base64 body: {e}")))?;
94 let body_string = String::from_utf8(decoded).map_err(|e| {
95 Error::from_str(
96 500,
97 format!("Failed to convert decoded body to string: {e}"),
98 )
99 })?;
100 req.set_body(body_string);
101 }
102
103 Ok(req)
104 }
105
106 fn should_base64_encode(content: &str) -> bool {
108 content.contains('<') && content.contains('>') || content.contains('%') && content.len() > 100 || content.chars().filter(|c| !c.is_ascii()).count() > content.len() / 10 }
113}
114
115impl SerializableResponse {
116 pub async fn from_response(mut res: Response) -> Result<Self, Error> {
117 let status = res.status().into();
118 let version = format!("{:?}", res.version());
119
120 let mut headers = HashMap::new();
121 for (name, values) in res.iter() {
122 let header_values: Vec<String> =
123 values.iter().map(|v| v.as_str().to_string()).collect();
124 headers.insert(name.as_str().to_string(), header_values);
125 }
126
127 let (body, body_base64) = if res.len().is_some() {
128 let body_string = res
129 .body_string()
130 .await
131 .map_err(|e| Error::from_str(500, format!("Failed to read response body: {e}")))?;
132
133 if Self::should_base64_encode(&body_string) {
135 (None, Some(general_purpose::STANDARD.encode(&body_string)))
136 } else {
137 (Some(body_string), None)
138 }
139 } else {
140 (None, None)
141 };
142
143 Ok(Self {
144 status,
145 headers,
146 body,
147 body_base64,
148 version,
149 })
150 }
151
152 pub async fn to_response(&self) -> Response {
153 let status = StatusCode::try_from(self.status).unwrap_or(StatusCode::InternalServerError);
154
155 let mut res = Response::new(status);
156
157 for (name, values) in &self.headers {
158 for value in values {
159 let _ = res.append_header(name.as_str(), value.as_str());
160 }
161 }
162
163 if let Some(body) = &self.body {
164 res.set_body(body.clone());
165 } else if let Some(body_base64) = &self.body_base64 {
166 if let Ok(decoded) = general_purpose::STANDARD.decode(body_base64) {
167 if let Ok(body_string) = String::from_utf8(decoded) {
168 res.set_body(body_string);
169 }
170 }
171 }
172
173 res
174 }
175
176 fn should_base64_encode(content: &str) -> bool {
178 content.contains('<') && content.contains('>') || content.contains('%') && content.len() > 100 || content.chars().filter(|c| !c.is_ascii()).count() > content.len() / 10 }
183}