1use std::{io::Write, time::Duration};
2
3use indicatif::{ProgressBar, ProgressStyle};
4use reqwest::multipart::Part;
5
6use crate::{cli::request::RequestCommands, HttpClient, HttpMethod, HttpResponse};
7
8impl RequestCommands {
9 pub async fn execute_request(
10 &self,
11 verbose: bool,
12 stdin_input: Vec<u8>,
13 stream: bool,
14 ) -> Result<(HttpResponse, u128), Box<dyn std::error::Error>> {
15 let data = self.get_data();
16
17 let current_url = if !stream {
18 RequestCommands::prompt_missing_body_data(data.url.clone())
19 } else {
20 data.url.clone()
21 };
22
23 let headers = if !stream {
24 Self::prompt_missing_header_data(data.headers.clone())
25 } else {
26 data.headers.clone()
27 };
28
29 let is_text = Self::is_text_data(&stdin_input);
30 let body = if stdin_input.is_empty() {
31 Self::prompt_missing_body_data(data.body.clone())
32 } else if is_text {
33 let text = String::from_utf8_lossy(&stdin_input).to_string();
35 Self::prompt_missing_body_data(text)
36 } else {
37 String::new() };
40
41 let part = if !stream && !stdin_input.is_empty() && !is_text {
42 let kind = infer::get(&stdin_input).ok_or_else(|| {
44 Box::new(std::io::Error::new(
45 std::io::ErrorKind::InvalidData,
46 "Unknown file type",
47 ))
48 })?;
49 let mime_type = kind.mime_type();
50 let extension = kind.extension();
51 let filename = format!("file.{}", extension);
52 Part::bytes(stdin_input.clone())
53 .file_name(filename)
54 .mime_str(mime_type)?
55 } else if !stream && !stdin_input.is_empty() && is_text {
56 Part::text(String::from_utf8_lossy(&stdin_input).to_string())
58 } else {
59 Part::bytes(body.clone().into_bytes())
61 };
62
63 if verbose && !stream {
64 Self::print_request_headers(&headers);
65 Self::print_request_body(body.as_str());
66 }
67
68 let client = HttpClient::new()
69 .with_follow_redirects(false)
70 .with_timeout(Duration::from_secs(120));
71
72 let method = match self {
73 Self::Get { .. } => HttpMethod::Get,
74 Self::Post { .. } => HttpMethod::Post,
75 Self::Put { .. } => HttpMethod::Put,
76 Self::Delete { .. } => HttpMethod::Delete,
77 Self::Patch { .. } => HttpMethod::Patch,
78 };
79
80 let pb = ProgressBar::new_spinner();
81
82 pb.set_style(
83 ProgressStyle::with_template("{spinner:.green} {elapsed} {msg}")
84 .unwrap()
85 .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]),
86 );
87
88 pb.enable_steady_tick(Duration::from_millis(80));
89 pb.set_message("Executing Request...");
90
91 let start = std::time::Instant::now();
92
93 let resp = if stream {
94 let body_bytes = if !stdin_input.is_empty() {
95 stdin_input
96 } else {
97 body.clone().into_bytes()
98 };
99 client
100 .request(method, ¤t_url)
101 .headers(headers.into_iter().collect())
102 .body_bytes(body_bytes)
103 .send_streaming(|chunk| {
104 std::io::stdout().write_all(chunk)?;
105 std::io::stdout().flush().unwrap();
106 Ok(())
107 })
108 .await
109 } else if is_text {
110 client
111 .request(method, ¤t_url)
112 .headers(headers.into_iter().collect())
113 .body(String::from_utf8_lossy(&stdin_input).as_ref())
114 .send()
115 .await
116 } else {
117 client
118 .request(method, ¤t_url)
119 .headers(headers.into_iter().collect())
120 .send_multipart(part)
121 .await
122 };
123
124 let elapsed = start.elapsed().as_millis();
125
126 match resp {
127 Ok(response) => {
128 pb.finish_with_message("Request completed");
129 Ok((response, elapsed))
130 }
131 Err(err) => {
132 pb.finish_with_message("Request failed");
133 Err(Box::new(err))
134 }
135 }
136 }
137
138 pub async fn run(
139 &self,
140 verbose: bool,
141 stdin_input: Vec<u8>,
142 stream: bool,
143 ) -> Result<(), Box<dyn std::error::Error>> {
144 let response = Self::execute_request(self, verbose, stdin_input, stream).await;
145
146 match response {
147 Ok((resp, elapsed)) => {
148 if verbose && !stream {
149 println!("{:?}", resp.version);
150 self.print_request_method(&resp.url, resp.status, elapsed);
151 }
152 Self::print_request_response(&resp, verbose, stream).await
153 }
154 Err(err) => Err(err),
155 }
156 }
157}