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