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: RcStr,
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> {
106 Some(match self {
107 Request::Raw {
108 path: Pattern::Constant(path),
109 ..
110 } => path.clone(),
111 Request::Relative {
112 path: Pattern::Constant(path),
113 ..
114 } => path.clone(),
115 Request::Module {
116 module,
117 path: Pattern::Constant(path),
118 ..
119 } => format!("{module}{path}").into(),
120 Request::ServerRelative {
121 path: Pattern::Constant(path),
122 ..
123 } => path.clone(),
124 Request::Windows {
125 path: Pattern::Constant(path),
126 ..
127 } => path.clone(),
128 Request::Empty => rcstr!(""),
129 Request::PackageInternal {
130 path: Pattern::Constant(path),
131 ..
132 } => path.clone(),
133 Request::Uri {
134 protocol,
135 remainder,
136 ..
137 } => format!("{protocol}{remainder}").into(),
138 Request::Unknown {
139 path: Pattern::Constant(path),
140 } => path.clone(),
141 _ => return None,
142 })
143 }
144
145 fn parse_ref(request: Pattern) -> Self {
148 match request {
149 Pattern::Dynamic => Request::Dynamic,
150 Pattern::Constant(r) => Request::parse_constant_pattern(r),
151 Pattern::Concatenation(list) => Request::parse_concatenation_pattern(list),
152 Pattern::Alternatives(_) => panic!(
153 "request should be normalized and alternatives should have already been handled.",
154 ),
155 }
156 }
157
158 fn parse_constant_pattern(r: RcStr) -> Self {
159 if r.is_empty() {
160 return Request::Empty;
161 }
162
163 if let Some(remainder) = r.strip_prefix("//") {
164 return Request::Uri {
165 protocol: rcstr!("//"),
166 remainder: remainder.into(),
167 query: RcStr::default(),
168 fragment: RcStr::default(),
169 };
170 }
171
172 if r.starts_with('/') {
173 let (path, query, fragment) = split_off_query_fragment(&r);
174
175 return Request::ServerRelative {
176 path,
177 query,
178 fragment,
179 };
180 }
181
182 if r.starts_with('#') {
183 return Request::PackageInternal {
184 path: Pattern::Constant(r),
185 };
186 }
187
188 if r.starts_with("./") || r.starts_with("../") || r == "." || r == ".." {
189 let (path, query, fragment) = split_off_query_fragment(&r);
190
191 return Request::Relative {
192 path,
193 force_in_lookup_dir: false,
194 query,
195 fragment,
196 };
197 }
198
199 if WINDOWS_PATH.is_match(&r) {
200 let (path, query, fragment) = split_off_query_fragment(&r);
201
202 return Request::Windows {
203 path,
204 query,
205 fragment,
206 };
207 }
208
209 if let Some(caps) = URI_PATH.captures(&r)
210 && let (Some(protocol), Some(remainder)) = (caps.get(1), caps.get(2))
211 {
212 if let Some(caps) = DATA_URI_REMAINDER.captures(remainder.as_str()) {
213 let media_type = caps.get(1).map_or(RcStr::default(), |m| m.as_str().into());
214 let encoding = caps.get(2).map_or(RcStr::default(), |e| e.as_str().into());
215 let data = caps.get(3).map_or(RcStr::default(), |d| d.as_str().into());
216
217 return Request::DataUri {
218 media_type,
219 encoding,
220 data: ResolvedVc::cell(data),
221 };
222 }
223
224 return Request::Uri {
225 protocol: protocol.as_str().into(),
226 remainder: remainder.as_str().into(),
227 query: RcStr::default(),
228 fragment: RcStr::default(),
229 };
230 }
231
232 if let Some((module, path)) = MODULE_PATH
233 .captures(&r)
234 .and_then(|caps| caps.get(1).zip(caps.get(2)))
235 {
236 let (path, query, fragment) = split_off_query_fragment(path.as_str());
237
238 return Request::Module {
239 module: module.as_str().into(),
240 path,
241 query,
242 fragment,
243 };
244 }
245
246 Request::Unknown {
247 path: Pattern::Constant(r),
248 }
249 }
250
251 fn parse_concatenation_pattern(list: Vec<Pattern>) -> Self {
252 if list.is_empty() {
253 return Request::Empty;
254 }
255
256 let mut result = Self::parse_ref(list[0].clone());
257
258 for item in list.into_iter().skip(1) {
259 match &mut result {
260 Request::Raw { path, .. } => {
261 path.push(item);
262 }
263 Request::Relative { path, .. } => {
264 path.push(item);
265 }
266 Request::Module { path, .. } => {
267 path.push(item);
268 }
269 Request::ServerRelative { path, .. } => {
270 path.push(item);
271 }
272 Request::Windows { path, .. } => {
273 path.push(item);
274 }
275 Request::Empty => {
276 result = Self::parse_ref(item);
277 }
278 Request::PackageInternal { path } => {
279 path.push(item);
280 }
281 Request::DataUri { .. } => {
282 result = Request::Dynamic;
283 }
284 Request::Uri { .. } => {
285 result = Request::Dynamic;
286 }
287 Request::Unknown { path } => {
288 path.push(item);
289 }
290 Request::Dynamic => {}
291 Request::Alternatives { .. } => unreachable!(),
292 };
293 }
294
295 result
296 }
297
298 pub fn parse_string(request: RcStr) -> Vc<Self> {
299 Self::parse(request.into())
300 }
301
302 pub fn parse(mut request: Pattern) -> Vc<Self> {
303 request.normalize();
305 Self::parse_inner(request)
306 }
307}
308
309#[turbo_tasks::value_impl]
310impl Request {
311 #[turbo_tasks::function]
312 async fn parse_inner(request: Pattern) -> Result<Vc<Self>> {
313 if let Pattern::Alternatives(alts) = request {
315 Ok(Self::cell(Self::Alternatives {
316 requests: alts
317 .into_iter()
318 .map(|p| Self::parse_inner(p).to_resolved())
322 .try_join()
323 .await?,
324 }))
325 } else {
326 Ok(Self::cell(Self::parse_ref(request)))
327 }
328 }
329
330 #[turbo_tasks::function]
331 pub fn raw(
332 request: Pattern,
333 query: RcStr,
334 fragment: RcStr,
335 force_in_lookup_dir: bool,
336 ) -> Vc<Self> {
337 Self::cell(Request::Raw {
338 path: request,
339 force_in_lookup_dir,
340 query,
341 fragment,
342 })
343 }
344
345 #[turbo_tasks::function]
346 pub fn relative(
347 request: Pattern,
348 query: RcStr,
349 fragment: RcStr,
350 force_in_lookup_dir: bool,
351 ) -> Vc<Self> {
352 Self::cell(Request::Relative {
353 path: request,
354 force_in_lookup_dir,
355 query,
356 fragment,
357 })
358 }
359
360 #[turbo_tasks::function]
361 pub fn module(module: RcStr, path: Pattern, query: RcStr, fragment: RcStr) -> Vc<Self> {
362 Self::cell(Request::Module {
363 module,
364 path,
365 query,
366 fragment,
367 })
368 }
369
370 #[turbo_tasks::function]
371 pub async fn as_relative(self: Vc<Self>) -> Result<Vc<Self>> {
372 let this = self.await?;
373 Ok(match &*this {
374 Request::Empty
375 | Request::Raw { .. }
376 | Request::ServerRelative { .. }
377 | Request::Windows { .. }
378 | Request::Relative { .. }
379 | Request::DataUri { .. }
380 | Request::Uri { .. }
381 | Request::Dynamic => self,
382 Request::Module {
383 module,
384 path,
385 query: _,
386 fragment: _,
387 } => {
388 let mut pat = Pattern::Constant(format!("./{module}").into());
389 pat.push(path.clone());
390 Self::parse(pat)
392 }
393 Request::PackageInternal { path } => {
394 let mut pat = Pattern::Constant(rcstr!("./"));
395 pat.push(path.clone());
396 Self::parse(pat)
397 }
398 Request::Unknown { path } => {
399 let mut pat = Pattern::Constant(rcstr!("./"));
400 pat.push(path.clone());
401 Self::parse(pat)
402 }
403 Request::Alternatives { requests } => {
404 let requests = requests
405 .iter()
406 .copied()
407 .map(|v| *v)
408 .map(Request::as_relative)
409 .map(|v| async move { v.to_resolved().await })
410 .try_join()
411 .await?;
412 Request::Alternatives { requests }.cell()
413 }
414 })
415 }
416
417 #[turbo_tasks::function]
418 pub async fn with_query(self: Vc<Self>, query: RcStr) -> Result<Vc<Self>> {
419 Ok(match &*self.await? {
420 Request::Raw {
421 path,
422 query: _,
423 force_in_lookup_dir,
424 fragment,
425 } => Request::raw(path.clone(), query, fragment.clone(), *force_in_lookup_dir),
426 Request::Relative {
427 path,
428 query: _,
429 force_in_lookup_dir,
430 fragment,
431 } => Request::relative(path.clone(), query, fragment.clone(), *force_in_lookup_dir),
432 Request::Module {
433 module,
434 path,
435 query: _,
436 fragment,
437 } => Request::module(module.clone(), path.clone(), query, fragment.clone()),
438 Request::ServerRelative {
439 path,
440 query: _,
441 fragment,
442 } => Request::ServerRelative {
443 path: path.clone(),
444 query,
445 fragment: fragment.clone(),
446 }
447 .cell(),
448 Request::Windows {
449 path,
450 query: _,
451 fragment,
452 } => Request::Windows {
453 path: path.clone(),
454 query,
455 fragment: fragment.clone(),
456 }
457 .cell(),
458 Request::Empty => self,
459 Request::PackageInternal { .. } => self,
460 Request::DataUri { .. } => self,
461 Request::Uri { .. } => self,
462 Request::Unknown { .. } => self,
463 Request::Dynamic => self,
464 Request::Alternatives { requests } => {
465 let requests = requests
466 .iter()
467 .copied()
468 .map(|req| req.with_query(query.clone()))
469 .map(|v| async move { v.to_resolved().await })
470 .try_join()
471 .await?;
472 Request::Alternatives { requests }.cell()
473 }
474 })
475 }
476
477 #[turbo_tasks::function]
478 pub async fn with_fragment(self: Vc<Self>, fragment: RcStr) -> Result<Vc<Self>> {
479 Ok(match &*self.await? {
480 Request::Raw {
481 path,
482 query,
483 force_in_lookup_dir,
484 fragment: _,
485 } => Request::Raw {
486 path: path.clone(),
487 query: query.clone(),
488 force_in_lookup_dir: *force_in_lookup_dir,
489 fragment,
490 }
491 .cell(),
492 Request::Relative {
493 path,
494 query,
495 force_in_lookup_dir,
496 fragment: _,
497 } => Request::Relative {
498 path: path.clone(),
499 query: query.clone(),
500 force_in_lookup_dir: *force_in_lookup_dir,
501 fragment,
502 }
503 .cell(),
504 Request::Module {
505 module,
506 path,
507 query,
508 fragment: _,
509 } => Request::Module {
510 module: module.clone(),
511 path: path.clone(),
512 query: query.clone(),
513 fragment,
514 }
515 .cell(),
516 Request::ServerRelative {
517 path,
518 query,
519 fragment: _,
520 } => Request::ServerRelative {
521 path: path.clone(),
522 query: query.clone(),
523 fragment,
524 }
525 .cell(),
526 Request::Windows {
527 path,
528 query,
529 fragment: _,
530 } => Request::Windows {
531 path: path.clone(),
532 query: query.clone(),
533 fragment,
534 }
535 .cell(),
536 Request::Empty => self,
537 Request::PackageInternal { .. } => self,
538 Request::DataUri { .. } => self,
539 Request::Uri { .. } => self,
540 Request::Unknown { .. } => self,
541 Request::Dynamic => self,
542 Request::Alternatives { requests } => {
543 let requests = requests
544 .iter()
545 .copied()
546 .map(|req| req.with_fragment(fragment.clone()))
547 .map(|v| async move { v.to_resolved().await })
548 .try_join()
549 .await?;
550 Request::Alternatives { requests }.cell()
551 }
552 })
553 }
554
555 #[turbo_tasks::function]
556 pub async fn append_path(self: Vc<Self>, suffix: RcStr) -> Result<Vc<Self>> {
557 Ok(match &*self.await? {
558 Request::Raw {
559 path,
560 query,
561 force_in_lookup_dir,
562 fragment,
563 } => {
564 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
565 pat.normalize();
566 Self::raw(pat, query.clone(), fragment.clone(), *force_in_lookup_dir)
567 }
568 Request::Relative {
569 path,
570 query,
571 force_in_lookup_dir,
572 fragment,
573 } => {
574 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
575 pat.normalize();
576 Self::relative(pat, query.clone(), fragment.clone(), *force_in_lookup_dir)
577 }
578 Request::Module {
579 module,
580 path,
581 query,
582 fragment,
583 } => {
584 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
585 pat.normalize();
586 Self::module(module.clone(), pat, query.clone(), fragment.clone())
587 }
588 Request::ServerRelative {
589 path,
590 query,
591 fragment,
592 } => {
593 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
594 pat.normalize();
595 Self::ServerRelative {
596 path: pat,
597 query: query.clone(),
598 fragment: fragment.clone(),
599 }
600 .cell()
601 }
602 Request::Windows {
603 path,
604 query,
605 fragment,
606 } => {
607 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
608 pat.normalize();
609 Self::Windows {
610 path: pat,
611 query: query.clone(),
612 fragment: fragment.clone(),
613 }
614 .cell()
615 }
616 Request::Empty => Self::parse(suffix.into()),
617 Request::PackageInternal { path } => {
618 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
619 pat.normalize();
620 Self::PackageInternal { path: pat }.cell()
621 }
622 Request::DataUri {
623 media_type,
624 encoding,
625 data,
626 } => {
627 let data = ResolvedVc::cell(format!("{}{}", data.await?, suffix).into());
628 Self::DataUri {
629 media_type: media_type.clone(),
630 encoding: encoding.clone(),
631 data,
632 }
633 .cell()
634 }
635 Request::Uri {
636 protocol,
637 remainder,
638 query,
639 fragment,
640 } => {
641 let remainder = format!("{remainder}{suffix}").into();
642 Self::Uri {
643 protocol: protocol.clone(),
644 remainder,
645 query: query.clone(),
646 fragment: fragment.clone(),
647 }
648 .cell()
649 }
650 Request::Unknown { path } => {
651 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
652 pat.normalize();
653 Self::Unknown { path: pat }.cell()
654 }
655 Request::Dynamic => self,
656 Request::Alternatives { requests } => {
657 let requests = requests
658 .iter()
659 .map(|req| async { req.append_path(suffix.clone()).to_resolved().await })
660 .try_join()
661 .await?;
662 Request::Alternatives { requests }.cell()
663 }
664 })
665 }
666
667 #[turbo_tasks::function]
668 pub fn query(&self) -> Vc<RcStr> {
669 Vc::cell(match self {
670 Request::Windows { query, .. }
671 | Request::ServerRelative { query, .. }
672 | Request::Module { query, .. }
673 | Request::Relative { query, .. }
674 | Request::Raw { query, .. } => query.clone(),
675 Request::Dynamic
676 | Request::Unknown { .. }
677 | Request::Uri { .. }
678 | Request::DataUri { .. }
679 | Request::PackageInternal { .. }
680 | Request::Empty => RcStr::default(),
681 Request::Alternatives { .. } => RcStr::default(),
683 })
684 }
685
686 #[turbo_tasks::function]
689 pub async fn request_pattern(self: Vc<Self>) -> Result<Vc<Pattern>> {
690 Ok(Pattern::new(match &*self.await? {
691 Request::Raw { path, .. } => path.clone(),
692 Request::Relative { path, .. } => path.clone(),
693 Request::Module { module, path, .. } => {
694 let mut path = path.clone();
695 path.push_front(Pattern::Constant(module.clone()));
696 path.normalize();
697 path
698 }
699 Request::ServerRelative { path, .. } => path.clone(),
700 Request::Windows { path, .. } => path.clone(),
701 Request::Empty => Pattern::Constant(rcstr!("")),
702 Request::PackageInternal { path } => path.clone(),
703 Request::DataUri {
704 media_type,
705 encoding,
706 data,
707 } => Pattern::Constant(
708 stringify_data_uri(media_type, encoding, *data)
709 .await?
710 .into(),
711 ),
712 Request::Uri {
713 protocol,
714 remainder,
715 ..
716 } => Pattern::Constant(format!("{protocol}{remainder}").into()),
717 Request::Unknown { path } => path.clone(),
718 Request::Dynamic => Pattern::Dynamic,
719 Request::Alternatives { requests } => Pattern::Alternatives(
720 requests
721 .iter()
722 .map(async |r: &ResolvedVc<Request>| -> Result<Pattern> {
723 Ok(r.request_pattern().owned().await?)
724 })
725 .try_join()
726 .await?,
727 ),
728 }))
729 }
730}
731
732#[turbo_tasks::value_impl]
733impl ValueToString for Request {
734 #[turbo_tasks::function]
735 async fn to_string(&self) -> Result<Vc<RcStr>> {
736 Ok(Vc::cell(match self {
737 Request::Raw {
738 path,
739 force_in_lookup_dir,
740 ..
741 } => {
742 if *force_in_lookup_dir {
743 format!("in-lookup-dir {path}").into()
744 } else {
745 format!("{path}").into()
746 }
747 }
748 Request::Relative {
749 path,
750 force_in_lookup_dir,
751 ..
752 } => {
753 if *force_in_lookup_dir {
754 format!("relative-in-lookup-dir {path}").into()
755 } else {
756 format!("relative {path}").into()
757 }
758 }
759 Request::Module { module, path, .. } => {
760 if path.could_match_others("") {
761 format!("module \"{module}\" with subpath {path}").into()
762 } else {
763 format!("module \"{module}\"").into()
764 }
765 }
766 Request::ServerRelative { path, .. } => format!("server relative {path}").into(),
767 Request::Windows { path, .. } => format!("windows {path}").into(),
768 Request::Empty => rcstr!("empty"),
769 Request::PackageInternal { path } => format!("package internal {path}").into(),
770 Request::DataUri {
771 media_type,
772 encoding,
773 data,
774 } => format!(
775 "data uri \"{media_type}\" \"{encoding}\" \"{}\"",
776 data.await?
777 )
778 .into(),
779 Request::Uri {
780 protocol,
781 remainder,
782 ..
783 } => format!("uri \"{protocol}\" \"{remainder}\"").into(),
784 Request::Unknown { path } => format!("unknown {path}").into(),
785 Request::Dynamic => rcstr!("dynamic"),
786 Request::Alternatives { requests } => {
787 let vec = requests.iter().map(|i| i.to_string()).try_join().await?;
788 vec.iter()
789 .map(|r| r.as_str())
790 .collect::<Vec<_>>()
791 .join(" or ")
792 .into()
793 }
794 }))
795 }
796}
797
798pub async fn stringify_data_uri(
799 media_type: &RcStr,
800 encoding: &RcStr,
801 data: ResolvedVc<RcStr>,
802) -> Result<String> {
803 Ok(format!(
804 "data:{media_type}{}{encoding},{}",
805 if encoding.is_empty() { "" } else { ";" },
806 data.await?
807 ))
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813
814 #[test]
815 fn test_split_query_fragment() {
816 assert_eq!(
817 (
818 Pattern::Constant(rcstr!("foo")),
819 RcStr::default(),
820 RcStr::default()
821 ),
822 split_off_query_fragment("foo")
823 );
824 assert_eq!(
827 (
828 Pattern::Constant(rcstr!("foo")),
829 rcstr!("?"),
830 RcStr::default()
831 ),
832 split_off_query_fragment("foo?")
833 );
834 assert_eq!(
835 (
836 Pattern::Constant(rcstr!("foo")),
837 RcStr::default(),
838 rcstr!("#")
839 ),
840 split_off_query_fragment("foo#")
841 );
842 assert_eq!(
843 (
844 Pattern::Constant(rcstr!("foo")),
845 rcstr!("?bar=baz"),
846 RcStr::default()
847 ),
848 split_off_query_fragment("foo?bar=baz")
849 );
850 assert_eq!(
851 (
852 Pattern::Constant(rcstr!("foo")),
853 RcStr::default(),
854 rcstr!("#stuff?bar=baz")
855 ),
856 split_off_query_fragment("foo#stuff?bar=baz")
857 );
858
859 assert_eq!(
860 (
861 Pattern::Constant(rcstr!("foo")),
862 rcstr!("?bar=baz"),
863 rcstr!("#stuff")
864 ),
865 split_off_query_fragment("foo?bar=baz#stuff")
866 );
867 }
868}