1use crate::cookie::Cookie;
2use crate::{Error, Result};
3use reqwest::{Method, StatusCode};
4use serde::de::DeserializeOwned;
5use serde_json::{to_string_pretty, Value};
6
7#[allow(unused)]
8#[cfg(feature = "color-output")]
9use colored::*;
10#[allow(unused)]
11#[cfg(feature = "color-output")]
12use colored_json::prelude::*;
13use reqwest::header::HeaderMap;
14
15pub struct Response {
16 request_method: Method,
17 request_url: String,
18
19 status: StatusCode,
20 header_map: HeaderMap,
21
22 client_cookies: Vec<Cookie>,
23
24 cookies: Vec<Cookie>,
26 body: Body,
27}
28
29enum Body {
30 Json(Value),
31 Text(String),
32 Other,
33}
34
35#[allow(unused)]
36#[cfg(feature = "color-output")]
37fn get_status_color(status: &StatusCode) -> Color {
38 match status.as_u16() {
39 200..=299 => Color::Green, 300..=399 => Color::Blue, 400..=499 => Color::Yellow, 500..=599 => Color::Red, _ => Color::White, }
45}
46
47#[allow(unused)]
48#[cfg(feature = "color-output")]
49fn get_method_background(method: &Method) -> Color {
50 match *method {
51 Method::GET => Color::TrueColor { r: 223, g: 231, b: 238 },
52 Method::POST => Color::TrueColor { r: 220, g: 233, b: 228 },
53 Method::PUT => Color::TrueColor { r: 238, g: 229, b: 218 },
54 Method::DELETE => Color::TrueColor { r: 238, g: 219, b: 219 },
55 _ => Color::White,
56 }
57}
58
59#[allow(unused)]
60#[cfg(feature = "color-output")]
61fn get_method_color(method: &Method) -> Color {
62 match *method {
63 Method::GET => Color::TrueColor { r: 92, g: 166, b: 241 },
64 Method::POST => Color::TrueColor { r: 59, g: 184, b: 127 },
65 Method::PUT => Color::TrueColor { r: 239, g: 153, b: 46 },
66 Method::DELETE => Color::TrueColor { r: 236, g: 59, b: 59 },
67 _ => Color::White,
68 }
69}
70
71#[allow(unused)]
72#[cfg(feature = "color-output")]
73fn split_and_color_url(url: &str) -> String {
74 let url_struct = url::Url::parse(url).unwrap();
75 let path = url_struct.path();
76 format!("{}", path.purple())
77}
78
79#[allow(unused)]
80#[cfg(feature = "color-output")]
81fn format_method(method: &Method) -> String {
82 format!(" {:<10}", method.to_string())
83}
84
85#[allow(unused)]
86#[cfg(feature = "color-output")]
87const INDENTATION: u8 = 12;
88
89impl Response {
90 pub(crate) async fn from_reqwest_response(
91 request_method: Method,
92 request_url: String,
93 client_cookies: Vec<Cookie>,
94 mut res: reqwest::Response,
95 ) -> Result<Response> {
96 let status = res.status();
97
98 let cookies: Vec<Cookie> = res.cookies().map(Cookie::from).collect();
100
101 let headers = res.headers_mut().drain().filter_map(|(n, v)| n.map(|n| (n, v)));
103 let header_map = HeaderMap::from_iter(headers);
104
105 let ct = header_map.get("content-type").and_then(|v| v.to_str().ok());
107 let body = if let Some(ct) = ct {
108 if ct.starts_with("application/json") {
109 Body::Json(res.json::<Value>().await?)
110 } else if ct.starts_with("text/") {
111 Body::Text(res.text().await?)
112 } else {
113 Body::Other
114 }
115 } else {
116 Body::Other
117 };
118
119 Ok(Response {
120 client_cookies,
121 request_method,
122 request_url,
123 status,
124 header_map,
125 cookies,
126 body,
127 })
128 }
129}
130
131impl Response {
132 pub async fn print(&self) -> Result<()> {
134 self.inner_print(true).await
135 }
136
137 pub async fn print_no_body(&self) -> Result<()> {
138 self.inner_print(false).await
139 }
140
141 #[allow(unused)]
143 #[cfg(feature = "color-output")]
144 async fn inner_print(&self, body: bool) -> Result<()> {
145 let method_color = get_method_color(&self.request_method);
146 let method_background = get_method_background(&self.request_method);
147 let colored_url = split_and_color_url(&self.request_url);
148 let status_color = get_status_color(&self.status);
149 println!();
150 println!(
151 "{}: {}",
152 format_method(&self.request_method)
153 .bold()
154 .color(method_color)
155 .on_truecolor(50, 50, 50),
156 colored_url
157 );
158 println!(
159 " {:<9} : {} {}",
160 "Status".blue(),
161 self.status.as_str().bold().color(status_color).on_black(),
162 self.status.canonical_reason().unwrap_or_default().color(status_color)
163 );
164
165 println!(" {:<9} :", "Headers".blue());
167
168 for (n, v) in self.header_map.iter() {
169 println!(" {}: {}", n.to_string().yellow(), v.to_str().unwrap_or_default());
170 }
171
172 if !self.cookies.is_empty() {
174 println!(" {}:", "Response Cookies".blue());
175 for c in self.cookies.iter() {
176 println!(" {}: {}", c.name.yellow(), c.value.bold());
177 }
178 }
179
180 if !self.client_cookies.is_empty() {
182 println!(" {}:", "Client Cookies".blue());
183 for c in self.client_cookies.iter() {
184 println!(" {}: {}", c.name.yellow(), c.value.bold());
185 }
186 }
187
188 if body {
189 println!("{}:", "Response Body".blue());
191 match &self.body {
192 Body::Json(val) => println!("{}", to_string_pretty(val)?.to_colored_json_auto()?),
193 Body::Text(val) => println!(" {}", val.color(status_color)),
194 _ => (),
195 }
196 }
197
198 println!("\n");
199 Ok(())
200 }
201
202 #[cfg(not(feature = "color-output"))]
203 async fn inner_print(&self, body: bool) -> Result<()> {
204 println!();
205 println!("=== Response for {} {}", self.request_method, &self.request_url);
206
207 println!(
208 "=> {:<15}: {} {}",
209 "Status",
210 self.status.as_str(),
211 self.status.canonical_reason().unwrap_or_default()
212 );
213
214 println!("=> {:<15}:", "Headers");
216
217 for (n, v) in self.header_map.iter() {
218 println!(" {}: {}", n, v.to_str().unwrap_or_default());
219 }
220
221 if !self.cookies.is_empty() {
223 println!("=> {:<15}:", "Response Cookies");
224 for c in self.cookies.iter() {
225 println!(" {}: {}", c.name, c.value);
226 }
227 }
228
229 if !self.client_cookies.is_empty() {
231 println!("=> {:<15}:", "Client Cookies");
232 for c in self.client_cookies.iter() {
233 println!(" {}: {}", c.name, c.value);
234 }
235 }
236
237 if body {
238 println!("=> {:<15}:", "Response Body");
240 match &self.body {
241 Body::Json(val) => println!("{}", to_string_pretty(val)?),
242 Body::Text(val) => println!("{}", val),
243 _ => (),
244 }
245 }
246
247 println!("===\n");
248 Ok(())
249 }
250
251 pub fn header_all(&self, name: &str) -> Vec<String> {
255 self.header_map
256 .get_all(name)
257 .iter()
258 .filter_map(|v| v.to_str().map(|v| v.to_string()).ok())
259 .collect()
260 }
261
262 pub fn header(&self, name: &str) -> Option<String> {
263 self.header_map.get(name).and_then(|v| v.to_str().map(|v| v.to_string()).ok())
264 }
265 pub fn status(&self) -> StatusCode {
270 self.status
271 }
272 pub fn res_cookie(&self, name: &str) -> Option<&Cookie> {
277 self.cookies.iter().find(|c| c.name == name)
278 }
279
280 pub fn res_cookie_value(&self, name: &str) -> Option<String> {
282 self.cookies.iter().find(|c| c.name == name).map(|c| c.value.clone())
283 }
284 pub fn client_cookie(&self, name: &str) -> Option<&Cookie> {
291 self.client_cookies.iter().find(|c| c.name == name)
292 }
293
294 pub fn client_cookie_value(&self, name: &str) -> Option<String> {
298 self.client_cookies.iter().find(|c| c.name == name).map(|c| c.value.clone())
299 }
300 pub fn json_body(&self) -> Result<Value> {
304 match &self.body {
305 Body::Json(val) => Ok(val.clone()),
306 _ => Err(Error::Static("No json body")),
307 }
308 }
309
310 pub fn text_body(&self) -> Result<String> {
311 match &self.body {
312 Body::Text(val) => Ok(val.clone()),
313 _ => Err(Error::Static("No text body")),
314 }
315 }
316
317 pub fn json_value<T>(&self, pointer: &str) -> Result<T>
318 where
319 T: DeserializeOwned,
320 {
321 let Body::Json(body) = &self.body else {
322 return Err(Error::Static("No json body"));
323 };
324
325 let value = body.pointer(pointer).ok_or_else(|| Error::NoJsonValueFound {
326 json_pointer: pointer.to_string(),
327 })?;
328
329 Ok(serde_json::from_value::<T>(value.clone())?)
330 }
331
332 pub fn json_body_as<T>(&self) -> Result<T>
333 where
334 T: DeserializeOwned,
335 {
336 self.json_body()
337 .and_then(|val| serde_json::from_value::<T>(val).map_err(Error::SerdeJson))
338 }
339 }