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