Skip to main content

turbopack_dev_server/source/
route_tree.rs

1use std::{fmt::Write, mem::replace};
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use turbo_rcstr::RcStr;
6use turbo_tasks::{FxIndexMap, ReadRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc, fxindexmap};
7
8use crate::source::{GetContentSourceContent, GetContentSourceContents};
9
10/// The type of the route. This will decide about the remaining segments of the
11/// route after the base.
12#[turbo_tasks::task_input]
13#[derive(Clone, Debug, PartialEq, Eq, Hash, Encode, Decode)]
14pub enum RouteType {
15    Exact,
16    CatchAll,
17    Fallback,
18    NotFound,
19}
20
21/// Some normal segment of a route.
22#[turbo_tasks::task_input]
23#[derive(Clone, Debug, PartialEq, Eq, Hash, Encode, Decode)]
24pub enum BaseSegment {
25    Static(RcStr),
26    Dynamic,
27}
28
29impl BaseSegment {
30    pub fn from_static_pathname(str: &str) -> impl Iterator<Item = BaseSegment> + '_ {
31        str.split('/')
32            .filter(|s| !s.is_empty())
33            .map(|s| BaseSegment::Static(s.into()))
34    }
35}
36
37/// This struct allows to cell a list of [`RouteTree`]s and merge them into one.
38///
39/// This can't be a single method `fn merge(Vec<Vc<RouteTree>>)` as this would lead to creating new
40/// tasks over and over. A celled list leads to task reuse and faster operation.
41#[turbo_tasks::value(transparent)]
42pub struct RouteTrees(Vec<ResolvedVc<RouteTree>>);
43
44#[turbo_tasks::value_impl]
45impl RouteTrees {
46    /// Merges the list of [`RouteTree`]s into one [`RouteTree`].
47    #[turbo_tasks::function]
48    pub async fn merge(&self) -> Result<Vc<RouteTree>> {
49        let trees = &self.0;
50        if trees.is_empty() {
51            return Ok(RouteTree::default().cell());
52        }
53        if trees.len() == 1 {
54            return Ok(**trees.iter().next().unwrap());
55        }
56
57        // Find common base
58        let mut tree_values = trees.iter().try_join().await?;
59        let mut common_base = 0;
60        let last_tree = tree_values.pop().unwrap();
61        'outer: while common_base < last_tree.base.len() {
62            for tree in tree_values.iter() {
63                if tree.base.len() <= common_base {
64                    break 'outer;
65                }
66                if tree.base[common_base] != last_tree.base[common_base] {
67                    break 'outer;
68                }
69            }
70            common_base += 1;
71        }
72        tree_values.push(last_tree);
73
74        // Normalize bases to common base
75        let trees = trees
76            .iter()
77            .enumerate()
78            .map(|(i, tree)| {
79                if tree_values[i].base.len() > common_base {
80                    tree.with_base_len(common_base)
81                } else {
82                    **tree
83                }
84            })
85            .collect::<Vec<_>>();
86
87        // Flat merge trees
88        let tree_values = trees.into_iter().try_join().await?;
89        let mut iter = tree_values.iter().map(|rr| &**rr);
90        let mut merged = iter.next().unwrap().clone();
91        merged.flat_merge(iter).await?;
92
93        Ok(merged.cell())
94    }
95}
96
97/// The prefix tree of routes. Also handling dynamic and catch all segments.
98#[turbo_tasks::value]
99#[derive(Default, Clone, Debug)]
100pub struct RouteTree {
101    base: Vec<BaseSegment>,
102    sources: Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>,
103    #[bincode(with = "turbo_bincode::indexmap")]
104    static_segments: FxIndexMap<RcStr, ResolvedVc<RouteTree>>,
105    dynamic_segments: Vec<ResolvedVc<RouteTree>>,
106    catch_all_sources: Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>,
107    fallback_sources: Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>,
108    not_found_sources: Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>,
109}
110
111impl RouteTree {
112    /// Creates a route tree for a single route.
113    pub fn new_route_ref(
114        base_segments: Vec<BaseSegment>,
115        route_type: RouteType,
116        source: ResolvedVc<Box<dyn GetContentSourceContent>>,
117    ) -> Self {
118        match route_type {
119            RouteType::Exact => Self {
120                base: base_segments,
121                sources: vec![source],
122                ..Default::default()
123            },
124            RouteType::CatchAll => Self {
125                base: base_segments,
126                catch_all_sources: vec![source],
127                ..Default::default()
128            },
129            RouteType::Fallback => Self {
130                base: base_segments,
131                fallback_sources: vec![source],
132                ..Default::default()
133            },
134            RouteType::NotFound => Self {
135                base: base_segments,
136                not_found_sources: vec![source],
137                ..Default::default()
138            },
139        }
140    }
141
142    async fn flat_merge(&mut self, others: impl IntoIterator<Item = &Self> + '_) -> Result<()> {
143        let mut static_segments = FxIndexMap::default();
144        for other in others {
145            debug_assert_eq!(self.base, other.base);
146            self.sources.extend(other.sources.iter().copied());
147            self.catch_all_sources
148                .extend(other.catch_all_sources.iter().copied());
149            self.fallback_sources
150                .extend(other.fallback_sources.iter().copied());
151            self.not_found_sources
152                .extend(other.not_found_sources.iter().copied());
153            for (key, value) in other.static_segments.iter() {
154                if let Some((key, self_value)) = self.static_segments.swap_remove_entry(key) {
155                    static_segments.insert(key, vec![self_value, *value]);
156                } else if let Some(list) = static_segments.get_mut(key) {
157                    list.push(*value);
158                } else {
159                    static_segments.insert(key.clone(), vec![*value]);
160                }
161            }
162            self.dynamic_segments
163                .extend(other.dynamic_segments.iter().copied());
164        }
165        self.static_segments.extend(
166            static_segments
167                .into_iter()
168                .map(async |(key, value)| {
169                    Ok((
170                        key,
171                        if value.len() == 1 {
172                            value.into_iter().next().unwrap()
173                        } else {
174                            Vc::<RouteTrees>::cell(value).merge().to_resolved().await?
175                        },
176                    ))
177                })
178                .try_join()
179                .await?,
180        );
181        Ok(())
182    }
183
184    fn prepend_base(&mut self, segments: Vec<BaseSegment>) {
185        self.base.splice(..0, segments);
186    }
187}
188
189#[turbo_tasks::value_impl]
190impl ValueToString for RouteTree {
191    #[turbo_tasks::function]
192    async fn to_string(&self) -> Result<Vc<RcStr>> {
193        let RouteTree {
194            base,
195            sources,
196            static_segments,
197            dynamic_segments,
198            catch_all_sources,
199            fallback_sources,
200            not_found_sources,
201        } = self;
202        let mut result = "RouteTree(".to_string();
203        for segment in base {
204            match segment {
205                BaseSegment::Static(str) => write!(result, "/{str}")?,
206                BaseSegment::Dynamic => result.push_str("/[dynamic]"),
207            }
208        }
209        if !base.is_empty() {
210            result.push_str(", ");
211        }
212        for (key, tree) in static_segments {
213            let tree = tree.to_string().await?;
214            write!(result, "{key}: {tree}, ")?;
215        }
216        if !sources.is_empty() {
217            write!(result, "{} x source, ", sources.len())?;
218        }
219        if !dynamic_segments.is_empty() {
220            write!(result, "{} x dynamic, ", dynamic_segments.len())?;
221        }
222        if !catch_all_sources.is_empty() {
223            write!(result, "{} x catch-all, ", catch_all_sources.len())?;
224        }
225        if !fallback_sources.is_empty() {
226            write!(result, "{} x fallback, ", fallback_sources.len())?;
227        }
228        if !not_found_sources.is_empty() {
229            write!(result, "{} x not-found, ", not_found_sources.len())?;
230        }
231        if result.ends_with(", ") {
232            result.truncate(result.len() - 2);
233        }
234        result.push(')');
235        Ok(Vc::cell(result.into()))
236    }
237}
238
239#[turbo_tasks::value_impl]
240impl RouteTree {
241    /// Creates an empty route tree.
242    #[turbo_tasks::function]
243    pub fn empty() -> Vc<RouteTree> {
244        RouteTree::default().cell()
245    }
246
247    /// Creates a route tree for a single route.
248    #[turbo_tasks::function]
249    pub fn new_route(
250        base_segments: Vec<BaseSegment>,
251        route_type: RouteType,
252        source: ResolvedVc<Box<dyn GetContentSourceContent>>,
253    ) -> Vc<Self> {
254        RouteTree::new_route_ref(base_segments, route_type, source).cell()
255    }
256
257    /// Gets the [`GetContentSourceContent`]s for the given path.
258    // TODO(WEB-1252) It's unnecessary to compute all [`GetContentSourceContent`]s at once, we could
259    // return some lazy iterator to make it more efficient.
260    #[turbo_tasks::function]
261    pub async fn get(self: Vc<Self>, path: RcStr) -> Result<Vc<GetContentSourceContents>> {
262        let RouteTree {
263            base,
264            sources,
265            static_segments,
266            dynamic_segments,
267            catch_all_sources,
268            fallback_sources,
269            not_found_sources,
270        } = &*self.await?;
271        let mut results = Vec::new();
272        if path.is_empty() {
273            if !base.is_empty() {
274                return Ok(Vc::cell(vec![]));
275            }
276            results.extend(sources.iter().copied());
277        } else {
278            let mut segments = path.split('/');
279            for base in base.iter() {
280                let Some(segment) = segments.next() else {
281                    return Ok(Vc::cell(vec![]));
282                };
283                match base {
284                    BaseSegment::Static(str) => {
285                        if str != segment {
286                            return Ok(Vc::cell(vec![]));
287                        }
288                    }
289                    BaseSegment::Dynamic => {
290                        // always matching
291                    }
292                }
293            }
294
295            if let Some(segment) = segments.next() {
296                let remainder = segments.remainder().unwrap_or("");
297                if let Some(tree) = static_segments.get(segment) {
298                    results.extend(tree.get(remainder.into()).await?.iter().copied());
299                }
300                for tree in dynamic_segments.iter() {
301                    results.extend(tree.get(remainder.into()).await?.iter().copied());
302                }
303            } else {
304                results.extend(sources.iter().copied());
305            };
306        }
307        results.extend(catch_all_sources.iter().copied());
308        results.extend(fallback_sources.iter().copied());
309        results.extend(not_found_sources.iter().copied());
310        Ok(Vc::cell(results))
311    }
312
313    /// Prepends a base path to all routes.
314    #[turbo_tasks::function]
315    pub async fn with_prepended_base(
316        self: Vc<Self>,
317        segments: Vec<BaseSegment>,
318    ) -> Result<Vc<RouteTree>> {
319        let mut this = self.owned().await?;
320        this.prepend_base(segments);
321        Ok(this.cell())
322    }
323
324    #[turbo_tasks::function]
325    async fn with_base_len(self: Vc<Self>, base_len: usize) -> Result<Vc<RouteTree>> {
326        let this = self.await?;
327        if this.base.len() > base_len {
328            let mut inner = ReadRef::into_owned(this);
329            let mut drain = inner.base.drain(base_len..);
330            let selector_segment = drain.next().unwrap();
331            let inner_base = drain.collect();
332            let base = replace(&mut inner.base, inner_base);
333            debug_assert!(base.len() == base_len);
334            match selector_segment {
335                BaseSegment::Static(value) => Ok(RouteTree {
336                    base,
337                    static_segments: fxindexmap! { value => inner.resolved_cell() },
338                    ..Default::default()
339                }
340                .cell()),
341                BaseSegment::Dynamic => Ok(RouteTree {
342                    base,
343                    dynamic_segments: vec![inner.resolved_cell()],
344                    ..Default::default()
345                }
346                .cell()),
347            }
348        } else {
349            Ok(self)
350        }
351    }
352
353    /// Applies a transformation on all [`GetContentSourceContent`]s in the
354    /// tree.
355    #[turbo_tasks::function]
356    pub async fn map_routes(
357        self: Vc<Self>,
358        mapper: Vc<Box<dyn MapGetContentSourceContent>>,
359    ) -> Result<Vc<Self>> {
360        let mut this = self.owned().await?;
361        let RouteTree {
362            base: _,
363            static_segments,
364            dynamic_segments,
365            sources,
366            catch_all_sources,
367            fallback_sources,
368            not_found_sources,
369        } = &mut this;
370
371        for s in sources.iter_mut() {
372            *s = mapper.map_get_content(**s).to_resolved().await?;
373        }
374        for s in catch_all_sources.iter_mut() {
375            *s = mapper.map_get_content(**s).to_resolved().await?;
376        }
377        for s in fallback_sources.iter_mut() {
378            *s = mapper.map_get_content(**s).to_resolved().await?;
379        }
380        for s in not_found_sources.iter_mut() {
381            *s = mapper.map_get_content(**s).to_resolved().await?;
382        }
383        for r in static_segments.values_mut() {
384            *r = r.map_routes(mapper).to_resolved().await?;
385        }
386        for r in dynamic_segments.iter_mut() {
387            *r = r.map_routes(mapper).to_resolved().await?;
388        }
389
390        Ok(this.cell())
391    }
392}
393
394/// Transformation functor
395#[turbo_tasks::value_trait]
396pub trait MapGetContentSourceContent {
397    #[turbo_tasks::function]
398    fn map_get_content(
399        self: Vc<Self>,
400        get_content: Vc<Box<dyn GetContentSourceContent>>,
401    ) -> Vc<Box<dyn GetContentSourceContent>>;
402}