coman/cli/
request_ops.rs

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            // Convert to string for text processing
34            let text = String::from_utf8_lossy(&stdin_input).to_string();
35            Self::prompt_missing_body_data(text)
36        } else {
37            // Binary: skip text prompts, use as-is (but reqwest body will handle bytes)
38            String::new() // Placeholder; we'll use bytes directly in the request
39        };
40
41        let part = if !stream && !stdin_input.is_empty() && !is_text {
42            // Binary data from stdin
43            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            // Text data from stdin
57            Part::text(String::from_utf8_lossy(&stdin_input).to_string())
58        } else {
59            // Use body string
60            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, &current_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, &current_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, &current_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}