1use crate::core::http_client::{HttpClient, HttpMethod};
7use crate::HttpResponse;
8use clap::{Args, Subcommand};
9use colored::{ColoredString, Colorize};
10use indicatif::{ProgressBar, ProgressStyle};
11use infer;
12use reqwest::multipart::Part;
13use serde_json::Value;
14use std::fmt;
15use std::io::{self, Write};
16use std::time::Duration;
17
18#[derive(Args, Clone, Debug)]
19pub struct RequestData {
20 pub url: String,
21
22 #[clap(
23 short = 'H',
24 long = "header",
25 value_parser = RequestData::parse_header,
26 value_name = "KEY:VALUE",
27 num_args = 1..,
28 required = false
29 )]
30 pub headers: Vec<(String, String)>,
31
32 #[clap(short, long, default_value = "", required = false)]
33 pub body: String,
34}
35
36impl RequestData {
37 pub fn parse_header(s: &str) -> Result<(String, String), String> {
38 let parts: Vec<&str> = s.splitn(2, ':').collect();
39 if parts.len() != 2 {
40 return Err(format!("Invalid header format: '{}'. Use KEY:VALUE", s));
41 }
42 Ok((parts[0].trim().to_string(), parts[1].trim().to_string()))
43 }
44}
45
46#[derive(Subcommand, Clone, Debug)]
47pub enum RequestCommands {
48 Get {
49 #[clap(flatten)]
50 data: RequestData,
51 },
52 Post {
53 #[clap(flatten)]
54 data: RequestData,
55 },
56 Put {
57 #[clap(flatten)]
58 data: RequestData,
59 },
60 Delete {
61 #[clap(flatten)]
62 data: RequestData,
63 },
64 Patch {
65 #[clap(flatten)]
66 data: RequestData,
67 },
68}
69
70impl fmt::Display for RequestCommands {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 match self {
73 Self::Get { .. } => write!(f, "GET"),
74 Self::Post { .. } => write!(f, "POST"),
75 Self::Put { .. } => write!(f, "PUT"),
76 Self::Delete { .. } => write!(f, "DELETE"),
77 Self::Patch { .. } => write!(f, "PATCH"),
78 }
79 }
80}
81
82impl RequestCommands {
83 pub fn get_data(&self) -> &RequestData {
84 match self {
86 Self::Get { data }
87 | Self::Post { data }
88 | Self::Put { data }
89 | Self::Delete { data }
90 | Self::Patch { data } => data,
91 }
92 }
93
94 pub fn print_request_method(&self, url: &str, status: u16, elapsed: u128) {
95 println!(
96 "\n[{}] {} - {} ({} ms)\n",
97 self.to_string().bold().bright_yellow(),
98 url.to_string().bold().bright_white(),
99 Self::colorize_status(status.to_string().parse().unwrap()),
100 elapsed
101 );
102 }
103
104 fn print_request_headers(headers: &[(String, String)]) {
105 println!("{}", "Request Headers:".to_string().bold().bright_blue());
106 for (key, value) in headers.iter() {
107 println!(" {}: {:?}", key.to_string().bright_white(), value);
108 }
109 }
110
111 fn print_request_body(body: &str) {
112 println!("{}", "Request Body:".to_string().bold().bright_blue());
113 println!("{}", body.italic());
114 }
115
116 async fn print_request_response(
117 response: &HttpResponse,
118 verbose: bool,
119 stream: bool,
120 ) -> Result<(), Box<dyn std::error::Error>> {
121 if verbose && !stream {
122 println!("{}", "Response Headers:".to_string().bold().bright_blue());
123 for (key, value) in response.headers.iter() {
124 println!(" {}: {:?}", key.to_string().bright_white(), value);
125 }
126 println!("\n{}", "Response Body:".to_string().bold().bright_blue());
127 }
128
129 if !stream {
130 if let Ok(json) = response.json::<Value>() {
132 let pretty = serde_json::to_string_pretty(&json)?;
133 println!("{}", pretty.green());
134 } else {
135 println!("{}", response.body.italic());
136 }
137 }
138
139 Ok(())
140 }
141
142 pub fn colorize_status(status: u16) -> ColoredString {
143 match status {
144 200..=299 => status.to_string().bold().bright_green(),
145 300..=499 => status.to_string().bold().bright_yellow(),
146 500..=599 => status.to_string().bold().bright_red(),
147 _ => status.to_string().white(),
148 }
149 }
150
151 fn prompt_missing_header_data(mut headers: Vec<(String, String)>) -> Vec<(String, String)> {
152 for header in headers.iter_mut() {
153 if header.1.contains(":?") {
154 eprint!(
155 "Header value for key '{}' is missing data. Please provide the correct value: ",
156 header.0
157 );
158 io::stdout().flush().ok();
159 let mut new_value = String::new();
160 std::io::stdin()
161 .read_line(&mut new_value)
162 .expect("Failed to read header value");
163 header.1 = new_value.trim().to_string();
164 }
165 }
166 headers
167 }
168
169 fn prompt_missing_body_data(mut body: String) -> String {
170 while let Some(idx) = body.find(":?") {
171 eprint!(
172 "Missing data at position {} - {}. Please provide the correct value: ",
173 idx, body
174 );
175 io::stdout().flush().ok();
176 let mut replacement = String::new();
177 std::io::stdin()
178 .read_line(&mut replacement)
179 .expect("Failed to read body placeholder");
180 let replacement = replacement.trim();
181 body.replace_range(idx..idx + 2, replacement);
182 }
183 body
184 }
185
186 fn is_text_data(data: &[u8]) -> bool {
188 std::str::from_utf8(data).is_ok()
189 }
190
191 pub async fn execute_request(
192 &self,
193 verbose: bool,
194 stdin_input: Vec<u8>,
195 stream: bool,
196 ) -> Result<(HttpResponse, u128), Box<dyn std::error::Error>> {
197 let data = self.get_data();
198
199 let current_url = if !stream {
200 Self::prompt_missing_body_data(data.url.clone())
201 } else {
202 data.url.clone()
203 };
204
205 let headers = if !stream {
206 Self::prompt_missing_header_data(data.headers.clone())
207 } else {
208 data.headers.clone()
209 };
210
211 let is_text = Self::is_text_data(&stdin_input);
212 let body = if stdin_input.is_empty() {
213 Self::prompt_missing_body_data(data.body.clone())
214 } else if is_text {
215 let text = String::from_utf8_lossy(&stdin_input).to_string();
217 Self::prompt_missing_body_data(text)
218 } else {
219 String::new() };
222
223 let part = if !stream && !stdin_input.is_empty() && !is_text {
224 let kind = infer::get(&stdin_input).ok_or_else(|| {
226 Box::new(std::io::Error::new(
227 std::io::ErrorKind::InvalidData,
228 "Unknown file type",
229 ))
230 })?;
231 let mime_type = kind.mime_type();
232 let extension = kind.extension();
233 let filename = format!("file.{}", extension);
234 Part::bytes(stdin_input.clone())
235 .file_name(filename)
236 .mime_str(mime_type)?
237 } else if !stream && !stdin_input.is_empty() && is_text {
238 Part::text(String::from_utf8_lossy(&stdin_input).to_string())
240 } else {
241 Part::bytes(body.clone().into_bytes())
243 };
244
245 if verbose && !stream {
246 Self::print_request_headers(&headers);
247 Self::print_request_body(body.as_str());
248 }
249
250 let client = HttpClient::new()
251 .with_follow_redirects(false)
252 .with_timeout(Duration::from_secs(120));
253
254 let method = match self {
255 Self::Get { .. } => HttpMethod::Get,
256 Self::Post { .. } => HttpMethod::Post,
257 Self::Put { .. } => HttpMethod::Put,
258 Self::Delete { .. } => HttpMethod::Delete,
259 Self::Patch { .. } => HttpMethod::Patch,
260 };
261
262 let pb = ProgressBar::new_spinner();
263
264 pb.set_style(
265 ProgressStyle::with_template("{spinner:.green} {elapsed} {msg}")
266 .unwrap()
267 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
268 );
269
270 pb.enable_steady_tick(Duration::from_millis(80));
271 pb.set_message("Executing Request...");
272
273 let start = std::time::Instant::now();
274
275 let resp = if stream {
276 let body_bytes = if !stdin_input.is_empty() {
277 stdin_input
278 } else {
279 body.clone().into_bytes()
280 };
281 client
282 .request(method, ¤t_url)
283 .headers(headers.into_iter().collect())
284 .body_bytes(body_bytes)
285 .send_streaming(|chunk| {
286 std::io::stdout().write_all(chunk)?;
287 std::io::stdout().flush().unwrap();
288 Ok(())
289 })
290 .await
291 } else if is_text {
292 client
293 .request(method, ¤t_url)
294 .headers(headers.into_iter().collect())
295 .body(String::from_utf8_lossy(&stdin_input).as_ref())
296 .send()
297 .await
298 } else {
299 client
300 .request(method, ¤t_url)
301 .headers(headers.into_iter().collect())
302 .send_multipart(part)
303 .await
304 };
305
306 let elapsed = start.elapsed().as_millis();
307
308 match resp {
309 Ok(response) => {
310 pb.finish_with_message("Request completed");
311 Ok((response, elapsed))
312 }
313 Err(err) => {
314 pb.finish_with_message("Request failed");
315 Err(Box::new(err))
316 }
317 }
318 }
319
320 pub async fn run(
321 &self,
322 verbose: bool,
323 stdin_input: Vec<u8>,
324 stream: bool,
325 ) -> Result<(), Box<dyn std::error::Error>> {
326 let response = Self::execute_request(self, verbose, stdin_input, stream).await;
327
328 match response {
329 Ok((resp, elapsed)) => {
330 if verbose && !stream {
331 println!("{:?}", resp.version);
332 self.print_request_method(&resp.url, resp.status, elapsed);
333 }
334 Self::print_request_response(&resp, verbose, stream).await
335 }
336 Err(err) => Err(err),
337 }
338 }
339}