turbopack_dev_server/source/
router.rs1use std::iter::once;
2
3use anyhow::Result;
4use turbo_rcstr::{RcStr, rcstr};
5use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
6use turbopack_core::introspect::{Introspectable, IntrospectableChildren};
7
8use crate::source::{
9 ContentSource, ContentSourceContent, ContentSourceData, ContentSourceDataVary, ContentSources,
10 GetContentSourceContent,
11 route_tree::{BaseSegment, MapGetContentSourceContent, RouteTree, RouteTrees},
12};
13
14#[turbo_tasks::value(shared)]
20pub struct PrefixedRouterContentSource {
21 pub prefix: ResolvedVc<RcStr>,
22 pub routes: Vec<(RcStr, ResolvedVc<Box<dyn ContentSource>>)>,
23 pub fallback: ResolvedVc<Box<dyn ContentSource>>,
24}
25
26#[turbo_tasks::value_impl]
27impl PrefixedRouterContentSource {
28 #[turbo_tasks::function]
29 pub fn new(
30 prefix: ResolvedVc<RcStr>,
31 routes: Vec<(RcStr, ResolvedVc<Box<dyn ContentSource>>)>,
32 fallback: ResolvedVc<Box<dyn ContentSource>>,
33 ) -> Vc<Self> {
34 PrefixedRouterContentSource {
35 prefix,
36 routes,
37 fallback,
38 }
39 .cell()
40 }
41}
42
43fn get_children(
44 routes: &[(RcStr, ResolvedVc<Box<dyn ContentSource>>)],
45 fallback: &ResolvedVc<Box<dyn ContentSource>>,
46) -> Vc<ContentSources> {
47 Vc::cell(
48 routes
49 .iter()
50 .map(|r| r.1)
51 .chain(std::iter::once(*fallback))
52 .collect(),
53 )
54}
55
56async fn get_introspection_children(
57 routes: &[(RcStr, ResolvedVc<Box<dyn ContentSource>>)],
58 fallback: &ResolvedVc<Box<dyn ContentSource>>,
59) -> Result<Vc<IntrospectableChildren>> {
60 Ok(Vc::cell(
61 routes
62 .iter()
63 .cloned()
64 .chain(std::iter::once((RcStr::default(), *fallback)))
65 .filter_map(|(path, source)| {
66 ResolvedVc::try_sidecast::<Box<dyn Introspectable>>(source).map(|i| (path, i))
67 })
68 .collect(),
69 ))
70}
71
72#[turbo_tasks::value_impl]
73impl ContentSource for PrefixedRouterContentSource {
74 #[turbo_tasks::function]
75 async fn get_routes(&self) -> Result<Vc<RouteTree>> {
76 let prefix = &*self.prefix.await?;
77 if cfg!(debug_assertions) {
78 debug_assert!(prefix.is_empty() || prefix.ends_with('/'));
79 debug_assert!(!prefix.starts_with('/'));
80 }
81
82 let prefix = if prefix.is_empty() {
83 Vec::new()
84 } else {
85 BaseSegment::from_static_pathname(prefix.as_str()).collect()
86 };
87
88 let inner_trees = self.routes.iter().map(|(path, source)| {
89 let prepended_base = prefix
90 .iter()
91 .cloned()
92 .chain(BaseSegment::from_static_pathname(path))
93 .collect();
94 source
95 .get_routes()
96 .with_prepended_base(prepended_base)
97 .map_routes(Vc::upcast(
98 PrefixedRouterContentSourceMapper {
99 prefix: self.prefix,
100 path: path.clone(),
101 }
102 .cell(),
103 ))
104 });
105 Ok(Vc::<RouteTrees>::cell(
106 inner_trees
107 .chain(once(self.fallback.get_routes()))
108 .map(|v| async move { v.to_resolved().await })
109 .try_join()
110 .await?,
111 )
112 .merge())
113 }
114
115 #[turbo_tasks::function]
116 fn get_children(&self) -> Vc<ContentSources> {
117 get_children(&self.routes, &self.fallback)
118 }
119}
120
121#[turbo_tasks::value]
122struct PrefixedRouterContentSourceMapper {
123 prefix: ResolvedVc<RcStr>,
124 path: RcStr,
125}
126
127#[turbo_tasks::value_impl]
128impl MapGetContentSourceContent for PrefixedRouterContentSourceMapper {
129 #[turbo_tasks::function]
130 fn map_get_content(
131 self: ResolvedVc<Self>,
132 get_content: ResolvedVc<Box<dyn GetContentSourceContent>>,
133 ) -> Vc<Box<dyn GetContentSourceContent>> {
134 Vc::upcast(
135 PrefixedRouterGetContentSourceContent {
136 mapper: self,
137 get_content,
138 }
139 .cell(),
140 )
141 }
142}
143
144#[turbo_tasks::value]
145struct PrefixedRouterGetContentSourceContent {
146 mapper: ResolvedVc<PrefixedRouterContentSourceMapper>,
147 get_content: ResolvedVc<Box<dyn GetContentSourceContent>>,
148}
149
150#[turbo_tasks::value_impl]
151impl GetContentSourceContent for PrefixedRouterGetContentSourceContent {
152 #[turbo_tasks::function]
153 fn vary(&self) -> Vc<ContentSourceDataVary> {
154 self.get_content.vary()
155 }
156
157 #[turbo_tasks::function]
158 async fn get(&self, path: RcStr, data: ContentSourceData) -> Result<Vc<ContentSourceContent>> {
159 let prefix = self.mapper.await?.prefix.await?;
160 if let Some(path) = path.strip_prefix(&**prefix) {
161 if path.is_empty() {
162 return Ok(self.get_content.get(RcStr::default(), data));
163 } else if prefix.is_empty() {
164 return Ok(self.get_content.get(path.into(), data));
165 } else if let Some(path) = path.strip_prefix('/') {
166 return Ok(self.get_content.get(path.into(), data));
167 }
168 }
169 Ok(ContentSourceContent::not_found())
170 }
171}
172
173#[turbo_tasks::value_impl]
174impl Introspectable for PrefixedRouterContentSource {
175 #[turbo_tasks::function]
176 fn ty(&self) -> Vc<RcStr> {
177 Vc::cell(rcstr!("prefixed router content source"))
178 }
179
180 #[turbo_tasks::function]
181 async fn details(&self) -> Result<Vc<RcStr>> {
182 let prefix = self.prefix.await?;
183 Ok(Vc::cell(format!("prefix: '{prefix}'").into()))
184 }
185
186 #[turbo_tasks::function]
187 async fn children(&self) -> Result<Vc<IntrospectableChildren>> {
188 get_introspection_children(&self.routes, &self.fallback).await
189 }
190}