1use crate::parsers::Request;
2use crate::request::body::{Body, MultipartPart};
3
4pub trait Curl {
5 fn curl(&self) -> String;
6}
7
8impl Curl for Request {
9 fn curl(&self) -> String {
10 let mut parts = vec![format!("curl -X {}", self.method.as_str())];
11
12 for (name, value) in self.headers.iter() {
13 parts.push(format!(
14 "-H \"{}: {}\"",
15 name.as_str().replace('"', r#"\""#),
16 value.to_str().unwrap().replace('"', r#"\""#)
17 ))
18 }
19
20 match &self.body {
21 Body::Plain(body) => {
22 if !body.is_empty() {
23 parts.push(format!("-d \"{}\"", escape_body(body)));
24 }
25 }
26 Body::Multipart(multiparts) => {
27 for prt in multiparts {
28 parts.push(match prt {
29 MultipartPart::File {
30 name,
31 file_path,
32 mime_str,
33 } => {
34 let type_and_end = match mime_str {
35 None => "\"".to_string(),
36 Some(mime) => format!("; type={}\"", mime),
37 };
38 format!(
39 "-F {name}=\"@{filepath}{type_and_end}",
40 name = name,
41 filepath = file_path.to_str(),
42 type_and_end = type_and_end,
43 )
44 }
45 MultipartPart::Text {
46 name,
47 text,
48 mime_str,
49 } => {
50 let type_and_end = match mime_str {
51 None => "\"".to_string(),
52 Some(mime) => format!("; type={}\"", mime),
53 };
54 format!(
55 "-F {name}=\"{text}{type_and_end}",
56 name = name,
57 text = text.replace('"', "\\\""),
58 type_and_end = type_and_end,
59 )
60 }
61 });
62 }
63 }
64 }
65
66 parts.push(format!("--url \"{}\"", &self.url.replace('"', r#"\""#)));
67
68 parts.join(" \\\n")
69 }
70}
71
72fn escape_body<S: Into<String>>(input: S) -> String {
73 input.into().replace('\n', "\\\n").replace('"', "\\\"")
74}
75
76#[cfg(test)]
77mod test {
78 use indoc::{formatdoc, indoc};
79 use serde_json::json;
80
81 use crate::request::body::MultipartPart;
82 use crate::test_utils::root;
83
84 use super::*;
85
86 #[test]
87 fn should_print_command_for_simple_request() {
88 let result = Request::basic("GET", "http://localhost/123").curl();
89
90 assert_eq!(
91 result,
92 indoc!(
93 r#"
94 curl -X GET \
95 --url "http://localhost/123""#
96 )
97 );
98 }
99
100 #[test]
101 fn should_print_command_with_headers() {
102 let result = Request::basic("GET", "http://localhost/123")
103 .add_header("accept", "application/json")
104 .add_header("content-type", "application/json")
105 .curl();
106
107 assert_eq!(
108 result,
109 indoc!(
110 r#"
111 curl -X GET \
112 -H "accept: application/json" \
113 -H "content-type: application/json" \
114 --url "http://localhost/123""#
115 )
116 );
117 }
118
119 #[test]
120 fn should_print_command_with_headers_and_body() {
121 let body = "{\n \"foo\": \"bar\",\n \"bar\": \"escape'me\"\n}";
122
123 let result = Request::basic("GET", "http://localhost/555")
124 .add_header("content-type", "application/json")
125 .body(body)
126 .curl();
127
128 assert_eq!(
129 result,
130 indoc!(
131 r#"
132 curl -X GET \
133 -H "content-type: application/json" \
134 -d "{\
135 \"foo\": \"bar\",\
136 \"bar\": \"escape'me\"\
137 }" \
138 --url "http://localhost/555""#
139 )
140 );
141 }
142
143 #[test]
144 fn should_print_command_with_plain_text_body() {
145 let result = Request::basic("GET", "http://localhost/555")
146 .add_header("content-type", "application/json")
147 .body("this is a so-called \"test\"")
148 .curl();
149
150 assert_eq!(
151 result,
152 indoc!(
153 r#"
154 curl -X GET \
155 -H "content-type: application/json" \
156 -d "this is a so-called \"test\"" \
157 --url "http://localhost/555""#
158 )
159 );
160 }
161
162 #[test]
163 fn should_print_command_with_body_with_newlines() {
164 let result = Request::basic("GET", "http://localhost/555")
165 .add_header("content-type", "application/json")
166 .body("one\ntwo\nthree")
167 .curl();
168
169 assert_eq!(
170 result,
171 indoc!(
172 r#"
173 curl -X GET \
174 -H "content-type: application/json" \
175 -d "one\
176 two\
177 three" \
178 --url "http://localhost/555""#
179 )
180 );
181 }
182
183 #[test]
184 fn should_print_command_with_gql_body() {
185 let result = Request::basic("GET", "http://localhost/555")
186 .add_header("content-type", "application/json")
187 .gql_body(json!({
188 "query": "query { search(filter: \"foobar\") { id } }",
189 }))
190 .curl();
191
192 assert_eq!(
193 result,
194 indoc!(
195 r#"
196 curl -X GET \
197 -H "content-type: application/json" \
198 -d "{\"query\":\"query { search(filter: \\"foobar\\") { id } }\"}" \
199 --url "http://localhost/555""#
200 )
201 );
202 }
203
204 #[test]
205 fn should_print_command_with_headers_and_files() {
206 let result = Request::basic("GET", "http://localhost/555")
207 .add_header("content-type", "application/json")
208 .multipart(&[
209 MultipartPart::File {
210 name: "file1".to_string(),
211 file_path: root().join("Cargo.toml"),
212 mime_str: None,
213 },
214 MultipartPart::File {
215 name: "file2".to_string(),
216 file_path: root().join("Cargo.lock"),
217 mime_str: None,
218 },
219 ])
220 .curl();
221
222 assert_eq!(
223 result,
224 formatdoc!(
225 r#"
226 curl -X GET \
227 -H "content-type: application/json" \
228 -F file1="@{base}/Cargo.toml" \
229 -F file2="@{base}/Cargo.lock" \
230 --url "http://localhost/555""#,
231 base = root().to_str().to_string(),
232 )
233 );
234 }
235
236 #[test]
237 fn should_print_command_with_multiparts() {
238 let filepath = root().join("resources/image.jpg");
239 let result = Request::basic("GET", "http://localhost/555")
240 .multipart(&[
241 MultipartPart::Text {
242 name: "textpart1".to_string(),
243 text: "this is a text".to_string(),
244 mime_str: Some("plain/text".to_string()),
245 },
246 MultipartPart::Text {
247 name: "textpart2".to_string(),
248 text: "{\"a\": 5}".to_string(),
249 mime_str: Some("application/json".to_string()),
250 },
251 MultipartPart::Text {
252 name: "textpart3".to_string(),
253 text: "this is a text".to_string(),
254 mime_str: None,
255 },
256 MultipartPart::File {
257 name: "filepart".to_string(),
258 file_path: filepath.clone(),
259 mime_str: None,
260 },
261 ])
262 .curl();
263
264 assert_eq!(
265 result,
266 formatdoc!(
267 r#"
268 curl -X GET \
269 -F textpart1="this is a text; type=plain/text" \
270 -F textpart2="{{\"a\": 5}}; type=application/json" \
271 -F textpart3="this is a text" \
272 -F filepart="@{filepath}" \
273 --url "http://localhost/555""#,
274 filepath = filepath.to_str(),
275 )
276 );
277 }
278}