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, NonLocalValue, ReadRef, ResolvedVc, TaskInput, TryJoinIterExt, ValueToString, Vc,
8    fxindexmap, 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#[derive(
16    TaskInput, Clone, Debug, PartialEq, Eq, Hash, TraceRawVcs, NonLocalValue, Encode, Decode,
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, TraceRawVcs, NonLocalValue, Encode, Decode,
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    #[bincode(with = "turbo_bincode::indexmap")]
110    static_segments: FxIndexMap<RcStr, ResolvedVc<RouteTree>>,
111    dynamic_segments: Vec<ResolvedVc<RouteTree>>,
112    catch_all_sources: Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>,
113    fallback_sources: Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>,
114    not_found_sources: Vec<ResolvedVc<Box<dyn GetContentSourceContent>>>,
115}
116
117impl RouteTree {
118    /// Creates a route tree for a single route.
119    pub fn new_route_ref(
120        base_segments: Vec<BaseSegment>,
121        route_type: RouteType,
122        source: ResolvedVc<Box<dyn GetContentSourceContent>>,
123    ) -> Self {
124        match route_type {
125            RouteType::Exact => Self {
126                base: base_segments,
127                sources: vec![source],
128                ..Default::default()
129            },
130            RouteType::CatchAll => Self {
131                base: base_segments,
132                catch_all_sources: vec![source],
133                ..Default::default()
134            },
135            RouteType::Fallback => Self {
136                base: base_segments,
137                fallback_sources: vec![source],
138                ..Default::default()
139            },
140            RouteType::NotFound => Self {
141                base: base_segments,
142                not_found_sources: vec![source],
143                ..Default::default()
144            },
145        }
146    }
147
148    async fn flat_merge(&mut self, others: impl IntoIterator<Item = &Self> + '_) -> Result<()> {
149        let mut static_segments = FxIndexMap::default();
150        for other in others {
151            debug_assert_eq!(self.base, other.base);
152            self.sources.extend(other.sources.iter().copied());
153            self.catch_all_sources
154                .extend(other.catch_all_sources.iter().copied());
155            self.fallback_sources
156                .extend(other.fallback_sources.iter().copied());
157            self.not_found_sources
158                .extend(other.not_found_sources.iter().copied());
159            for (key, value) in other.static_segments.iter() {
160                if let Some((key, self_value)) = self.static_segments.swap_remove_entry(key) {
161                    static_segments.insert(key, vec![self_value, *value]);
162                } else if let Some(list) = static_segments.get_mut(key) {
163                    list.push(*value);
164                } else {
165                    static_segments.insert(key.clone(), vec![*value]);
166                }
167            }
168            self.dynamic_segments
169                .extend(other.dynamic_segments.iter().copied());
170        }
171        self.static_segments.extend(
172            static_segments
173                .into_iter()
174                .map(|(key, value)| async {
175                    Ok((
176                        key,
177                        if value.len() == 1 {
178                            value.into_iter().next().unwrap()
179                        } else {
180                            Vc::<RouteTrees>::cell(value).merge().to_resolved().await?
181                        },
182                    ))
183                })
184                .try_join()
185                .await?,
186        );
187        Ok(())
188    }
189
190    fn prepend_base(&mut self, segments: Vec<BaseSegment>) {
191        self.base.splice(..0, segments);
192    }
193}
194
195#[turbo_tasks::value_impl]
196impl ValueToString for RouteTree {
197    #[turbo_tasks::function]
198    async fn to_string(&self) -> Result<Vc<RcStr>> {
199        let RouteTree {
200            base,
201            sources,
202            static_segments,
203            dynamic_segments,
204            catch_all_sources,
205            fallback_sources,
206            not_found_sources,
207        } = self;
208        let mut result = "RouteTree(".to_string();
209        for segment in base {
210            match segment {
211                BaseSegment::Static(str) => write!(result, "/{str}")?,
212                BaseSegment::Dynamic => result.push_str("/[dynamic]"),
213            }
214        }
215        if !base.is_empty() {
216            result.push_str(", ");
217        }
218        for (key, tree) in static_segments {
219            let tree = tree.to_string().await?;
220            write!(result, "{key}: {tree}, ")?;
221        }
222        if !sources.is_empty() {
223            write!(result, "{} x source, ", sources.len())?;
224        }
225        if !dynamic_segments.is_empty() {
226            write!(result, "{} x dynamic, ", dynamic_segments.len())?;
227        }
228        if !catch_all_sources.is_empty() {
229            write!(result, "{} x catch-all, ", catch_all_sources.len())?;
230        }
231        if !fallback_sources.is_empty() {
232            write!(result, "{} x fallback, ", fallback_sources.len())?;
233        }
234        if !not_found_sources.is_empty() {
235            write!(result, "{} x not-found, ", not_found_sources.len())?;
236        }
237        if result.ends_with(", ") {
238            result.truncate(result.len() - 2);
239        }
240        result.push(')');
241        Ok(Vc::cell(result.into()))
242    }
243}
244
245#[turbo_tasks::value_impl]
246impl RouteTree {
247    /// Creates an empty route tree.
248    #[turbo_tasks::function]
249    pub fn empty() -> Vc<RouteTree> {
250        RouteTree::default().cell()
251    }
252
253    /// Creates a route tree for a single route.
254    #[turbo_tasks::function]
255    pub fn new_route(
256        base_segments: Vec<BaseSegment>,
257        route_type: RouteType,
258        source: ResolvedVc<Box<dyn GetContentSourceContent>>,
259    ) -> Vc<Self> {
260        RouteTree::new_route_ref(base_segments, route_type, source).cell()
261    }
262
263    /// Gets the [`GetContentSourceContent`]s for the given path.
264    // TODO(WEB-1252) It's unnecessary to compute all [`GetContentSourceContent`]s at once, we could
265    // return some lazy iterator to make it more efficient.
266    #[turbo_tasks::function]
267    pub async fn get(self: Vc<Self>, path: RcStr) -> Result<Vc<GetContentSourceContents>> {
268        let RouteTree {
269            base,
270            sources,
271            static_segments,
272            dynamic_segments,
273            catch_all_sources,
274            fallback_sources,
275            not_found_sources,
276        } = &*self.await?;
277        let mut results = Vec::new();
278        if path.is_empty() {
279            if !base.is_empty() {
280                return Ok(Vc::cell(vec![]));
281            }
282            results.extend(sources.iter().copied());
283        } else {
284            let mut segments = path.split('/');
285            for base in base.iter() {
286                let Some(segment) = segments.next() else {
287                    return Ok(Vc::cell(vec![]));
288                };
289                match base {
290                    BaseSegment::Static(str) => {
291                        if str != segment {
292                            return Ok(Vc::cell(vec![]));
293                        }
294                    }
295                    BaseSegment::Dynamic => {
296                        // always matching
297                    }
298                }
299            }
300
301            if let Some(segment) = segments.next() {
302                let remainder = segments.remainder().unwrap_or("");
303                if let Some(tree) = static_segments.get(segment) {
304                    results.extend(tree.get(remainder.into()).await?.iter().copied());
305                }
306                for tree in dynamic_segments.iter() {
307                    results.extend(tree.get(remainder.into()).await?.iter().copied());
308                }
309            } else {
310                results.extend(sources.iter().copied());
311            };
312        }
313        results.extend(catch_all_sources.iter().copied());
314        results.extend(fallback_sources.iter().copied());
315        results.extend(not_found_sources.iter().copied());
316        Ok(Vc::cell(results))
317    }
318
319    /// Prepends a base path to all routes.
320    #[turbo_tasks::function]
321    pub async fn with_prepended_base(
322        self: Vc<Self>,
323        segments: Vec<BaseSegment>,
324    ) -> Result<Vc<RouteTree>> {
325        let mut this = self.owned().await?;
326        this.prepend_base(segments);
327        Ok(this.cell())
328    }
329
330    #[turbo_tasks::function]
331    async fn with_base_len(self: Vc<Self>, base_len: usize) -> Result<Vc<RouteTree>> {
332        let this = self.await?;
333        if this.base.len() > base_len {
334            let mut inner = ReadRef::into_owned(this);
335            let mut drain = inner.base.drain(base_len..);
336            let selector_segment = drain.next().unwrap();
337            let inner_base = drain.collect();
338            let base = replace(&mut inner.base, inner_base);
339            debug_assert!(base.len() == base_len);
340            match selector_segment {
341                BaseSegment::Static(value) => Ok(RouteTree {
342                    base,
343                    static_segments: fxindexmap! { value => inner.resolved_cell() },
344                    ..Default::default()
345                }
346                .cell()),
347                BaseSegment::Dynamic => Ok(RouteTree {
348                    base,
349                    dynamic_segments: vec![inner.resolved_cell()],
350                    ..Default::default()
351                }
352                .cell()),
353            }
354        } else {
355            Ok(self)
356        }
357    }
358
359    /// Applies a transformation on all [`GetContentSourceContent`]s in the
360    /// tree.
361    #[turbo_tasks::function]
362    pub async fn map_routes(
363        self: Vc<Self>,
364        mapper: Vc<Box<dyn MapGetContentSourceContent>>,
365    ) -> Result<Vc<Self>> {
366        let mut this = self.owned().await?;
367        let RouteTree {
368            base: _,
369            static_segments,
370            dynamic_segments,
371            sources,
372            catch_all_sources,
373            fallback_sources,
374            not_found_sources,
375        } = &mut this;
376
377        for s in sources.iter_mut() {
378            *s = mapper.map_get_content(**s).to_resolved().await?;
379        }
380        for s in catch_all_sources.iter_mut() {
381            *s = mapper.map_get_content(**s).to_resolved().await?;
382        }
383        for s in fallback_sources.iter_mut() {
384            *s = mapper.map_get_content(**s).to_resolved().await?;
385        }
386        for s in not_found_sources.iter_mut() {
387            *s = mapper.map_get_content(**s).to_resolved().await?;
388        }
389        for r in static_segments.values_mut() {
390            *r = r.map_routes(mapper).to_resolved().await?;
391        }
392        for r in dynamic_segments.iter_mut() {
393            *r = r.map_routes(mapper).to_resolved().await?;
394        }
395
396        Ok(this.cell())
397    }
398}
399
400/// Transformation functor
401#[turbo_tasks::value_trait]
402pub trait MapGetContentSourceContent {
403    #[turbo_tasks::function]
404    fn map_get_content(
405        self: Vc<Self>,
406        get_content: Vc<Box<dyn GetContentSourceContent>>,
407    ) -> Vc<Box<dyn GetContentSourceContent>>;
408}