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::Unknown { path } => {
294 path.push(item);
295 }
296 Request::DataUri { .. } | Request::Uri { .. } | Request::Dynamic => {
297 return Request::Dynamic;
298 }
299 Request::Alternatives { .. } => unreachable!(),
300 };
301 }
302 if let Request::Relative {
303 path,
304 fragment,
305 query,
306 ..
307 } = &mut result
308 && fragment.is_empty()
309 && query.is_empty()
310 && let Pattern::Concatenation(parts) = path
311 && let Pattern::Constant(last_part) = parts.last().unwrap()
312 {
313 let (prefix, new_query, new_fragment) = split_off_query_fragment(last_part);
314
315 *parts.last_mut().unwrap() = prefix;
316 *query = new_query;
317 *fragment = new_fragment;
318 path.normalize();
320 }
321
322 result
323 }
324
325 pub fn parse_string(request: RcStr) -> Vc<Self> {
326 Self::parse(request.into())
327 }
328
329 pub fn parse(mut request: Pattern) -> Vc<Self> {
330 request.normalize();
332 Self::parse_inner(request)
333 }
334}
335
336#[turbo_tasks::value_impl]
337impl Request {
338 #[turbo_tasks::function]
339 async fn parse_inner(request: Pattern) -> Result<Vc<Self>> {
340 if let Pattern::Alternatives(alts) = request {
342 Ok(Self::cell(Self::Alternatives {
343 requests: alts
344 .into_iter()
345 .map(|p| Self::parse_inner(p).to_resolved())
349 .try_join()
350 .await?,
351 }))
352 } else {
353 Ok(Self::cell(Self::parse_ref(request)))
354 }
355 }
356
357 #[turbo_tasks::function]
358 pub fn raw(
359 request: Pattern,
360 query: RcStr,
361 fragment: RcStr,
362 force_in_lookup_dir: bool,
363 ) -> Vc<Self> {
364 Self::cell(Request::Raw {
365 path: request,
366 force_in_lookup_dir,
367 query,
368 fragment,
369 })
370 }
371
372 #[turbo_tasks::function]
373 pub fn relative(
374 request: Pattern,
375 query: RcStr,
376 fragment: RcStr,
377 force_in_lookup_dir: bool,
378 ) -> Vc<Self> {
379 Self::cell(Request::Relative {
380 path: request,
381 force_in_lookup_dir,
382 query,
383 fragment,
384 })
385 }
386
387 #[turbo_tasks::function]
388 pub fn module(module: Pattern, path: Pattern, query: RcStr, fragment: RcStr) -> Vc<Self> {
389 Self::cell(Request::Module {
390 module,
391 path,
392 query,
393 fragment,
394 })
395 }
396
397 #[turbo_tasks::function]
398 pub async fn as_relative(self: Vc<Self>) -> Result<Vc<Self>> {
399 let this = self.await?;
400 Ok(match &*this {
401 Request::Empty
402 | Request::Raw { .. }
403 | Request::ServerRelative { .. }
404 | Request::Windows { .. }
405 | Request::Relative { .. }
406 | Request::DataUri { .. }
407 | Request::Uri { .. }
408 | Request::Dynamic => self,
409 Request::Module {
410 module,
411 path,
412 query: _,
413 fragment: _,
414 } => {
415 let mut pat = module.clone();
416 pat.push_front(rcstr!("./").into());
417 pat.push(path.clone());
418 Self::parse(pat)
420 }
421 Request::PackageInternal { path } => {
422 let mut pat = Pattern::Constant(rcstr!("./"));
423 pat.push(path.clone());
424 Self::parse(pat)
425 }
426 Request::Unknown { path } => {
427 let mut pat = Pattern::Constant(rcstr!("./"));
428 pat.push(path.clone());
429 Self::parse(pat)
430 }
431 Request::Alternatives { requests } => {
432 let requests = requests
433 .iter()
434 .copied()
435 .map(|v| *v)
436 .map(Request::as_relative)
437 .map(|v| async move { v.to_resolved().await })
438 .try_join()
439 .await?;
440 Request::Alternatives { requests }.cell()
441 }
442 })
443 }
444
445 #[turbo_tasks::function]
446 pub async fn with_query(self: Vc<Self>, query: RcStr) -> Result<Vc<Self>> {
447 Ok(match &*self.await? {
448 Request::Raw {
449 path,
450 query: _,
451 force_in_lookup_dir,
452 fragment,
453 } => Request::raw(path.clone(), query, fragment.clone(), *force_in_lookup_dir),
454 Request::Relative {
455 path,
456 query: _,
457 force_in_lookup_dir,
458 fragment,
459 } => Request::relative(path.clone(), query, fragment.clone(), *force_in_lookup_dir),
460 Request::Module {
461 module,
462 path,
463 query: _,
464 fragment,
465 } => Request::module(module.clone(), path.clone(), query, fragment.clone()),
466 Request::ServerRelative {
467 path,
468 query: _,
469 fragment,
470 } => Request::ServerRelative {
471 path: path.clone(),
472 query,
473 fragment: fragment.clone(),
474 }
475 .cell(),
476 Request::Windows {
477 path,
478 query: _,
479 fragment,
480 } => Request::Windows {
481 path: path.clone(),
482 query,
483 fragment: fragment.clone(),
484 }
485 .cell(),
486 Request::Empty => self,
487 Request::PackageInternal { .. } => self,
488 Request::DataUri { .. } => self,
489 Request::Uri { .. } => self,
490 Request::Unknown { .. } => self,
491 Request::Dynamic => self,
492 Request::Alternatives { requests } => {
493 let requests = requests
494 .iter()
495 .copied()
496 .map(|req| req.with_query(query.clone()))
497 .map(|v| async move { v.to_resolved().await })
498 .try_join()
499 .await?;
500 Request::Alternatives { requests }.cell()
501 }
502 })
503 }
504
505 #[turbo_tasks::function]
506 pub async fn with_fragment(self: Vc<Self>, fragment: RcStr) -> Result<Vc<Self>> {
507 Ok(match &*self.await? {
508 Request::Raw {
509 path,
510 query,
511 force_in_lookup_dir,
512 fragment: _,
513 } => Request::Raw {
514 path: path.clone(),
515 query: query.clone(),
516 force_in_lookup_dir: *force_in_lookup_dir,
517 fragment,
518 }
519 .cell(),
520 Request::Relative {
521 path,
522 query,
523 force_in_lookup_dir,
524 fragment: _,
525 } => Request::Relative {
526 path: path.clone(),
527 query: query.clone(),
528 force_in_lookup_dir: *force_in_lookup_dir,
529 fragment,
530 }
531 .cell(),
532 Request::Module {
533 module,
534 path,
535 query,
536 fragment: _,
537 } => Request::Module {
538 module: module.clone(),
539 path: path.clone(),
540 query: query.clone(),
541 fragment,
542 }
543 .cell(),
544 Request::ServerRelative {
545 path,
546 query,
547 fragment: _,
548 } => Request::ServerRelative {
549 path: path.clone(),
550 query: query.clone(),
551 fragment,
552 }
553 .cell(),
554 Request::Windows {
555 path,
556 query,
557 fragment: _,
558 } => Request::Windows {
559 path: path.clone(),
560 query: query.clone(),
561 fragment,
562 }
563 .cell(),
564 Request::Empty => self,
565 Request::PackageInternal { .. } => self,
566 Request::DataUri { .. } => self,
567 Request::Uri { .. } => self,
568 Request::Unknown { .. } => self,
569 Request::Dynamic => self,
570 Request::Alternatives { requests } => {
571 let requests = requests
572 .iter()
573 .copied()
574 .map(|req| req.with_fragment(fragment.clone()))
575 .map(|v| async move { v.to_resolved().await })
576 .try_join()
577 .await?;
578 Request::Alternatives { requests }.cell()
579 }
580 })
581 }
582
583 #[turbo_tasks::function]
584 pub async fn append_path(self: Vc<Self>, suffix: RcStr) -> Result<Vc<Self>> {
585 Ok(match &*self.await? {
586 Request::Raw {
587 path,
588 query,
589 force_in_lookup_dir,
590 fragment,
591 } => {
592 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
593 pat.normalize();
594 Self::raw(pat, query.clone(), fragment.clone(), *force_in_lookup_dir)
595 }
596 Request::Relative {
597 path,
598 query,
599 force_in_lookup_dir,
600 fragment,
601 } => {
602 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
603 pat.normalize();
604 Self::relative(pat, query.clone(), fragment.clone(), *force_in_lookup_dir)
605 }
606 Request::Module {
607 module,
608 path,
609 query,
610 fragment,
611 } => {
612 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
613 pat.normalize();
614 Self::module(module.clone(), pat, query.clone(), fragment.clone())
615 }
616 Request::ServerRelative {
617 path,
618 query,
619 fragment,
620 } => {
621 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
622 pat.normalize();
623 Self::ServerRelative {
624 path: pat,
625 query: query.clone(),
626 fragment: fragment.clone(),
627 }
628 .cell()
629 }
630 Request::Windows {
631 path,
632 query,
633 fragment,
634 } => {
635 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
636 pat.normalize();
637 Self::Windows {
638 path: pat,
639 query: query.clone(),
640 fragment: fragment.clone(),
641 }
642 .cell()
643 }
644 Request::Empty => Self::parse(suffix.into()),
645 Request::PackageInternal { path } => {
646 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
647 pat.normalize();
648 Self::PackageInternal { path: pat }.cell()
649 }
650 Request::DataUri {
651 media_type,
652 encoding,
653 data,
654 } => {
655 let data = ResolvedVc::cell(format!("{}{}", data.await?, suffix).into());
656 Self::DataUri {
657 media_type: media_type.clone(),
658 encoding: encoding.clone(),
659 data,
660 }
661 .cell()
662 }
663 Request::Uri {
664 protocol,
665 remainder,
666 query,
667 fragment,
668 } => {
669 let remainder = format!("{remainder}{suffix}").into();
670 Self::Uri {
671 protocol: protocol.clone(),
672 remainder,
673 query: query.clone(),
674 fragment: fragment.clone(),
675 }
676 .cell()
677 }
678 Request::Unknown { path } => {
679 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
680 pat.normalize();
681 Self::Unknown { path: pat }.cell()
682 }
683 Request::Dynamic => self,
684 Request::Alternatives { requests } => {
685 let requests = requests
686 .iter()
687 .map(|req| async { req.append_path(suffix.clone()).to_resolved().await })
688 .try_join()
689 .await?;
690 Request::Alternatives { requests }.cell()
691 }
692 })
693 }
694
695 #[turbo_tasks::function]
696 pub fn query(&self) -> Vc<RcStr> {
697 Vc::cell(match self {
698 Request::Windows { query, .. }
699 | Request::ServerRelative { query, .. }
700 | Request::Module { query, .. }
701 | Request::Relative { query, .. }
702 | Request::Raw { query, .. } => query.clone(),
703 Request::Dynamic
704 | Request::Unknown { .. }
705 | Request::Uri { .. }
706 | Request::DataUri { .. }
707 | Request::PackageInternal { .. }
708 | Request::Empty => RcStr::default(),
709 Request::Alternatives { .. } => RcStr::default(),
711 })
712 }
713
714 #[turbo_tasks::function]
717 pub async fn request_pattern(self: Vc<Self>) -> Result<Vc<Pattern>> {
718 Ok(Pattern::new(match &*self.await? {
719 Request::Raw { path, .. } => path.clone(),
720 Request::Relative { path, .. } => path.clone(),
721 Request::Module { module, path, .. } => {
722 let mut path = path.clone();
723 path.push_front(module.clone());
724 path.normalize();
725 path
726 }
727 Request::ServerRelative { path, .. } => path.clone(),
728 Request::Windows { path, .. } => path.clone(),
729 Request::Empty => Pattern::Constant(rcstr!("")),
730 Request::PackageInternal { path } => path.clone(),
731 Request::DataUri {
732 media_type,
733 encoding,
734 data,
735 } => Pattern::Constant(
736 stringify_data_uri(media_type, encoding, *data)
737 .await?
738 .into(),
739 ),
740 Request::Uri {
741 protocol,
742 remainder,
743 ..
744 } => Pattern::Constant(format!("{protocol}{remainder}").into()),
745 Request::Unknown { path } => path.clone(),
746 Request::Dynamic => Pattern::Dynamic,
747 Request::Alternatives { requests } => Pattern::Alternatives(
748 requests
749 .iter()
750 .map(async |r: &ResolvedVc<Request>| -> Result<Pattern> {
751 Ok(r.request_pattern().owned().await?)
752 })
753 .try_join()
754 .await?,
755 ),
756 }))
757 }
758}
759
760#[turbo_tasks::value_impl]
761impl ValueToString for Request {
762 #[turbo_tasks::function]
763 async fn to_string(&self) -> Result<Vc<RcStr>> {
764 Ok(Vc::cell(match self {
765 Request::Raw {
766 path,
767 force_in_lookup_dir,
768 ..
769 } => {
770 if *force_in_lookup_dir {
771 format!("in-lookup-dir {}", path.describe_as_string()).into()
772 } else {
773 path.describe_as_string().into()
774 }
775 }
776 Request::Relative {
777 path,
778 force_in_lookup_dir,
779 ..
780 } => {
781 if *force_in_lookup_dir {
782 format!("relative-in-lookup-dir {}", path.describe_as_string()).into()
783 } else {
784 format!("relative {}", path.describe_as_string()).into()
785 }
786 }
787 Request::Module { module, path, .. } => {
788 if path.could_match_others("") {
789 format!(
790 "module {} with subpath {}",
791 module.describe_as_string(),
792 path.describe_as_string()
793 )
794 .into()
795 } else {
796 format!("module \"{}\"", module.describe_as_string()).into()
797 }
798 }
799 Request::ServerRelative { path, .. } => {
800 format!("server relative {}", path.describe_as_string()).into()
801 }
802 Request::Windows { path, .. } => {
803 format!("windows {}", path.describe_as_string()).into()
804 }
805 Request::Empty => rcstr!("empty"),
806 Request::PackageInternal { path } => {
807 format!("package internal {}", path.describe_as_string()).into()
808 }
809 Request::DataUri {
810 media_type,
811 encoding,
812 data,
813 } => format!(
814 "data uri \"{media_type}\" \"{encoding}\" \"{}\"",
815 data.await?
816 )
817 .into(),
818 Request::Uri {
819 protocol,
820 remainder,
821 ..
822 } => format!("uri \"{protocol}\" \"{remainder}\"").into(),
823 Request::Unknown { path } => format!("unknown {}", path.describe_as_string()).into(),
824 Request::Dynamic => rcstr!("dynamic"),
825 Request::Alternatives { requests } => {
826 let vec = requests.iter().map(|i| i.to_string()).try_join().await?;
827 vec.iter()
828 .map(|r| r.as_str())
829 .collect::<Vec<_>>()
830 .join(" or ")
831 .into()
832 }
833 }))
834 }
835}
836
837pub async fn stringify_data_uri(
838 media_type: &RcStr,
839 encoding: &RcStr,
840 data: ResolvedVc<RcStr>,
841) -> Result<String> {
842 Ok(format!(
843 "data:{media_type}{}{encoding},{}",
844 if encoding.is_empty() { "" } else { ";" },
845 data.await?
846 ))
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852
853 #[test]
854 fn test_parse_module() {
855 assert_eq!(
856 Request::Module {
857 module: rcstr!("foo").into(),
858 path: rcstr!("").into(),
859 query: rcstr!(""),
860 fragment: rcstr!(""),
861 },
862 Request::parse_ref(rcstr!("foo").into())
863 );
864 assert_eq!(
865 Request::Module {
866 module: rcstr!("@org/foo").into(),
867 path: rcstr!("").into(),
868 query: rcstr!(""),
869 fragment: rcstr!(""),
870 },
871 Request::parse_ref(rcstr!("@org/foo").into())
872 );
873
874 assert_eq!(
875 Request::Module {
876 module: Pattern::Concatenation(vec![
877 Pattern::Constant(rcstr!("foo-")),
878 Pattern::DynamicNoSlash,
879 ]),
880 path: Pattern::Dynamic,
881 query: rcstr!(""),
882 fragment: rcstr!(""),
883 },
884 Request::parse_ref(Pattern::Concatenation(vec![
885 Pattern::Constant(rcstr!("foo-")),
886 Pattern::Dynamic,
887 ]))
888 );
889
890 assert_eq!(
891 Request::Module {
892 module: Pattern::Concatenation(vec![
893 Pattern::Constant(rcstr!("foo-")),
894 Pattern::DynamicNoSlash,
895 ]),
896 path: Pattern::Concatenation(vec![
897 Pattern::Dynamic,
898 Pattern::Constant(rcstr!("/file")),
899 ]),
900 query: rcstr!(""),
901 fragment: rcstr!(""),
902 },
903 Request::parse_ref(Pattern::Concatenation(vec![
904 Pattern::Constant(rcstr!("foo-")),
905 Pattern::Dynamic,
906 Pattern::Constant(rcstr!("/file")),
907 ]))
908 );
909 assert_eq!(
910 Request::Module {
911 module: Pattern::Concatenation(vec![
912 Pattern::Constant(rcstr!("foo-")),
913 Pattern::DynamicNoSlash,
914 ]),
915 path: Pattern::Concatenation(vec![
916 Pattern::Dynamic,
917 Pattern::Constant(rcstr!("/file")),
918 Pattern::Dynamic,
919 Pattern::Constant(rcstr!("sub")),
920 ]),
921 query: rcstr!(""),
922 fragment: rcstr!(""),
923 },
924 Request::parse_ref(Pattern::Concatenation(vec![
925 Pattern::Constant(rcstr!("foo-")),
926 Pattern::Dynamic,
927 Pattern::Constant(rcstr!("/file")),
928 Pattern::Dynamic,
929 Pattern::Constant(rcstr!("sub")),
930 ]))
931 );
932
933 }
970
971 #[test]
972 fn test_split_query_fragment() {
973 assert_eq!(
974 (
975 Pattern::Constant(rcstr!("foo")),
976 RcStr::default(),
977 RcStr::default()
978 ),
979 split_off_query_fragment("foo")
980 );
981 assert_eq!(
984 (
985 Pattern::Constant(rcstr!("foo")),
986 rcstr!("?"),
987 RcStr::default()
988 ),
989 split_off_query_fragment("foo?")
990 );
991 assert_eq!(
992 (
993 Pattern::Constant(rcstr!("foo")),
994 RcStr::default(),
995 rcstr!("#")
996 ),
997 split_off_query_fragment("foo#")
998 );
999 assert_eq!(
1000 (
1001 Pattern::Constant(rcstr!("foo")),
1002 rcstr!("?bar=baz"),
1003 RcStr::default()
1004 ),
1005 split_off_query_fragment("foo?bar=baz")
1006 );
1007 assert_eq!(
1008 (
1009 Pattern::Constant(rcstr!("foo")),
1010 RcStr::default(),
1011 rcstr!("#stuff?bar=baz")
1012 ),
1013 split_off_query_fragment("foo#stuff?bar=baz")
1014 );
1015
1016 assert_eq!(
1017 (
1018 Pattern::Constant(rcstr!("foo")),
1019 rcstr!("?bar=baz"),
1020 rcstr!("#stuff")
1021 ),
1022 split_off_query_fragment("foo?bar=baz#stuff")
1023 );
1024 }
1025}