turbopack_dev_server/source/
router.rs

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