1use crate::cli::request_data::RequestData;
7use crate::HttpResponse;
8use clap::Subcommand;
9use colored::{ColoredString, Colorize};
10use serde_json::Value;
11use std::fmt;
12use std::io::{self, Write};
13
14#[derive(Subcommand, Clone, Debug)]
15pub enum RequestCommands {
16 Get {
17 #[clap(flatten)]
18 data: RequestData,
19 },
20 Post {
21 #[clap(flatten)]
22 data: RequestData,
23 },
24 Put {
25 #[clap(flatten)]
26 data: RequestData,
27 },
28 Delete {
29 #[clap(flatten)]
30 data: RequestData,
31 },
32 Patch {
33 #[clap(flatten)]
34 data: RequestData,
35 },
36}
37
38impl fmt::Display for RequestCommands {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 Self::Get { .. } => write!(f, "GET"),
42 Self::Post { .. } => write!(f, "POST"),
43 Self::Put { .. } => write!(f, "PUT"),
44 Self::Delete { .. } => write!(f, "DELETE"),
45 Self::Patch { .. } => write!(f, "PATCH"),
46 }
47 }
48}
49
50impl RequestCommands {
51 pub fn get_data(&self) -> &RequestData {
52 match self {
54 Self::Get { data }
55 | Self::Post { data }
56 | Self::Put { data }
57 | Self::Delete { data }
58 | Self::Patch { data } => data,
59 }
60 }
61
62 pub fn print_request_method(&self, url: &str, status: u16, elapsed: u128) {
63 println!(
64 "\n[{}] {} - {} ({} ms)\n",
65 self.to_string().bold().bright_yellow(),
66 url.to_string().bold().bright_white(),
67 Self::colorize_status(status.to_string().parse().unwrap()),
68 elapsed
69 );
70 }
71
72 pub fn print_request_headers(headers: &[(String, String)]) {
73 println!("{}", "Request Headers:".to_string().bold().bright_blue());
74 for (key, value) in headers.iter() {
75 println!(" {}: {:?}", key.to_string().bright_white(), value);
76 }
77 }
78
79 pub fn print_request_body(body: &str) {
80 println!("{}", "Request Body:".to_string().bold().bright_blue());
81 println!("{}", body.italic());
82 }
83
84 pub fn print_lines_with_numbers(lines: &Vec<&str>, line_numbers: &[usize]) {
85 for (i, line) in lines.iter().enumerate() {
86 if line_numbers.contains(&(i + 1)) {
87 println!("{}: {}", (i + 1).to_string().bright_cyan(), line);
88 }
89 }
90 }
91
92 pub fn print_response_body(body: &str, output: &str) {
93 if output.starts_with("lines") {
94 let parts: Vec<&str> = output.split(',').collect();
95 let lines: Vec<&str> = body.lines().collect();
96 if parts.len() >= 2 {
97 let range_parts: Vec<&str> = parts[1].split('-').collect();
99 if range_parts.len() == 1 {
100 if let Ok(line_num) = range_parts[0].parse::<usize>() {
101 if line_num > 0 && line_num <= lines.len() {
102 println!(
103 "{}: {}",
104 line_num.to_string().bright_cyan(),
105 lines[line_num - 1]
106 );
107 } else {
108 eprintln!(
109 "Line number {} is out of range. The response body has {} lines.",
110 line_num,
111 lines.len()
112 );
113 }
114 } else {
115 eprintln!("Invalid line number specified. Expected format: 'lines,line' e.g. 'lines,34'");
116 }
117 } else if range_parts.len() == 2 {
118 if let (Ok(start), Ok(end)) = (
119 range_parts[0].parse::<usize>(),
120 range_parts[1].parse::<usize>(),
121 ) {
122 if start > 0 && end <= lines.len() && start <= end {
123 for i in start..=end {
124 println!("{}: {}", i.to_string().bright_cyan(), lines[i - 1]);
125 }
126 } else {
127 eprintln!("Invalid line range specified. Ensure that start and end are within the range of the response body lines and that start is less than or equal to end.");
128 }
129 } else {
130 eprintln!("Invalid line range specified. Expected format: 'lines,start-end' e.g. 'lines,34-35'");
131 }
132 } else if range_parts.len() > 2 {
133 let mut line_numbers = Vec::new();
135 for part in range_parts {
136 if let Ok(line_num) = part.parse::<usize>() {
137 if line_num > 0 && line_num <= lines.len() {
138 line_numbers.push(line_num);
139 }
140 } else {
141 eprintln!("Invalid output format. Expected format: 'lines,start-end' or 'lines'");
142 }
143 }
144 Self::print_lines_with_numbers(&lines, &line_numbers);
145 } else {
146 eprintln!(
147 "Invalid output format. Expected format: 'lines,start-end' or 'lines'"
148 );
149 }
150 } else {
151 for (i, line) in lines.iter().enumerate() {
152 println!("{}: {}", (i + 1).to_string().bright_cyan(), line);
153 }
154 }
155 } else if output.starts_with("json") {
156 let parts: Vec<&str> = output.split(',').collect();
158 if let Ok(json) = serde_json::from_str::<Value>(body) {
159 if parts.len() == 2 {
160 let key = parts[1];
161 if let Some(value) = json.get(key) {
162 let pretty = serde_json::to_string_pretty(value)
163 .unwrap_or_else(|_| value.to_string());
164 println!("{}", pretty.green());
165 } else {
166 eprintln!("Key '{}' not found in JSON response.", key);
167 }
168 } else {
169 let pretty =
170 serde_json::to_string_pretty(&json).unwrap_or_else(|_| body.to_string());
171 println!("{}", pretty.green());
172 }
173 } else {
174 eprintln!("Failed to parse response body as JSON.");
175 }
176 } else {
177 println!("{}", body.italic());
178 }
179 }
180
181 pub fn print_request_response(
182 response: &HttpResponse,
183 verbose: bool,
184 stream: bool,
185 output: &Option<String>,
186 ) -> Result<(), Box<dyn std::error::Error>> {
187 if verbose && !stream {
188 println!("{}", "Response Headers:".to_string().bold().bright_blue());
189 for (key, value) in response.headers.iter() {
190 println!(" {}: {:?}", key.to_string().bright_white(), value);
191 }
192 println!("\n{}", "Response Body:".to_string().bold().bright_blue());
193 }
194
195 if !stream {
196 if let Some(output) = output {
197 Self::print_response_body(&response.body, output);
198 } else {
199 if let Ok(json) = response.json::<Value>() {
201 let pretty = serde_json::to_string_pretty(&json)?;
202 println!("{}", pretty.green());
203 } else {
204 println!("{}", response.body.italic());
205 }
206 }
207 }
208
209 Ok(())
210 }
211
212 pub fn colorize_status(status: u16) -> ColoredString {
213 match status {
214 200..=299 => status.to_string().bold().bright_green(),
215 300..=499 => status.to_string().bold().bright_yellow(),
216 500..=599 => status.to_string().bold().bright_red(),
217 _ => status.to_string().white(),
218 }
219 }
220
221 pub fn prompt_missing_header_data(mut headers: Vec<(String, String)>) -> Vec<(String, String)> {
222 for header in headers.iter_mut() {
223 if header.1.contains(":?") {
224 eprint!(
225 "Header value for key '{}' is missing data. Please provide the correct value: ",
226 header.0
227 );
228 io::stdout().flush().ok();
229 let mut new_value = String::new();
230 std::io::stdin()
231 .read_line(&mut new_value)
232 .expect("Failed to read header value");
233 header.1 = new_value.trim().to_string();
234 }
235 }
236 headers
237 }
238
239 pub fn prompt_missing_body_data(mut body: String) -> String {
240 while let Some(idx) = body.find(":?") {
241 eprint!(
242 "Missing data at position {} - {}. Please provide the correct value: ",
243 idx, body
244 );
245 io::stdout().flush().ok();
246 let mut replacement = String::new();
247 std::io::stdin()
248 .read_line(&mut replacement)
249 .expect("Failed to read body placeholder");
250 let replacement = replacement.trim();
251 body.replace_range(idx..idx + 2, replacement);
252 }
253 body
254 }
255
256 pub fn is_text_data(data: &[u8]) -> bool {
258 std::str::from_utf8(data).is_ok()
259 }
260}