1use anyhow::{Ok, Result};
2use lazy_static::lazy_static;
3use regex::Regex;
4use turbo_rcstr::RcStr;
5use turbo_tasks::{ResolvedVc, TryJoinIterExt, Value, ValueToString, Vc};
6
7use super::pattern::Pattern;
8
9#[turbo_tasks::value(shared)]
10#[derive(Hash, Clone, Debug)]
11pub enum Request {
12 Raw {
13 path: Pattern,
14 query: ResolvedVc<RcStr>,
15 force_in_lookup_dir: bool,
16 fragment: ResolvedVc<RcStr>,
17 },
18 Relative {
19 path: Pattern,
20 query: ResolvedVc<RcStr>,
21 force_in_lookup_dir: bool,
22 fragment: ResolvedVc<RcStr>,
23 },
24 Module {
25 module: RcStr,
26 path: Pattern,
27 query: ResolvedVc<RcStr>,
28 fragment: ResolvedVc<RcStr>,
29 },
30 ServerRelative {
31 path: Pattern,
32 query: ResolvedVc<RcStr>,
33 fragment: ResolvedVc<RcStr>,
34 },
35 Windows {
36 path: Pattern,
37 query: ResolvedVc<RcStr>,
38 fragment: ResolvedVc<RcStr>,
39 },
40 Empty,
41 PackageInternal {
42 path: Pattern,
43 },
44 Uri {
45 protocol: RcStr,
46 remainder: RcStr,
47 query: ResolvedVc<RcStr>,
48 fragment: ResolvedVc<RcStr>,
49 },
50 DataUri {
51 media_type: RcStr,
52 encoding: RcStr,
53 data: ResolvedVc<RcStr>,
54 },
55 Unknown {
56 path: Pattern,
57 },
58 Dynamic,
59 Alternatives {
60 requests: Vec<ResolvedVc<Request>>,
61 },
62}
63
64fn split_off_query_fragment(raw: RcStr) -> (Pattern, Vc<RcStr>, Vc<RcStr>) {
65 let Some((raw, query)) = raw.split_once('?') else {
66 if let Some((raw, fragment)) = raw.split_once('#') {
67 return (
68 Pattern::Constant(raw.into()),
69 Vc::<RcStr>::default(),
70 Vc::cell(fragment.into()),
71 );
72 }
73
74 return (
75 Pattern::Constant(raw),
76 Vc::<RcStr>::default(),
77 Vc::<RcStr>::default(),
78 );
79 };
80
81 let (query, fragment) = query.split_once('#').unwrap_or((query, ""));
82
83 (
84 Pattern::Constant(raw.into()),
85 Vc::cell(format!("?{query}").into()),
86 Vc::cell(format!("#{fragment}").into()),
87 )
88}
89
90lazy_static! {
91 static ref WINDOWS_PATH: Regex = Regex::new(r"^[A-Za-z]:\\|\\\\").unwrap();
92 static ref URI_PATH: Regex = Regex::new(r"^([^/\\:]+:)(.+)$").unwrap();
93 static ref DATA_URI_REMAINDER: Regex = Regex::new(r"^([^;,]*)(?:;([^,]+))?,(.*)$").unwrap();
94 static ref MODULE_PATH: Regex = Regex::new(r"^((?:@[^/]+/)?[^/]+)(.*)$").unwrap();
95}
96
97impl Request {
98 pub fn request(&self) -> Option<RcStr> {
103 Some(match self {
104 Request::Raw {
105 path: Pattern::Constant(path),
106 ..
107 } => path.clone(),
108 Request::Relative {
109 path: Pattern::Constant(path),
110 ..
111 } => path.clone(),
112 Request::Module {
113 module,
114 path: Pattern::Constant(path),
115 ..
116 } => format!("{module}{path}").into(),
117 Request::ServerRelative {
118 path: Pattern::Constant(path),
119 ..
120 } => path.clone(),
121 Request::Windows {
122 path: Pattern::Constant(path),
123 ..
124 } => path.clone(),
125 Request::Empty => "".into(),
126 Request::PackageInternal {
127 path: Pattern::Constant(path),
128 ..
129 } => path.clone(),
130 Request::Uri {
131 protocol,
132 remainder,
133 ..
134 } => format!("{protocol}{remainder}").into(),
135 Request::Unknown {
136 path: Pattern::Constant(path),
137 } => path.clone(),
138 _ => return None,
139 })
140 }
141
142 pub async fn parse_ref(mut request: Pattern) -> Result<Self> {
143 request.normalize();
144 Ok(match request {
145 Pattern::Dynamic => Request::Dynamic,
146 Pattern::Constant(r) => Request::parse_constant_pattern(r).await?,
147 Pattern::Concatenation(list) => Request::parse_concatenation_pattern(list).await?,
148 Pattern::Alternatives(list) => Request::parse_alternatives_pattern(list).await?,
149 })
150 }
151
152 async fn parse_constant_pattern(r: RcStr) -> Result<Self> {
153 if r.is_empty() {
154 return Ok(Request::Empty);
155 }
156
157 if let Some(remainder) = r.strip_prefix("//") {
158 return Ok(Request::Uri {
159 protocol: "//".into(),
160 remainder: remainder.into(),
161 query: ResolvedVc::cell(RcStr::default()),
162 fragment: ResolvedVc::cell(RcStr::default()),
163 });
164 }
165
166 if r.starts_with('/') {
167 let (path, query, fragment) = split_off_query_fragment(r);
168
169 return Ok(Request::ServerRelative {
170 path,
171 query: query.to_resolved().await?,
172 fragment: fragment.to_resolved().await?,
173 });
174 }
175
176 if r.starts_with('#') {
177 return Ok(Request::PackageInternal {
178 path: Pattern::Constant(r),
179 });
180 }
181
182 if r.starts_with("./") || r.starts_with("../") || r == "." || r == ".." {
183 let (path, query, fragment) = split_off_query_fragment(r);
184
185 return Ok(Request::Relative {
186 path,
187 force_in_lookup_dir: false,
188 query: query.to_resolved().await?,
189 fragment: fragment.to_resolved().await?,
190 });
191 }
192
193 if WINDOWS_PATH.is_match(&r) {
194 let (path, query, fragment) = split_off_query_fragment(r);
195
196 return Ok(Request::Windows {
197 path,
198 query: query.to_resolved().await?,
199 fragment: fragment.to_resolved().await?,
200 });
201 }
202
203 if let Some(caps) = URI_PATH.captures(&r) {
204 if let (Some(protocol), Some(remainder)) = (caps.get(1), caps.get(2)) {
205 if let Some(caps) = DATA_URI_REMAINDER.captures(remainder.as_str()) {
206 let media_type = caps.get(1).map_or("", |m| m.as_str()).into();
207 let encoding = caps.get(2).map_or("", |e| e.as_str()).into();
208 let data = caps.get(3).map_or("", |d| d.as_str()).into();
209
210 return Ok(Request::DataUri {
211 media_type,
212 encoding,
213 data: ResolvedVc::cell(data),
214 });
215 }
216
217 return Ok(Request::Uri {
218 protocol: protocol.as_str().into(),
219 remainder: remainder.as_str().into(),
220 query: ResolvedVc::cell(RcStr::default()),
221 fragment: ResolvedVc::cell(RcStr::default()),
222 });
223 }
224 }
225
226 if let Some((module, path)) = MODULE_PATH
227 .captures(&r)
228 .and_then(|caps| caps.get(1).zip(caps.get(2)))
229 {
230 let (path, query, fragment) = split_off_query_fragment(path.as_str().into());
231
232 return Ok(Request::Module {
233 module: module.as_str().into(),
234 path,
235 query: query.to_resolved().await?,
236 fragment: fragment.to_resolved().await?,
237 });
238 }
239
240 Ok(Request::Unknown {
241 path: Pattern::Constant(r),
242 })
243 }
244
245 async fn parse_concatenation_pattern(list: Vec<Pattern>) -> Result<Self> {
246 if list.is_empty() {
247 return Ok(Request::Empty);
248 }
249
250 let mut result = Box::pin(Self::parse_ref(list[0].clone())).await?;
251
252 for item in list.into_iter().skip(1) {
253 match &mut result {
254 Request::Raw { path, .. } => {
255 path.push(item);
256 }
257 Request::Relative { path, .. } => {
258 path.push(item);
259 }
260 Request::Module { path, .. } => {
261 path.push(item);
262 }
263 Request::ServerRelative { path, .. } => {
264 path.push(item);
265 }
266 Request::Windows { path, .. } => {
267 path.push(item);
268 }
269 Request::Empty => {
270 result = Box::pin(Self::parse_ref(item)).await?;
271 }
272 Request::PackageInternal { path } => {
273 path.push(item);
274 }
275 Request::DataUri { .. } => {
276 result = Request::Dynamic;
277 }
278 Request::Uri { .. } => {
279 result = Request::Dynamic;
280 }
281 Request::Unknown { path } => {
282 path.push(item);
283 }
284 Request::Dynamic => {}
285 Request::Alternatives { .. } => unreachable!(),
286 };
287 }
288
289 Ok(result)
290 }
291
292 async fn parse_alternatives_pattern(list: Vec<Pattern>) -> Result<Self> {
293 Ok(Request::Alternatives {
294 requests: list
295 .into_iter()
296 .map(Value::new)
297 .map(Request::parse)
298 .map(|v| async move { v.to_resolved().await })
299 .try_join()
300 .await?,
301 })
302 }
303}
304
305#[turbo_tasks::value_impl]
306impl Request {
307 #[turbo_tasks::function]
308 pub async fn parse(request: Value<Pattern>) -> Result<Vc<Self>> {
309 Ok(Self::cell(Request::parse_ref(request.into_value()).await?))
310 }
311
312 #[turbo_tasks::function]
313 pub async fn parse_string(request: RcStr) -> Result<Vc<Self>> {
314 Ok(Self::cell(Request::parse_ref(request.into()).await?))
315 }
316
317 #[turbo_tasks::function]
318 pub async fn raw(
319 request: Value<Pattern>,
320 query: Vc<RcStr>,
321 fragment: Vc<RcStr>,
322 force_in_lookup_dir: bool,
323 ) -> Result<Vc<Self>> {
324 Ok(Self::cell(Request::Raw {
325 path: request.into_value(),
326 force_in_lookup_dir,
327 query: query.to_resolved().await?,
328 fragment: fragment.to_resolved().await?,
329 }))
330 }
331
332 #[turbo_tasks::function]
333 pub async fn relative(
334 request: Value<Pattern>,
335 query: Vc<RcStr>,
336 fragment: Vc<RcStr>,
337 force_in_lookup_dir: bool,
338 ) -> Result<Vc<Self>> {
339 Ok(Self::cell(Request::Relative {
340 path: request.into_value(),
341 force_in_lookup_dir,
342 query: query.to_resolved().await?,
343 fragment: fragment.to_resolved().await?,
344 }))
345 }
346
347 #[turbo_tasks::function]
348 pub async fn module(
349 module: RcStr,
350 path: Value<Pattern>,
351 query: Vc<RcStr>,
352 fragment: Vc<RcStr>,
353 ) -> Result<Vc<Self>> {
354 Ok(Self::cell(Request::Module {
355 module,
356 path: path.into_value(),
357 query: query.to_resolved().await?,
358 fragment: fragment.to_resolved().await?,
359 }))
360 }
361
362 #[turbo_tasks::function]
363 pub async fn as_relative(self: Vc<Self>) -> Result<Vc<Self>> {
364 let this = self.await?;
365 Ok(match &*this {
366 Request::Empty
367 | Request::Raw { .. }
368 | Request::ServerRelative { .. }
369 | Request::Windows { .. }
370 | Request::Relative { .. }
371 | Request::DataUri { .. }
372 | Request::Uri { .. }
373 | Request::Dynamic => self,
374 Request::Module {
375 module,
376 path,
377 query: _,
378 fragment: _,
379 } => {
380 let mut pat = Pattern::Constant(format!("./{module}").into());
381 pat.push(path.clone());
382 Self::parse(Value::new(pat))
384 }
385 Request::PackageInternal { path } => {
386 let mut pat = Pattern::Constant("./".into());
387 pat.push(path.clone());
388 Self::parse(Value::new(pat))
389 }
390 Request::Unknown { path } => {
391 let mut pat = Pattern::Constant("./".into());
392 pat.push(path.clone());
393 Self::parse(Value::new(pat))
394 }
395 Request::Alternatives { requests } => {
396 let requests = requests
397 .iter()
398 .copied()
399 .map(|v| *v)
400 .map(Request::as_relative)
401 .map(|v| async move { v.to_resolved().await })
402 .try_join()
403 .await?;
404 Request::Alternatives { requests }.cell()
405 }
406 })
407 }
408
409 #[turbo_tasks::function]
410 pub async fn with_query(self: Vc<Self>, query: Vc<RcStr>) -> Result<Vc<Self>> {
411 Ok(match &*self.await? {
412 Request::Raw {
413 path,
414 query: _,
415 force_in_lookup_dir,
416 fragment,
417 } => Request::Raw {
418 path: path.clone(),
419 query: query.to_resolved().await?,
420 force_in_lookup_dir: *force_in_lookup_dir,
421 fragment: *fragment,
422 }
423 .cell(),
424 Request::Relative {
425 path,
426 query: _,
427 force_in_lookup_dir,
428 fragment,
429 } => Request::Relative {
430 path: path.clone(),
431 query: query.to_resolved().await?,
432 force_in_lookup_dir: *force_in_lookup_dir,
433 fragment: *fragment,
434 }
435 .cell(),
436 Request::Module {
437 module,
438 path,
439 query: _,
440 fragment,
441 } => Request::Module {
442 module: module.clone(),
443 path: path.clone(),
444 query: query.to_resolved().await?,
445 fragment: *fragment,
446 }
447 .cell(),
448 Request::ServerRelative {
449 path,
450 query: _,
451 fragment,
452 } => Request::ServerRelative {
453 path: path.clone(),
454 query: query.to_resolved().await?,
455 fragment: *fragment,
456 }
457 .cell(),
458 Request::Windows {
459 path,
460 query: _,
461 fragment,
462 } => Request::Windows {
463 path: path.clone(),
464 query: query.to_resolved().await?,
465 fragment: *fragment,
466 }
467 .cell(),
468 Request::Empty => self,
469 Request::PackageInternal { .. } => self,
470 Request::DataUri { .. } => self,
471 Request::Uri { .. } => self,
472 Request::Unknown { .. } => self,
473 Request::Dynamic => self,
474 Request::Alternatives { requests } => {
475 let requests = requests
476 .iter()
477 .copied()
478 .map(|req| req.with_query(query))
479 .map(|v| async move { v.to_resolved().await })
480 .try_join()
481 .await?;
482 Request::Alternatives { requests }.cell()
483 }
484 })
485 }
486
487 #[turbo_tasks::function]
488 pub async fn with_fragment(self: Vc<Self>, fragment: Vc<RcStr>) -> Result<Vc<Self>> {
489 Ok(match &*self.await? {
490 Request::Raw {
491 path,
492 query,
493 force_in_lookup_dir,
494 fragment: _,
495 } => Request::Raw {
496 path: path.clone(),
497 query: *query,
498 force_in_lookup_dir: *force_in_lookup_dir,
499 fragment: fragment.to_resolved().await?,
500 }
501 .cell(),
502 Request::Relative {
503 path,
504 query,
505 force_in_lookup_dir,
506 fragment: _,
507 } => Request::Relative {
508 path: path.clone(),
509 query: *query,
510 force_in_lookup_dir: *force_in_lookup_dir,
511 fragment: fragment.to_resolved().await?,
512 }
513 .cell(),
514 Request::Module {
515 module,
516 path,
517 query,
518 fragment: _,
519 } => Request::Module {
520 module: module.clone(),
521 path: path.clone(),
522 query: *query,
523 fragment: fragment.to_resolved().await?,
524 }
525 .cell(),
526 Request::ServerRelative {
527 path,
528 query,
529 fragment: _,
530 } => Request::ServerRelative {
531 path: path.clone(),
532 query: *query,
533 fragment: fragment.to_resolved().await?,
534 }
535 .cell(),
536 Request::Windows {
537 path,
538 query,
539 fragment: _,
540 } => Request::Windows {
541 path: path.clone(),
542 query: *query,
543 fragment: fragment.to_resolved().await?,
544 }
545 .cell(),
546 Request::Empty => self,
547 Request::PackageInternal { .. } => self,
548 Request::DataUri { .. } => self,
549 Request::Uri { .. } => self,
550 Request::Unknown { .. } => self,
551 Request::Dynamic => self,
552 Request::Alternatives { requests } => {
553 let requests = requests
554 .iter()
555 .copied()
556 .map(|req| req.with_fragment(fragment))
557 .map(|v| async move { v.to_resolved().await })
558 .try_join()
559 .await?;
560 Request::Alternatives { requests }.cell()
561 }
562 })
563 }
564
565 #[turbo_tasks::function]
566 pub async fn append_path(self: Vc<Self>, suffix: RcStr) -> Result<Vc<Self>> {
567 Ok(match &*self.await? {
568 Request::Raw {
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::raw(Value::new(pat), **query, **fragment, *force_in_lookup_dir)
577 }
578 Request::Relative {
579 path,
580 query,
581 force_in_lookup_dir,
582 fragment,
583 } => {
584 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
585 pat.normalize();
586 Self::relative(Value::new(pat), **query, **fragment, *force_in_lookup_dir)
587 }
588 Request::Module {
589 module,
590 path,
591 query,
592 fragment,
593 } => {
594 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
595 pat.normalize();
596 Self::module(module.clone(), Value::new(pat), **query, **fragment)
597 }
598 Request::ServerRelative {
599 path,
600 query,
601 fragment,
602 } => {
603 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
604 pat.normalize();
605 Self::ServerRelative {
606 path: pat,
607 query: *query,
608 fragment: *fragment,
609 }
610 .cell()
611 }
612 Request::Windows {
613 path,
614 query,
615 fragment,
616 } => {
617 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
618 pat.normalize();
619 Self::Windows {
620 path: pat,
621 query: *query,
622 fragment: *fragment,
623 }
624 .cell()
625 }
626 Request::Empty => Self::parse(Value::new(suffix.into())),
627 Request::PackageInternal { path } => {
628 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
629 pat.normalize();
630 Self::PackageInternal { path: pat }.cell()
631 }
632 Request::DataUri {
633 media_type,
634 encoding,
635 data,
636 } => {
637 let data = ResolvedVc::cell(format!("{}{}", data.await?, suffix).into());
638 Self::DataUri {
639 media_type: media_type.clone(),
640 encoding: encoding.clone(),
641 data,
642 }
643 .cell()
644 }
645 Request::Uri {
646 protocol,
647 remainder,
648 query,
649 fragment,
650 } => {
651 let remainder = format!("{remainder}{suffix}").into();
652 Self::Uri {
653 protocol: protocol.clone(),
654 remainder,
655 query: *query,
656 fragment: *fragment,
657 }
658 .cell()
659 }
660 Request::Unknown { path } => {
661 let mut pat = Pattern::concat([path.clone(), suffix.into()]);
662 pat.normalize();
663 Self::Unknown { path: pat }.cell()
664 }
665 Request::Dynamic => self,
666 Request::Alternatives { requests } => {
667 let requests = requests
668 .iter()
669 .map(|req| async { req.append_path(suffix.clone()).to_resolved().await })
670 .try_join()
671 .await?;
672 Request::Alternatives { requests }.cell()
673 }
674 })
675 }
676
677 #[turbo_tasks::function]
678 pub fn query(&self) -> Vc<RcStr> {
679 match self {
680 Request::Raw { query, .. } => **query,
681 Request::Relative { query, .. } => **query,
682 Request::Module { query, .. } => **query,
683 Request::ServerRelative { query, .. } => **query,
684 Request::Windows { query, .. } => **query,
685 Request::Empty => Vc::<RcStr>::default(),
686 Request::PackageInternal { .. } => Vc::<RcStr>::default(),
687 Request::DataUri { .. } => Vc::<RcStr>::default(),
688 Request::Uri { .. } => Vc::<RcStr>::default(),
689 Request::Unknown { .. } => Vc::<RcStr>::default(),
690 Request::Dynamic => Vc::<RcStr>::default(),
691 Request::Alternatives { .. } => Vc::<RcStr>::default(),
693 }
694 }
695
696 #[turbo_tasks::function]
699 pub async fn request_pattern(self: Vc<Self>) -> Result<Vc<Pattern>> {
700 Ok(match &*self.await? {
701 Request::Raw { path, .. } => path.clone(),
702 Request::Relative { path, .. } => path.clone(),
703 Request::Module { module, path, .. } => {
704 let mut path = path.clone();
705 path.push_front(Pattern::Constant(module.clone()));
706 path.normalize();
707 path
708 }
709 Request::ServerRelative { path, .. } => path.clone(),
710 Request::Windows { path, .. } => path.clone(),
711 Request::Empty => Pattern::Constant("".into()),
712 Request::PackageInternal { path } => path.clone(),
713 Request::DataUri {
714 media_type,
715 encoding,
716 data,
717 } => Pattern::Constant(
718 stringify_data_uri(media_type, encoding, *data)
719 .await?
720 .into(),
721 ),
722 Request::Uri {
723 protocol,
724 remainder,
725 ..
726 } => Pattern::Constant(format!("{protocol}{remainder}").into()),
727 Request::Unknown { path } => path.clone(),
728 Request::Dynamic => Pattern::Dynamic,
729 Request::Alternatives { requests } => Pattern::Alternatives(
730 requests
731 .iter()
732 .map(async |r: &ResolvedVc<Request>| -> Result<Pattern> {
733 Ok(r.request_pattern().owned().await?)
734 })
735 .try_join()
736 .await?,
737 ),
738 }
739 .cell())
740 }
741}
742
743#[turbo_tasks::value_impl]
744impl ValueToString for Request {
745 #[turbo_tasks::function]
746 async fn to_string(&self) -> Result<Vc<RcStr>> {
747 Ok(Vc::cell(match self {
748 Request::Raw {
749 path,
750 force_in_lookup_dir,
751 ..
752 } => {
753 if *force_in_lookup_dir {
754 format!("in-lookup-dir {path}").into()
755 } else {
756 format!("{path}").into()
757 }
758 }
759 Request::Relative {
760 path,
761 force_in_lookup_dir,
762 ..
763 } => {
764 if *force_in_lookup_dir {
765 format!("relative-in-lookup-dir {path}").into()
766 } else {
767 format!("relative {path}").into()
768 }
769 }
770 Request::Module { module, path, .. } => {
771 if path.could_match_others("") {
772 format!("module \"{module}\" with subpath {path}").into()
773 } else {
774 format!("module \"{module}\"").into()
775 }
776 }
777 Request::ServerRelative { path, .. } => format!("server relative {path}").into(),
778 Request::Windows { path, .. } => format!("windows {path}").into(),
779 Request::Empty => "empty".into(),
780 Request::PackageInternal { path } => format!("package internal {path}").into(),
781 Request::DataUri {
782 media_type,
783 encoding,
784 data,
785 } => format!(
786 "data uri \"{media_type}\" \"{encoding}\" \"{}\"",
787 data.await?
788 )
789 .into(),
790 Request::Uri {
791 protocol,
792 remainder,
793 ..
794 } => format!("uri \"{protocol}\" \"{remainder}\"").into(),
795 Request::Unknown { path } => format!("unknown {path}").into(),
796 Request::Dynamic => "dynamic".into(),
797 Request::Alternatives { requests } => {
798 let vec = requests.iter().map(|i| i.to_string()).try_join().await?;
799 vec.iter()
800 .map(|r| r.as_str())
801 .collect::<Vec<_>>()
802 .join(" or ")
803 .into()
804 }
805 }))
806 }
807}
808
809pub async fn stringify_data_uri(
810 media_type: &RcStr,
811 encoding: &RcStr,
812 data: ResolvedVc<RcStr>,
813) -> Result<String> {
814 Ok(format!(
815 "data:{media_type}{}{encoding},{}",
816 if encoding.is_empty() { "" } else { ";" },
817 data.await?
818 ))
819}