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