1use std::sync::LazyLock;
2
3use anyhow::{Ok, Result};
4use regex::Regex;
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToString, Vc};
7
8use super::pattern::Pattern;
9
10#[turbo_tasks::value(shared)]
11#[derive(Hash, Clone, Debug)]
12pub enum Request {
13 Raw {
14 path: Pattern,
15 query: RcStr,
16 force_in_lookup_dir: bool,
17 fragment: RcStr,
18 },
19 Relative {
20 path: Pattern,
21 query: RcStr,
22 force_in_lookup_dir: bool,
23 fragment: RcStr,
24 },
25 Module {
26 module: Pattern,
27 path: Pattern,
28 query: RcStr,
29 fragment: RcStr,
30 },
31 ServerRelative {
32 path: Pattern,
33 query: RcStr,
34 fragment: RcStr,
35 },
36 Windows {
37 path: Pattern,
38 query: RcStr,
39 fragment: RcStr,
40 },
41 Empty,
42 PackageInternal {
43 path: Pattern,
44 },
45 Uri {
46 protocol: RcStr,
47 remainder: RcStr,
48 query: RcStr,
49 fragment: RcStr,
50 },
51 DataUri {
52 media_type: RcStr,
53 encoding: RcStr,
54 data: ResolvedVc<RcStr>,
55 },
56 Unknown {
57 path: Pattern,
58 },
59 Dynamic,
60 Alternatives {
61 requests: Vec<ResolvedVc<Request>>,
62 },
63}
64
65fn split_off_query_fragment(mut raw: &str) -> (Pattern, RcStr, RcStr) {
70 let hash = match raw.as_bytes().iter().position(|&b| b == b'#') {
74 Some(pos) => {
75 let (prefix, hash) = raw.split_at(pos);
76 raw = prefix;
77 RcStr::from(hash)
78 }
79 None => RcStr::default(),
80 };
81
82 let query = match raw.as_bytes().iter().position(|&b| b == b'?') {
83 Some(pos) => {
84 let (prefix, query) = raw.split_at(pos);
85 raw = prefix;
86 RcStr::from(query)
87 }
88 None => RcStr::default(),
89 };
90 (Pattern::Constant(RcStr::from(raw)), query, hash)
91}
92
93static WINDOWS_PATH: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[A-Za-z]:\\|\\\\").unwrap());
94static URI_PATH: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^([^/\\:]+:)(.+)$").unwrap());
95static DATA_URI_REMAINDER: LazyLock<Regex> =
96 LazyLock::new(|| Regex::new(r"^([^;,]*)(?:;([^,]+))?,(.*)$").unwrap());
97static MODULE_PATH: LazyLock<Regex> =
98 LazyLock::new(|| Regex::new(r"^((?:@[^/]+/)?[^/]+)(.*)$").unwrap());
99
100impl Request {
101 pub fn request(&self) -> Option<RcStr> {
108 Some(match self {
109 Request::Raw {
110 path: Pattern::Constant(path),
111 ..
112 } => path.clone(),
113 Request::Relative {
114 path: Pattern::Constant(path),
115 ..
116 } => path.clone(),
117 Request::Module {
118 module: Pattern::Constant(module),
119 path: Pattern::Constant(path),
120 ..
121 } => format!("{module}{path}").into(),
122 Request::ServerRelative {
123 path: Pattern::Constant(path),
124 ..
125 } => path.clone(),
126 Request::Windows {
127 path: Pattern::Constant(path),
128 ..
129 } => path.clone(),
130 Request::Empty => rcstr!(""),
131 Request::PackageInternal {
132 path: Pattern::Constant(path),
133 ..
134 } => path.clone(),
135 Request::Uri {
136 protocol,
137 remainder,
138 ..
139 } => format!("{protocol}{remainder}").into(),
140 Request::Unknown {
141 path: Pattern::Constant(path),
142 } => path.clone(),
143 _ => return None,
144 })
145 }
146
147 fn parse_ref(request: Pattern) -> Self {
150 match request {
151 Pattern::Dynamic | Pattern::DynamicNoSlash => Request::Dynamic,
152 Pattern::Constant(r) => Request::parse_constant_pattern(r),
153 Pattern::Concatenation(list) => Request::parse_concatenation_pattern(list),
154 Pattern::Alternatives(_) => panic!(
155 "request should be normalized and alternatives should have already been handled.",
156 ),
157 }
158 }
159
160 fn parse_constant_pattern(r: RcStr) -> Self {
161 if r.is_empty() {
162 return Request::Empty;
163 }
164
165 if let Some(remainder) = r.strip_prefix("//") {
166 return Request::Uri {
167 protocol: rcstr!("//"),
168 remainder: remainder.into(),
169 query: RcStr::default(),
170 fragment: RcStr::default(),
171 };
172 }
173
174 if r.starts_with('/') {
175 let (path, query, fragment) = split_off_query_fragment(&r);
176
177 return Request::ServerRelative {
178 path,
179 query,
180 fragment,
181 };
182 }
183
184 if r.starts_with('#') {
185 return Request::PackageInternal {
186 path: Pattern::Constant(r),
187 };
188 }
189
190 if r.starts_with("./") || r.starts_with("../") || r == "." || r == ".." {
191 let (path, query, fragment) = split_off_query_fragment(&r);
192
193 return Request::Relative {
194 path,
195 force_in_lookup_dir: false,
196 query,
197 fragment,
198 };
199 }
200
201 if WINDOWS_PATH.is_match(&r) {
202 let (path, query, fragment) = split_off_query_fragment(&r);
203
204 return Request::Windows {
205 path,
206 query,
207 fragment,
208 };
209 }
210
211 if let Some(caps) = URI_PATH.captures(&r)
212 && let (Some(protocol), Some(remainder)) = (caps.get(1), caps.get(2))
213 {
214 if let Some(caps) = DATA_URI_REMAINDER.captures(remainder.as_str()) {
215 let media_type = caps.get(1).map_or(RcStr::default(), |m| m.as_str().into());
216 let encoding = caps.get(2).map_or(RcStr::default(), |e| e.as_str().into());
217 let data = caps.get(3).map_or(RcStr::default(), |d| d.as_str().into());
218
219 return Request::DataUri {
220 media_type,
221 encoding,
222 data: ResolvedVc::cell(data),
223 };
224 }
225
226 return Request::Uri {
227 protocol: protocol.as_str().into(),
228 remainder: remainder.as_str().into(),
229 query: RcStr::default(),
230 fragment: RcStr::default(),
231 };
232 }
233
234 if let Some((module, path)) = MODULE_PATH
235 .captures(&r)
236 .and_then(|caps| caps.get(1).zip(caps.get(2)))
237 {
238 let (path, query, fragment) = split_off_query_fragment(path.as_str());
239
240 return Request::Module {
241 module: RcStr::from(module.as_str()).into(),
242 path,
243 query,
244 fragment,
245 };
246 }
247
248 Request::Unknown {
249 path: Pattern::Constant(r),
250 }
251 }
252
253 fn parse_concatenation_pattern(list: Vec<Pattern>) -> Self {
254 if list.is_empty() {
255 return Request::Empty;
256 }
257
258 let mut result = Self::parse_ref(list[0].clone());
259
260 for item in list.into_iter().skip(1) {
261 match &mut result {
262 Request::Raw { path, .. } => {
263 path.push(item);
264 }
265 Request::Relative { path, .. } => {
266 path.push(item);
267 }
268 Request::Module { module, path, .. } => {
269 if path.is_empty() && matches!(item, Pattern::Dynamic) {
270 module.push(Pattern::DynamicNoSlash);
278 }
279 path.push(item);
280 }
281 Request::ServerRelative { path, .. } => {
282 path.push(item);
283 }
284 Request::Windows { path, .. } => {
285 path.push(item);
286 }
287 Request::Empty => {
288 result = Self::parse_ref(item);
289 }
290 Request::PackageInternal { path } => {
291 path.push(item);
292 }
293 Request::DataUri { .. } => {
294 result = Request::Dynamic;
295 }
296 Request::Uri { .. } => {
297 result = Request::Dynamic;
298 }
299 Request::Unknown { path } => {
300 path.push(item);
301 }
302 Request::Dynamic => {}
303 Request::Alternatives { .. } => unreachable!(),
304 };
305 }
306
307 result
308 }
309
310 pub fn parse_string(request: RcStr) -> Vc<Self> {
311 Self::parse(request.into())
312 }
313
314 pub fn parse(mut request: Pattern) -> Vc<Self> {
315 request.normalize();
317 Self::parse_inner(request)
318 }
319}
320
321#[turbo_tasks::value_impl]
322impl Request {
323 #[turbo_tasks::function]
324 async fn parse_inner(request: Pattern) -> Result<Vc<Self>> {
325 if let Pattern::Alternatives(alts) = request {
327 Ok(Self::cell(Self::Alternatives {
328 requests: alts
329 .into_iter()
330 .map(|p| Self::parse_inner(p).to_resolved())
334 .try_join()
335 .await?,
336 }))
337 } else {
338 Ok(Self::cell(Self::parse_ref(request)))
339 }
340 }
341
342 #[turbo_tasks::function]
343 pub fn raw(
344 request: Pattern,
345 query: RcStr,
346 fragment: RcStr,
347 force_in_lookup_dir: bool,
348 ) -> Vc<Self> {
349 Self::cell(Request::Raw {
350 path: request,
351 force_in_lookup_dir,
352 query,
353 fragment,
354 })
355 }
356
357 #[turbo_tasks::function]
358 pub fn relative(
359 request: Pattern,
360 query: RcStr,
361 fragment: RcStr,
362 force_in_lookup_dir: bool,
363 ) -> Vc<Self> {
364 Self::cell(Request::Relative {
365 path: request,
366 force_in_lookup_dir,
367 query,
368 fragment,
369 })
370 }
371
372 #[turbo_tasks::function]
373 pub fn module(module: Pattern, path: Pattern, query: RcStr, fragment: RcStr) -> Vc<Self> {
374 Self::cell(Request::Module {
375 module,
376 path,
377 query,
378 fragment,
379 })
380 }
381
382 #[turbo_tasks::function]
383 pub async fn as_relative(self: Vc<Self>) -> Result<Vc<Self>> {
384 let this = self.await?;
385 Ok(match &*this {
386 Request::Empty
387 | Request::Raw { .. }
388 | Request::ServerRelative { .. }
389 | Request::Windows { .. }
390 | Request::Relative { .. }
391 | Request::DataUri { .. }
392 | Request::Uri { .. }
393 | Request::Dynamic => self,
394 Request::Module {
395 module,
396 path,
397 query: _,
398 fragment: _,
399 } => {
400 let mut pat = module.clone();
401 pat.push_front(rcstr!("./").into());
402 pat.push(path.clone());
403 Self::parse(pat)
405 }
406 Request::PackageInternal { path } => {
407 let mut pat = Pattern::Constant(rcstr!("./"));
408 pat.push(path.clone());
409 Self::parse(pat)
410 }
411 Request::Unknown { path } => {
412 let mut pat = Pattern::Constant(rcstr!("./"));
413 pat.push(path.clone());
414 Self::parse(pat)
415 }
416 Request::Alternatives { requests } => {
417 let requests = requests
418 .iter()
419 .copied()
420 .map(|v| *v)
421 .map(Request::as_relative)
422 .map(|v| async move { v.to_resolved().await })
423 .try_join()
424 .await?;
425 Request::Alternatives { requests }.cell()
426 }
427 })
428 }
429
430 #[turbo_tasks::function]
431 pub async fn with_query(self: Vc<Self>, query: RcStr) -> Result<Vc<Self>> {
432 Ok(match &*self.await? {
433 Request::Raw {
434 path,
435 query: _,
436 force_in_lookup_dir,
437 fragment,
438 } => Request::raw(path.clone(), query, fragment.clone(), *force_in_lookup_dir),
439 Request::Relative {
440 path,
441 query: _,
442 force_in_lookup_dir,
443 fragment,
444 } => Request::relative(path.clone(), query, fragment.clone(), *force_in_lookup_dir),
445 Request::Module {
446 module,
447 path,
448 query: _,
449 fragment,
450 } => Request::module(module.clone(), path.clone(), query, fragment.clone()),
451 Request::ServerRelative {
452 path,
453 query: _,
454 fragment,
455 } => Request::ServerRelative {
456 path: path.clone(),
457 query,
458 fragment: fragment.clone(),
459 }
460 .cell(),
461 Request::Windows {
462 path,
463 query: _,
464 fragment,
465 } => Request::Windows {
466 path: path.clone(),
467 query,
468 fragment: fragment.clone(),
469 }
470 .cell(),
471 Request::Empty => self,
472 Request::PackageInternal { .. } => self,
473 Request::DataUri { .. } => self,
474 Request::Uri { .. } => self,
475 Request::Unknown { .. } => self,
476 Request::Dynamic => self,
477 Request::Alternatives { requests } => {
478 let requests = requests
479 .iter()
480 .copied()
481 .map(|req| req.with_query(query.clone()))
482 .map(|v| async move { v.to_resolved().await })
483 .try_join()
484 .await?;
485 Request::Alternatives { requests }.cell()
486 }
487 })
488 }
489
490 #[turbo_tasks::function]
491 pub async fn with_fragment(self: Vc<Self>, fragment: RcStr) -> Result<Vc<Self>> {
492 Ok(match &*self.await? {
493 Request::Raw {
494 path,
495 query,
496 force_in_lookup_dir,
497 fragment: _,
498 } => Request::Raw {
499 path: path.clone(),
500 query: query.clone(),
501 force_in_lookup_dir: *force_in_lookup_dir,
502 fragment,
503 }
504 .cell(),
505 Request::Relative {
506 path,
507 query,
508 force_in_lookup_dir,
509 fragment: _,
510 } => Request::Relative {
511 path: path.clone(),
512 query: query.clone(),
513 force_in_lookup_dir: *force_in_lookup_dir,
514 fragment,
515 }
516 .cell(),
517 Request::Module {
518 module,
519 path,
520 query,
521 fragment: _,
522 } => Request::Module {
523 module: module.clone(),
524 path: path.clone(),
525 query: query.clone(),
526 fragment,
527 }
528 .cell(),
529 Request::ServerRelative {
530 path,
531 query,
532 fragment: _,
533 } => Request::ServerRelative {
534 path: path.clone(),
535 query: query.clone(),
536 fragment,
537 }
538 .cell(),
539 Request::Windows {
540 path,
541 query,
542 fragment: _,
543 } => Request::Windows {
544 path: path.clone(),
545 query: query.clone(),
546 fragment,
547 }
548 .cell(),
549 Request::Empty => self,
550 Request::PackageInternal { .. } => self,
551 Request::DataUri { .. } => self,
552 Request::Uri { .. } => self,
553 Request::Unknown { .. } => self,
554 Request::Dynamic => self,
555 Request::Alternatives { requests } => {
556 let requests = requests
557 .iter()
558 .copied()
559 .map(|req| req.with_fragment(fragment.clone()))
560 .map(|v| async move { v.to_resolved().await })
561 .try_join()
562 .await?;
563 Request::Alternatives { requests }.cell()
564 }
565 })
566 }
567
568 #[turbo_tasks::function]
569 pub async fn append_path(self: Vc<Self>, suffix: RcStr) -> Result<Vc<Self>> {
570 Ok(match &*self.await? {
571 Request::Raw {
572 path,
573 query,
574 force_in_lookup_dir,
575 fragment,
576 } => {
577 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
578 pat.normalize();
579 Self::raw(pat, query.clone(), fragment.clone(), *force_in_lookup_dir)
580 }
581 Request::Relative {
582 path,
583 query,
584 force_in_lookup_dir,
585 fragment,
586 } => {
587 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
588 pat.normalize();
589 Self::relative(pat, query.clone(), fragment.clone(), *force_in_lookup_dir)
590 }
591 Request::Module {
592 module,
593 path,
594 query,
595 fragment,
596 } => {
597 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
598 pat.normalize();
599 Self::module(module.clone(), pat, query.clone(), fragment.clone())
600 }
601 Request::ServerRelative {
602 path,
603 query,
604 fragment,
605 } => {
606 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
607 pat.normalize();
608 Self::ServerRelative {
609 path: pat,
610 query: query.clone(),
611 fragment: fragment.clone(),
612 }
613 .cell()
614 }
615 Request::Windows {
616 path,
617 query,
618 fragment,
619 } => {
620 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
621 pat.normalize();
622 Self::Windows {
623 path: pat,
624 query: query.clone(),
625 fragment: fragment.clone(),
626 }
627 .cell()
628 }
629 Request::Empty => Self::parse(suffix.into()),
630 Request::PackageInternal { path } => {
631 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
632 pat.normalize();
633 Self::PackageInternal { path: pat }.cell()
634 }
635 Request::DataUri {
636 media_type,
637 encoding,
638 data,
639 } => {
640 let data = ResolvedVc::cell(format!("{}{}", data.await?, suffix).into());
641 Self::DataUri {
642 media_type: media_type.clone(),
643 encoding: encoding.clone(),
644 data,
645 }
646 .cell()
647 }
648 Request::Uri {
649 protocol,
650 remainder,
651 query,
652 fragment,
653 } => {
654 let remainder = format!("{remainder}{suffix}").into();
655 Self::Uri {
656 protocol: protocol.clone(),
657 remainder,
658 query: query.clone(),
659 fragment: fragment.clone(),
660 }
661 .cell()
662 }
663 Request::Unknown { path } => {
664 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
665 pat.normalize();
666 Self::Unknown { path: pat }.cell()
667 }
668 Request::Dynamic => self,
669 Request::Alternatives { requests } => {
670 let requests = requests
671 .iter()
672 .map(|req| async { req.append_path(suffix.clone()).to_resolved().await })
673 .try_join()
674 .await?;
675 Request::Alternatives { requests }.cell()
676 }
677 })
678 }
679
680 #[turbo_tasks::function]
681 pub fn query(&self) -> Vc<RcStr> {
682 Vc::cell(match self {
683 Request::Windows { query, .. }
684 | Request::ServerRelative { query, .. }
685 | Request::Module { query, .. }
686 | Request::Relative { query, .. }
687 | Request::Raw { query, .. } => query.clone(),
688 Request::Dynamic
689 | Request::Unknown { .. }
690 | Request::Uri { .. }
691 | Request::DataUri { .. }
692 | Request::PackageInternal { .. }
693 | Request::Empty => RcStr::default(),
694 Request::Alternatives { .. } => RcStr::default(),
696 })
697 }
698
699 #[turbo_tasks::function]
702 pub async fn request_pattern(self: Vc<Self>) -> Result<Vc<Pattern>> {
703 Ok(Pattern::new(match &*self.await? {
704 Request::Raw { path, .. } => path.clone(),
705 Request::Relative { path, .. } => path.clone(),
706 Request::Module { module, path, .. } => {
707 let mut path = path.clone();
708 path.push_front(module.clone());
709 path.normalize();
710 path
711 }
712 Request::ServerRelative { path, .. } => path.clone(),
713 Request::Windows { path, .. } => path.clone(),
714 Request::Empty => Pattern::Constant(rcstr!("")),
715 Request::PackageInternal { path } => path.clone(),
716 Request::DataUri {
717 media_type,
718 encoding,
719 data,
720 } => Pattern::Constant(
721 stringify_data_uri(media_type, encoding, *data)
722 .await?
723 .into(),
724 ),
725 Request::Uri {
726 protocol,
727 remainder,
728 ..
729 } => Pattern::Constant(format!("{protocol}{remainder}").into()),
730 Request::Unknown { path } => path.clone(),
731 Request::Dynamic => Pattern::Dynamic,
732 Request::Alternatives { requests } => Pattern::Alternatives(
733 requests
734 .iter()
735 .map(async |r: &ResolvedVc<Request>| -> Result<Pattern> {
736 Ok(r.request_pattern().owned().await?)
737 })
738 .try_join()
739 .await?,
740 ),
741 }))
742 }
743}
744
745#[turbo_tasks::value_impl]
746impl ValueToString for Request {
747 #[turbo_tasks::function]
748 async fn to_string(&self) -> Result<Vc<RcStr>> {
749 Ok(Vc::cell(match self {
750 Request::Raw {
751 path,
752 force_in_lookup_dir,
753 ..
754 } => {
755 if *force_in_lookup_dir {
756 format!("in-lookup-dir {}", path.describe_as_string()).into()
757 } else {
758 path.describe_as_string().into()
759 }
760 }
761 Request::Relative {
762 path,
763 force_in_lookup_dir,
764 ..
765 } => {
766 if *force_in_lookup_dir {
767 format!("relative-in-lookup-dir {}", path.describe_as_string()).into()
768 } else {
769 format!("relative {}", path.describe_as_string()).into()
770 }
771 }
772 Request::Module { module, path, .. } => {
773 if path.could_match_others("") {
774 format!(
775 "module {} with subpath {}",
776 module.describe_as_string(),
777 path.describe_as_string()
778 )
779 .into()
780 } else {
781 format!("module \"{}\"", module.describe_as_string()).into()
782 }
783 }
784 Request::ServerRelative { path, .. } => {
785 format!("server relative {}", path.describe_as_string()).into()
786 }
787 Request::Windows { path, .. } => {
788 format!("windows {}", path.describe_as_string()).into()
789 }
790 Request::Empty => rcstr!("empty"),
791 Request::PackageInternal { path } => {
792 format!("package internal {}", path.describe_as_string()).into()
793 }
794 Request::DataUri {
795 media_type,
796 encoding,
797 data,
798 } => format!(
799 "data uri \"{media_type}\" \"{encoding}\" \"{}\"",
800 data.await?
801 )
802 .into(),
803 Request::Uri {
804 protocol,
805 remainder,
806 ..
807 } => format!("uri \"{protocol}\" \"{remainder}\"").into(),
808 Request::Unknown { path } => format!("unknown {}", path.describe_as_string()).into(),
809 Request::Dynamic => rcstr!("dynamic"),
810 Request::Alternatives { requests } => {
811 let vec = requests.iter().map(|i| i.to_string()).try_join().await?;
812 vec.iter()
813 .map(|r| r.as_str())
814 .collect::<Vec<_>>()
815 .join(" or ")
816 .into()
817 }
818 }))
819 }
820}
821
822pub async fn stringify_data_uri(
823 media_type: &RcStr,
824 encoding: &RcStr,
825 data: ResolvedVc<RcStr>,
826) -> Result<String> {
827 Ok(format!(
828 "data:{media_type}{}{encoding},{}",
829 if encoding.is_empty() { "" } else { ";" },
830 data.await?
831 ))
832}
833
834#[cfg(test)]
835mod tests {
836 use super::*;
837
838 #[test]
839 fn test_parse_module() {
840 assert_eq!(
841 Request::Module {
842 module: rcstr!("foo").into(),
843 path: rcstr!("").into(),
844 query: rcstr!(""),
845 fragment: rcstr!(""),
846 },
847 Request::parse_ref(rcstr!("foo").into())
848 );
849 assert_eq!(
850 Request::Module {
851 module: rcstr!("@org/foo").into(),
852 path: rcstr!("").into(),
853 query: rcstr!(""),
854 fragment: rcstr!(""),
855 },
856 Request::parse_ref(rcstr!("@org/foo").into())
857 );
858
859 assert_eq!(
860 Request::Module {
861 module: Pattern::Concatenation(vec![
862 Pattern::Constant(rcstr!("foo-")),
863 Pattern::DynamicNoSlash,
864 ]),
865 path: Pattern::Dynamic,
866 query: rcstr!(""),
867 fragment: rcstr!(""),
868 },
869 Request::parse_ref(Pattern::Concatenation(vec![
870 Pattern::Constant(rcstr!("foo-")),
871 Pattern::Dynamic,
872 ]))
873 );
874
875 assert_eq!(
876 Request::Module {
877 module: Pattern::Concatenation(vec![
878 Pattern::Constant(rcstr!("foo-")),
879 Pattern::DynamicNoSlash,
880 ]),
881 path: Pattern::Concatenation(vec![
882 Pattern::Dynamic,
883 Pattern::Constant(rcstr!("/file")),
884 ]),
885 query: rcstr!(""),
886 fragment: rcstr!(""),
887 },
888 Request::parse_ref(Pattern::Concatenation(vec![
889 Pattern::Constant(rcstr!("foo-")),
890 Pattern::Dynamic,
891 Pattern::Constant(rcstr!("/file")),
892 ]))
893 );
894 assert_eq!(
895 Request::Module {
896 module: Pattern::Concatenation(vec![
897 Pattern::Constant(rcstr!("foo-")),
898 Pattern::DynamicNoSlash,
899 ]),
900 path: Pattern::Concatenation(vec![
901 Pattern::Dynamic,
902 Pattern::Constant(rcstr!("/file")),
903 Pattern::Dynamic,
904 Pattern::Constant(rcstr!("sub")),
905 ]),
906 query: rcstr!(""),
907 fragment: rcstr!(""),
908 },
909 Request::parse_ref(Pattern::Concatenation(vec![
910 Pattern::Constant(rcstr!("foo-")),
911 Pattern::Dynamic,
912 Pattern::Constant(rcstr!("/file")),
913 Pattern::Dynamic,
914 Pattern::Constant(rcstr!("sub")),
915 ]))
916 );
917
918 }
955
956 #[test]
957 fn test_split_query_fragment() {
958 assert_eq!(
959 (
960 Pattern::Constant(rcstr!("foo")),
961 RcStr::default(),
962 RcStr::default()
963 ),
964 split_off_query_fragment("foo")
965 );
966 assert_eq!(
969 (
970 Pattern::Constant(rcstr!("foo")),
971 rcstr!("?"),
972 RcStr::default()
973 ),
974 split_off_query_fragment("foo?")
975 );
976 assert_eq!(
977 (
978 Pattern::Constant(rcstr!("foo")),
979 RcStr::default(),
980 rcstr!("#")
981 ),
982 split_off_query_fragment("foo#")
983 );
984 assert_eq!(
985 (
986 Pattern::Constant(rcstr!("foo")),
987 rcstr!("?bar=baz"),
988 RcStr::default()
989 ),
990 split_off_query_fragment("foo?bar=baz")
991 );
992 assert_eq!(
993 (
994 Pattern::Constant(rcstr!("foo")),
995 RcStr::default(),
996 rcstr!("#stuff?bar=baz")
997 ),
998 split_off_query_fragment("foo#stuff?bar=baz")
999 );
1000
1001 assert_eq!(
1002 (
1003 Pattern::Constant(rcstr!("foo")),
1004 rcstr!("?bar=baz"),
1005 rcstr!("#stuff")
1006 ),
1007 split_off_query_fragment("foo?bar=baz#stuff")
1008 );
1009 }
1010}