turbopack_dev_server/source/
router.rs

1use 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/// Binds different ContentSources to different subpaths.
16///
17/// The request path must begin with the prefix, which will be stripped (along with the subpath)
18/// before querying the ContentSource. A fallback ContentSource will serve all
19/// other subpaths, including if the request path does not include the prefix.
20#[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}