turbopack_dev_server/source/
route_tree.rs

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