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 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 .filter_map(|(path, source)| {
67 ResolvedVc::try_sidecast::<Box<dyn Introspectable>>(source).map(|i| (path, i))
68 })
69 .collect(),
70 ))
71}
72
73#[turbo_tasks::value_impl]
74impl ContentSource for PrefixedRouterContentSource {
75 #[turbo_tasks::function]
76 async fn get_routes(&self) -> Result<Vc<RouteTree>> {
77 let prefix = &*self.prefix.await?;
78 if cfg!(debug_assertions) {
79 debug_assert!(prefix.is_empty() || prefix.ends_with('/'));
80 debug_assert!(!prefix.starts_with('/'));
81 }
82
83 let prefix = if prefix.is_empty() {
84 Vec::new()
85 } else {
86 BaseSegment::from_static_pathname(prefix.as_str()).collect()
87 };
88
89 let inner_trees = self.routes.iter().map(|(path, source)| {
90 let prepended_base = prefix
91 .iter()
92 .cloned()
93 .chain(BaseSegment::from_static_pathname(path))
94 .collect();
95 source
96 .get_routes()
97 .with_prepended_base(prepended_base)
98 .map_routes(Vc::upcast(
99 PrefixedRouterContentSourceMapper {
100 prefix: self.prefix,
101 path: path.clone(),
102 }
103 .cell(),
104 ))
105 });
106 Ok(Vc::<RouteTrees>::cell(
107 inner_trees
108 .chain(once(self.fallback.get_routes()))
109 .map(|v| async move { v.to_resolved().await })
110 .try_join()
111 .await?,
112 )
113 .merge())
114 }
115
116 #[turbo_tasks::function]
117 fn get_children(&self) -> Vc<ContentSources> {
118 get_children(&self.routes, &self.fallback)
119 }
120}
121
122#[turbo_tasks::value]
123struct PrefixedRouterContentSourceMapper {
124 prefix: ResolvedVc<RcStr>,
125 path: RcStr,
126}
127
128#[turbo_tasks::value_impl]
129impl MapGetContentSourceContent for PrefixedRouterContentSourceMapper {
130 #[turbo_tasks::function]
131 fn map_get_content(
132 self: ResolvedVc<Self>,
133 get_content: ResolvedVc<Box<dyn GetContentSourceContent>>,
134 ) -> Vc<Box<dyn GetContentSourceContent>> {
135 Vc::upcast(
136 PrefixedRouterGetContentSourceContent {
137 mapper: self,
138 get_content,
139 }
140 .cell(),
141 )
142 }
143}
144
145#[turbo_tasks::value]
146struct PrefixedRouterGetContentSourceContent {
147 mapper: ResolvedVc<PrefixedRouterContentSourceMapper>,
148 get_content: ResolvedVc<Box<dyn GetContentSourceContent>>,
149}
150
151#[turbo_tasks::value_impl]
152impl GetContentSourceContent for PrefixedRouterGetContentSourceContent {
153 #[turbo_tasks::function]
154 fn vary(&self) -> Vc<ContentSourceDataVary> {
155 self.get_content.vary()
156 }
157
158 #[turbo_tasks::function]
159 async fn get(&self, path: RcStr, data: ContentSourceData) -> Result<Vc<ContentSourceContent>> {
160 let prefix = self.mapper.await?.prefix.await?;
161 if let Some(path) = path.strip_prefix(&**prefix) {
162 if path.is_empty() {
163 return Ok(self.get_content.get(RcStr::default(), data));
164 } else if prefix.is_empty() {
165 return Ok(self.get_content.get(path.into(), data));
166 } else if let Some(path) = path.strip_prefix('/') {
167 return Ok(self.get_content.get(path.into(), data));
168 }
169 }
170 Ok(ContentSourceContent::not_found())
171 }
172}
173
174#[turbo_tasks::value_impl]
175impl Introspectable for PrefixedRouterContentSource {
176 #[turbo_tasks::function]
177 fn ty(&self) -> Vc<RcStr> {
178 Vc::cell(rcstr!("prefixed router content source"))
179 }
180
181 #[turbo_tasks::function]
182 async fn details(&self) -> Result<Vc<RcStr>> {
183 let prefix = self.prefix.await?;
184 Ok(Vc::cell(format!("prefix: '{prefix}'").into()))
185 }
186
187 #[turbo_tasks::function]
188 async fn children(&self) -> Result<Vc<IntrospectableChildren>> {
189 get_introspection_children(&self.routes, &self.fallback).await
190 }
191}