1use error::RequestParseError;
2use header::{HeaderToken, parse_header};
3use method::parse_method_input;
4use uri::parse_uri_input;
5use variables::{parse_variable, parse_variable_declaration};
6use version::parse_http_version;
7
8pub mod error;
9mod header;
10mod method;
11mod uri;
12mod variables;
13mod version;
14
15#[derive(Copy, Clone, PartialEq)]
16enum ParserMode {
17 Request,
18 Headers,
19 Body,
20}
21
22#[derive(Debug)]
23enum RequestToken {
24 Method(http::method::Method),
25 Uri(http::uri::Uri),
26 HttpVersion(http::version::Version),
27 Header(HeaderToken),
28 Body(Option<String>),
29}
30
31#[inline]
32fn to_enum_chars(input: &str) -> core::iter::Enumerate<core::str::Chars> {
33 input.chars().enumerate()
34}
35
36#[inline]
37fn tokenize(
38 buffer: &str,
39 input_variables: &std::collections::HashMap<String, String>,
40) -> Result<Vec<RequestToken>, RequestParseError> {
41 let mut tokens: Vec<RequestToken> = Vec::new();
42
43 let mut parser_mode = ParserMode::Request;
44
45 let mut body_parts: Vec<String> = Vec::new();
46
47 let mut vars = input_variables.to_owned();
48
49 for line in buffer.lines() {
50 let trimmed_line = line.trim();
51
52 if trimmed_line.starts_with('#') {
54 if trimmed_line.starts_with("###") && parser_mode != ParserMode::Request {
55 if body_parts.is_empty() {
56 tokens.push(RequestToken::Body(None));
57 } else {
58 tokens.push(RequestToken::Body(Some(body_parts.join("\n"))));
59
60 body_parts.clear();
61 };
62
63 parser_mode = ParserMode::Request;
64 }
65
66 if parser_mode == ParserMode::Request {
67 continue;
68 }
69 } else if trimmed_line.starts_with("//") {
70 if parser_mode == ParserMode::Request {
72 continue;
73 }
74 }
75
76 match parser_mode {
77 ParserMode::Request => {
78 if trimmed_line.starts_with('@') {
79 let mut chrs = to_enum_chars(trimmed_line);
80
81 chrs.next();
83
84 if let Some((name, value)) = parse_variable_declaration(&mut chrs, &vars)? {
85 vars.insert(name, value);
86 continue;
87 }
88 }
89
90 if !trimmed_line.is_empty() {
91 let mut chrs = to_enum_chars(trimmed_line);
92 let method = parse_method_input(&mut chrs, &vars)?;
93
94 tokens.push(RequestToken::Method(method));
95
96 let uri = parse_uri_input(&mut chrs, &vars)?;
97
98 tokens.push(RequestToken::Uri(uri));
99
100 if let Some(http_version) = parse_http_version(&mut chrs, &vars) {
101 tokens.push(RequestToken::HttpVersion(http_version));
102 }
103
104 parser_mode = ParserMode::Headers;
105 }
106 }
107
108 ParserMode::Headers => {
109 if trimmed_line.is_empty() {
110 parser_mode = ParserMode::Body;
111 } else if let Some(header_token) =
112 parse_header(&mut to_enum_chars(trimmed_line), &vars)?
113 {
114 tokens.push(RequestToken::Header(header_token));
115 }
116 }
117
118 ParserMode::Body => {
119 let mut current_line = String::new();
120 let mut chars = to_enum_chars(line);
121
122 while let Some((_, ch)) = chars.next() {
123 if ch == '{' {
124 if let Some((var, jumps)) = parse_variable(&mut chars.clone()) {
126 if let Some(variable_value) = vars.get(&var) {
127 current_line.push_str(variable_value);
128
129 for _ in 0..jumps {
130 chars.next();
131 }
132
133 continue;
134 }
135
136 return Err(RequestParseError::VariableNotFound(var));
137 }
138 }
139
140 current_line.push(ch);
141 }
142
143 body_parts.push(current_line);
144 }
145 };
146 }
147
148 if !body_parts.is_empty() {
149 tokens.push(RequestToken::Body(Some(body_parts.join("\n"))));
150 }
151
152 Ok(tokens)
153}
154
155#[cfg(test)]
156mod test_tokenize {
157 use core::fmt::Write as _;
158
159 use once_cell::sync::Lazy;
160
161 use crate::{RequestToken, error::RequestParseError, tokenize};
162
163 static EMPTY_VARS: Lazy<std::collections::HashMap<String, String>> =
164 Lazy::new(std::collections::HashMap::new);
165
166 #[test]
167 fn should_return_a_list_of_tokens() {
168 let method_input = "GET";
169 let uri_input = "https://mhouge.dk/";
170 let http_version = "HTTP/2";
171 let header1_key = "content-type";
172 let header1_value = "application/json";
173 let body_input = "{ \"key\": \"value\" }";
174
175 let input_request = format!(
176 "{method_input} {uri_input} {http_version}\n{header1_key}: {header1_value}\n\n{body_input}"
177 );
178
179 let tokens =
180 tokenize(&input_request, &EMPTY_VARS).expect("it to return Result<Vec<RequestToken>>");
181
182 assert_eq!(tokens.len(), 5);
183
184 for token in tokens {
185 match token {
186 RequestToken::Uri(uri_token) => assert_eq!(uri_input, uri_token.to_string()),
187 RequestToken::Method(method_token) => {
188 assert_eq!(method_input, method_token.as_str());
189 }
190 RequestToken::Header(header_token) => {
191 assert_eq!(header1_key, header_token.key.to_string());
192
193 assert_eq!(
194 header1_value,
195 header_token
196 .value
197 .to_str()
198 .expect("value to be a valid str")
199 );
200 }
201
202 RequestToken::Body(body_token) => {
203 assert!(body_token.is_some());
204
205 let body_inner = body_token.expect("body to be defined");
206
207 assert_eq!(body_input, body_inner);
208 }
209
210 RequestToken::HttpVersion(version_token) => {
211 assert_eq!(version_token, http::version::Version::HTTP_2);
212 }
213 }
214 }
215 }
216
217 #[test]
218 fn it_should_be_able_to_parse_multiple_requests() {
219 let uri = "https://mhouge.dk/";
220
221 let methods = [
222 http::Method::GET,
223 http::Method::PUT,
224 http::Method::POST,
225 http::Method::PATCH,
226 http::Method::DELETE,
227 http::Method::OPTIONS,
228 http::Method::HEAD,
229 http::Method::TRACE,
230 http::Method::CONNECT,
231 ];
232
233 let versions = [
234 http::Version::HTTP_09,
235 http::Version::HTTP_10,
236 http::Version::HTTP_11,
237 http::Version::HTTP_2,
238 http::Version::HTTP_3,
239 ];
240
241 let header_key = "x-test-header";
242 let header_value = "test-value";
243
244 let body = "mads was here\n".to_owned();
245
246 let mut input = String::new();
247
248 let mut input_request_index: u16 = 0;
249
250 for method in &methods {
251 for version in &versions {
252 writeln!(input, "{method} {uri} {version:?}").expect("it to write");
253 writeln!(input, "{header_key}: {header_value}\n").expect("it to write");
254
255 if input_request_index % 2 == 0 {
256 writeln!(input, "{body}").expect("it to write");
257 }
258
259 writeln!(input, "###\n").expect("it to write");
260
261 input_request_index += 1;
262 }
263 }
264
265 let tokens = tokenize(&input, &EMPTY_VARS).expect("it to return a list of tokens");
266
267 assert_eq!(tokens.len(), methods.len() * versions.len() * 5);
268
269 let mut output_request_index: u16 = 0;
270 let mut token_index = 0;
271
272 let body_option = Some(body);
273
274 for method in &methods {
275 for version in &versions {
276 let method_token = tokens.get(token_index).expect("it to be a method token");
277 assert!(matches!(method_token, RequestToken::Method(m) if m == method));
278 token_index += 1;
279
280 let uri_token = tokens.get(token_index).expect("it to be an uri token");
281 assert!(matches!(uri_token, RequestToken::Uri(u) if u == uri));
282 token_index += 1;
283
284 let version_token = tokens.get(token_index).expect("it to be a version token");
285 assert!(matches!(version_token, RequestToken::HttpVersion(v) if v == version));
286 token_index += 1;
287
288 let header_token = tokens.get(token_index).expect("it to be a header token");
289 assert!(
290 matches!(header_token, RequestToken::Header(h) if h.key == header_key && h.value == header_value)
291 );
292 token_index += 1;
293
294 let body_token = tokens.get(token_index).expect("it to be a body token");
295 if output_request_index % 2 == 0 {
296 assert!(matches!(body_token, RequestToken::Body(b) if b == &body_option));
297 } else {
298 assert!(matches!(body_token, RequestToken::Body(b) if b.is_none()));
299 }
300 token_index += 1;
301
302 output_request_index += 1;
303 }
304 }
305 }
306
307 #[test]
308 fn it_should_ignore_comments() {
309 let input = "
310// comment 1
311# comment 2
312
313DELETE https://mhouge.dk/";
314
315 let tokens = tokenize(input, &EMPTY_VARS).expect("it to parse successfully");
316
317 assert_eq!(tokens.len(), 2);
318
319 let method_token = tokens.first().expect("it to be some");
320
321 assert!(
322 matches!(method_token, RequestToken::Method(m) if m == http::method::Method::DELETE)
323 );
324
325 let uri_token = tokens.get(1).expect("it to be Some");
326
327 let expected_uri = "https://mhouge.dk/";
328
329 assert!(matches!(uri_token, RequestToken::Uri(uri) if uri == expected_uri));
330 }
331
332 #[test]
333 fn it_should_only_check_for_comments_when_parsermode_request() {
334 let url = "https://mhouge.dk/api/something/?refresh=true";
335 let method = "DELETE";
336
337 let status_line = format!("{method} {url}");
338
339 for comment_style in ["#", "//"] {
340 let body = format!("{comment_style} this is not a comment");
341
342 let hashtag = format!(
343 "{status_line}
344
345{body}"
346 );
347
348 let tokens = tokenize(&hashtag, &EMPTY_VARS).expect("it to parse successfully");
349
350 assert_eq!(tokens.len(), 3);
351
352 let method_token = tokens.first().expect("it to be some");
353
354 assert!(
355 matches!(method_token, RequestToken::Method(m) if m == http::method::Method::DELETE)
356 );
357
358 let uri_token = tokens.get(1).expect("it to be Some");
359
360 assert!(matches!(uri_token, RequestToken::Uri(u) if u == url));
361
362 let body_token = tokens.get(2).expect("it to be Some");
363
364 assert!(matches!(body_token, RequestToken::Body(b) if b == &Some(body)));
365 }
366 }
367
368 #[test]
369 fn it_should_support_variables() {
370 {
371 let input = "
372@method = GET
373@host = https://mhouge.dk
374@path = /api
375@query_value = mads@mhouge.dk
376@body_input = { \"key\": \"value\" }
377
378{{method}} {{host}}{{path}}?email={{query_value}}
379
380{{ body_input }}";
381
382 let tokens = tokenize(input, &EMPTY_VARS).expect("it to tokenize successfully");
383
384 assert_eq!(tokens.len(), 3);
385
386 let method_token = tokens.first().expect("it to be some");
387
388 assert!(
389 matches!(method_token, RequestToken::Method(m) if m == http::method::Method::GET)
390 );
391
392 let uri_token = tokens.get(1).expect("it to be Some");
393
394 let expected_uri = "https://mhouge.dk/api?email=mads@mhouge.dk";
395
396 assert!(matches!(uri_token, RequestToken::Uri(uri) if uri == expected_uri));
397
398 let body_token = tokens.get(2).expect("it to be set");
399
400 let expected_body_value = "{ \"key\": \"value\" }";
401
402 assert!(matches!(
403 body_token,
404 RequestToken::Body(value)
405 if value.clone().expect( "value to exist") == expected_body_value
406 ));
407 };
408
409 {
410 let input = "
411GET https://mhouge.dk/
412
413{{ body_input }}";
414
415 let tokens = tokenize(input, &EMPTY_VARS).expect_err("it to return an error");
416
417 assert!(matches!(
418 tokens,
419 RequestParseError::VariableNotFound(var)
420 if var == "body_input"
421 ));
422 }
423 }
424
425 #[test]
426 fn it_should_support_input_variables() {
427 let vars = std::collections::HashMap::from([
428 ("method".to_owned(), "GET".to_owned()),
429 ("host".to_owned(), "https://mhouge.dk".to_owned()),
430 ("path".to_owned(), "/api".to_owned()),
431 ("query_value".to_owned(), "mads@mhouge.dk".to_owned()),
432 ("body_input".to_owned(), "{ \"key\": \"value\" }".to_owned()),
433 ]);
434
435 let input = "
436{{method}} {{host}}{{path}}?email={{query_value}}
437
438{{ body_input }}";
439
440 let tokens = tokenize(input, &vars).expect("it to tokenize successfully");
441
442 assert_eq!(tokens.len(), 3);
443
444 let method_token = tokens.first().expect("it to return token");
445
446 assert!(
447 matches!(method_token, RequestToken::Method(method) if method == http::Method::GET)
448 );
449
450 let expected_uri = "https://mhouge.dk/api?email=mads@mhouge.dk";
451
452 let uri_token = tokens.get(1).expect("it to return an uri");
453
454 assert!(matches!(uri_token, RequestToken::Uri(uri) if uri == expected_uri));
455
456 let body_token = tokens.get(2).expect("it to return a token");
457
458 let expected_body = "{ \"key\": \"value\" }";
459
460 assert!(
461 matches!(body_token, RequestToken::Body(Some(output_body)) if output_body == expected_body)
462 );
463 }
464
465 #[test]
466 fn it_should_raise_error_if_missing_variable() {
467 let input = "GET {{missing_variable}}";
468
469 let err = tokenize(input, &EMPTY_VARS).expect_err("it to be a missing variable error");
470
471 assert!(matches!(err, RequestParseError::VariableNotFound(v) if v == "missing_variable"));
472 }
473
474 #[test]
475 fn input_variables_should_be_overwritten_by_local_variables() {
476 let vars = std::collections::HashMap::from([("method".to_owned(), "PUT".to_owned())]);
477
478 let input = "
479@method = POST
480
481{{method}} https://mhouge.dk/";
482
483 let tokens = tokenize(input, &vars).expect("it to parse successfully");
484
485 assert_eq!(tokens.len(), 2);
486
487 let method_token = tokens.first().expect("it to return token");
488
489 assert!(
490 matches!(method_token, RequestToken::Method(method) if method == http::Method::POST)
491 );
492
493 let uri_token = tokens.get(1).expect("it to return token");
494
495 assert!(matches!(uri_token, RequestToken::Uri(uri) if uri == "https://mhouge.dk/"));
496 }
497
498 #[test]
499 fn it_should_ignore_triple_hashtag_when_in_parsermode_request() {
500 let input = "
501###
502
503###
504
505###
506
507OPTIONS https://mhouge.dk/
508###
509###
510###
511
512HEAD https://mhouge.dk/blog/";
513
514 let output = tokenize(input, &EMPTY_VARS).expect("it to parse");
515
516 assert_eq!(output.len(), 5);
517
518 {
519 let method = output.first().expect("it to return a method token");
520 assert!(matches!(method, RequestToken::Method(m) if m == http::Method::OPTIONS));
521
522 let uri = output.get(1).expect("it to return a uri token");
523 assert!(matches!(uri, RequestToken::Uri(u) if u == "https://mhouge.dk/"));
524
525 let body = output.get(2).expect("it to be an empty body token");
526 assert!(matches!(body, RequestToken::Body(b) if b.is_none()));
527 };
528
529 {
530 let method = output.get(3).expect("it to return a method token");
531 assert!(matches!(method, RequestToken::Method(m) if m == http::Method::HEAD));
532
533 let uri = output.get(4).expect("it to return a uri token");
534 assert!(matches!(uri, RequestToken::Uri(u) if u == "https://mhouge.dk/blog/"));
535 };
536 }
537}
538
539#[derive(Debug)]
540pub struct HittRequest {
541 pub method: http::method::Method,
542 pub uri: http::uri::Uri,
543 pub headers: http::HeaderMap,
544 pub body: Option<String>,
545 pub http_version: Option<http::version::Version>,
546}
547
548#[derive(Default)]
549struct PartialHittRequest {
550 method: Option<http::method::Method>,
551 uri: Option<http::uri::Uri>,
552 headers: http::HeaderMap,
553 body: Option<String>,
554 http_version: Option<http::version::Version>,
555}
556
557impl PartialHittRequest {
558 #[inline]
559 fn build(self) -> Result<HittRequest, RequestParseError> {
560 match self.method {
561 Some(method) => match self.uri {
562 Some(uri) => Ok(HittRequest {
563 method,
564 uri,
565 headers: self.headers,
566 body: self.body,
567 http_version: self.http_version,
568 }),
569 None => Err(RequestParseError::MissingUri),
570 },
571 None => Err(RequestParseError::MissingMethod),
572 }
573 }
574}
575
576#[cfg(test)]
577mod test_partial_http_request {
578
579 use http::{HeaderMap, Uri};
580
581 use crate::{PartialHittRequest, error::RequestParseError};
582
583 #[test]
584 fn build_should_reject_if_no_uri() {
585 let request = PartialHittRequest {
586 uri: None,
587 method: Some(http::Method::GET),
588 http_version: None,
589 headers: HeaderMap::default(),
590 body: None,
591 }
592 .build()
593 .expect_err("it to raise RequestParseError::MissingUri");
594
595 assert!(matches!(request, RequestParseError::MissingUri));
596 }
597
598 #[test]
599 fn build_should_reject_if_no_method() {
600 let uri = Uri::from_static("https://mhouge.dk/");
601
602 let request = PartialHittRequest {
603 uri: Some(uri),
604 method: None,
605 http_version: None,
606 headers: HeaderMap::default(),
607 body: None,
608 }
609 .build()
610 .expect_err("it to raise RequestParseError::MissingMethod");
611
612 assert!(matches!(request, RequestParseError::MissingMethod));
613 }
614}
615
616#[inline]
617pub fn parse_requests(
618 buffer: &str,
619 input_variables: &std::collections::HashMap<String, String>,
620) -> Result<Vec<HittRequest>, RequestParseError> {
621 let mut requests = Vec::new();
622
623 let tokens = tokenize(buffer, input_variables)?;
624
625 let mut partial_request = PartialHittRequest::default();
626
627 for token in tokens {
628 match token {
629 RequestToken::Method(method) => {
630 partial_request.method = Some(method);
631 }
632
633 RequestToken::Uri(uri) => {
634 partial_request.uri = Some(uri);
635 }
636
637 RequestToken::Header(header) => {
638 partial_request.headers.insert(header.key, header.value);
639 }
640
641 RequestToken::Body(body) => {
642 partial_request.body = body;
643
644 requests.push(partial_request.build()?);
645
646 partial_request = PartialHittRequest::default();
647 }
648
649 RequestToken::HttpVersion(version_token) => {
650 partial_request.http_version = Some(version_token);
651 }
652 };
653 }
654
655 if partial_request.method.is_some() {
656 requests.push(partial_request.build()?);
657 };
658
659 Ok(requests)
660}
661
662#[cfg(test)]
663mod test_parse_requests {
664 use core::str::FromStr;
665
666 use once_cell::sync::Lazy;
667
668 use crate::{error::RequestParseError, parse_requests};
669
670 const HTTP_METHODS: [&str; 9] = [
671 "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "CONNECT", "TRACE",
672 ];
673
674 static EMPTY_VARS: Lazy<std::collections::HashMap<String, String>> =
675 Lazy::new(std::collections::HashMap::new);
676
677 #[test]
678 fn it_should_parse_http_method_correctly() {
679 let url = "https://mhouge.dk";
680
681 for method in &HTTP_METHODS {
682 let expected_method = http::Method::from_str(method).expect("m is a valid method");
683
684 let input = format!("{method} {url}");
685
686 let parsed_requests =
687 parse_requests(&input, &EMPTY_VARS).expect("request should be valid");
688
689 assert!(parsed_requests.len() == 1);
690
691 let first_request = parsed_requests.first().expect("it to be a request");
692
693 assert_eq!(expected_method, first_request.method);
694
695 let expected_uri = url.parse::<http::uri::Uri>().expect("url should be valid");
696
697 assert_eq!(expected_uri, first_request.uri);
698
699 assert_eq!(0, first_request.headers.len());
700
701 assert_eq!(None, first_request.body);
702 }
703 }
704
705 #[test]
706 fn it_should_be_able_to_parse_requests() {
707 let method_input = "GET";
708 let uri_input = "https://mhouge.dk/";
709
710 let header1_key = "content-type";
711 let header1_value = "application/json";
712 let body_input = "{ \"key\": \"value\" }";
713
714 let input_request =
715 format!("{method_input} {uri_input}\n{header1_key}: {header1_value}\n\n{body_input}");
716
717 let result =
718 parse_requests(&input_request, &EMPTY_VARS).expect("it to return a list of requests");
719
720 assert!(result.len() == 1);
721
722 let request = result.first().expect("request len to be 1");
723
724 assert_eq!(method_input, request.method.as_str());
725
726 assert_eq!(uri_input, request.uri.to_string());
727
728 let body_inner = request.body.clone().expect("body to be defined");
729
730 assert_eq!(body_inner, body_input);
731
732 assert_eq!(1, request.headers.len());
733
734 let header1_output = request
735 .headers
736 .get(header1_key)
737 .expect("header1_key to exist");
738
739 assert_eq!(
740 header1_value,
741 header1_output.to_str().expect("it to be a valid header")
742 );
743
744 assert!(request.http_version.is_none());
745 }
746
747 #[test]
748 fn it_should_be_able_to_parse_multiple_requests() {
749 let input = "
750GET https://mhouge.dk/ HTTP/0.9
751
752###
753
754PUT https://mhouge.dk/ HTTP/1.0
755
756###
757
758POST https://mhouge.dk/ HTTP/1.1
759
760###
761
762PATCH https://mhouge.dk/ HTTP/2
763
764###
765
766DELETE https://mhouge.dk/ HTTP/3
767
768###
769";
770
771 let requests = parse_requests(input, &EMPTY_VARS).expect("to get a list of requests");
772
773 assert_eq!(5, requests.len());
774
775 {
776 let request = requests.first().expect("it to be exist");
777
778 assert_eq!(http::Method::GET, request.method);
779
780 assert_eq!("https://mhouge.dk/", request.uri.to_string());
781
782 assert!(request.headers.is_empty());
783
784 assert!(request.body.is_none());
785
786 assert_eq!(
787 http::Version::HTTP_09,
788 request.http_version.expect("http_version to be defined")
789 );
790 };
791
792 {
793 let request = requests.get(1).expect("it to be exist");
794
795 assert_eq!(http::Method::PUT, request.method);
796
797 assert_eq!("https://mhouge.dk/", request.uri.to_string());
798
799 assert!(request.headers.is_empty());
800
801 assert!(request.body.is_none());
802
803 assert_eq!(
804 http::Version::HTTP_10,
805 request.http_version.expect("http_version to be defined")
806 );
807 };
808
809 {
810 let request = requests.get(2).expect("it to be exist");
811
812 assert_eq!(http::Method::POST, request.method);
813
814 assert_eq!("https://mhouge.dk/", request.uri.to_string());
815
816 assert!(request.headers.is_empty());
817
818 assert!(request.body.is_none());
819
820 assert_eq!(
821 http::Version::HTTP_11,
822 request.http_version.expect("http_version to be defined")
823 );
824 };
825
826 {
827 let request = requests.get(3).expect("it to be exist");
828
829 assert_eq!(http::Method::PATCH, request.method);
830
831 assert_eq!("https://mhouge.dk/", request.uri.to_string());
832
833 assert!(request.headers.is_empty());
834
835 assert!(request.body.is_none());
836
837 assert_eq!(
838 http::Version::HTTP_2,
839 request.http_version.expect("http_version to be defined")
840 );
841 };
842
843 {
844 let request = requests.get(4).expect("it to be exist");
845
846 assert_eq!(http::Method::DELETE, request.method);
847
848 assert_eq!("https://mhouge.dk/", request.uri.to_string());
849
850 assert!(request.headers.is_empty());
851
852 assert!(request.body.is_none());
853
854 assert_eq!(
855 http::Version::HTTP_3,
856 request.http_version.expect("http_version to be defined")
857 );
858 };
859 }
860
861 #[test]
862 fn it_should_support_variables() {
863 let input = "
864@method = GET
865@host = https://mhouge.dk
866@path = /api
867@query_value = mads@mhouge.dk
868@body_input = { \"key\": \"value\" }
869
870{{method}} {{host}}{{path}}?email={{query_value}}
871
872{{ body_input }}";
873
874 let requests = parse_requests(input, &EMPTY_VARS).expect("to get a list of requests");
875
876 assert_eq!(requests.len(), 1);
877
878 let request = requests.first().expect("it to have 1 request");
879
880 assert_eq!(request.method, http::method::Method::GET);
881
882 assert_eq!(request.uri, "https://mhouge.dk/api?email=mads@mhouge.dk");
883
884 assert_eq!(
885 "{ \"key\": \"value\" }",
886 request.body.clone().expect("body to be set"),
887 );
888 }
889
890 #[test]
891 fn it_should_support_variable_input() {
892 {
893 let mut vars = std::collections::HashMap::from([
894 ("method".to_owned(), "GET".to_owned()),
895 ("host".to_owned(), "https://mhouge.dk".to_owned()),
896 ("path".to_owned(), "/api".to_owned()),
897 ("query_value".to_owned(), "mads@mhouge.dk".to_owned()),
898 ("body_input".to_owned(), "{ \"key\": \"value\" }".to_owned()),
899 ]);
900
901 let input = "
902{{method}} {{host}}{{path}}?email={{query_value}}
903{{header_name}}: {{header_value}}
904
905{{ body_input }}";
906
907 for i in u8::MIN..u8::MAX {
908 let header_name = format!("mads-was-here{i}");
909 let header_value = format!("or was i{i}?");
910
911 vars.insert("header_name".to_owned(), header_name.clone());
912 vars.insert("header_value".to_owned(), header_value.clone());
913
914 let requests = parse_requests(input, &vars).expect("to get a list of requests");
915
916 assert_eq!(requests.len(), 1);
917
918 let request = requests.first().expect("it to have 1 request");
919
920 assert_eq!(request.method, http::method::Method::GET);
921
922 assert_eq!(request.uri, "https://mhouge.dk/api?email=mads@mhouge.dk");
923
924 assert_eq!(request.headers.len(), 1);
925
926 let result_header_value = request.headers.get(header_name).expect("it to exist");
927
928 assert_eq!(
929 header_value,
930 result_header_value
931 .to_str()
932 .expect("it to be a valid string"),
933 );
934
935 assert_eq!(
936 "{ \"key\": \"value\" }",
937 request.body.clone().expect("body to be set"),
938 );
939 }
940 }
941
942 {
943 let input = "
944GET https://mhouge.dk/
945
946{{ body_input }}";
947
948 let request = parse_requests(input, &EMPTY_VARS).expect_err("it to return an error");
949
950 assert!(matches!(
951 request,
952 RequestParseError::VariableNotFound(var)
953 if var == "body_input"
954 ));
955 }
956 }
957
958 #[test]
959 fn input_variables_should_be_overwritten_by_local_variables() {
960 let vars = std::collections::HashMap::from([("method".to_owned(), "PUT".to_owned())]);
961
962 let input = "
963@method = POST
964
965{{method}} https://mhouge.dk/";
966
967 let requests = parse_requests(input, &vars).expect("it to parse successfully");
968
969 assert_eq!(requests.len(), 1);
970
971 let request = requests.first().expect("it to exist");
972
973 assert_eq!(request.method, http::method::Method::POST);
974
975 assert_eq!(request.uri, "https://mhouge.dk/");
976
977 assert_eq!(request.headers.len(), 0);
978 }
979
980 #[test]
981 fn it_should_ignore_comments() {
982 let input = "
983// comment 1
984# comment 2
985
986DELETE https://mhouge.dk/";
987
988 let requests = parse_requests(input, &EMPTY_VARS).expect("it to parse successfully");
989
990 assert_eq!(requests.len(), 1);
991
992 let request = requests.first().expect("it to exist");
993
994 assert_eq!(request.method, http::method::Method::DELETE);
995
996 let expected_uri = "https://mhouge.dk/";
997
998 assert_eq!(request.uri, expected_uri);
999
1000 assert!(request.headers.is_empty());
1001
1002 assert!(request.body.is_none());
1003 }
1004}