turbopack_dev_server/source/
combined.rs

1use anyhow::Result;
2use turbo_rcstr::RcStr;
3use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
4use turbopack_core::introspect::{Introspectable, IntrospectableChildren};
5
6use super::{
7    ContentSource,
8    route_tree::{RouteTree, RouteTrees},
9};
10use crate::source::ContentSources;
11
12/// Combines multiple [ContentSource]s by trying all content sources in order.
13///
14/// The content source which responds with the most specific response (that is
15/// not a [ContentSourceContent::NotFound]) will be returned.
16#[turbo_tasks::value(shared)]
17pub struct CombinedContentSource {
18    pub sources: Vec<ResolvedVc<Box<dyn ContentSource>>>,
19}
20
21impl CombinedContentSource {
22    pub fn new(sources: Vec<ResolvedVc<Box<dyn ContentSource>>>) -> Vc<Self> {
23        CombinedContentSource { sources }.cell()
24    }
25}
26
27#[turbo_tasks::value_impl]
28impl ContentSource for CombinedContentSource {
29    #[turbo_tasks::function]
30    async fn get_routes(&self) -> Result<Vc<RouteTree>> {
31        let all_routes = self
32            .sources
33            .iter()
34            .map(|s| async move { s.get_routes().to_resolved().await })
35            .try_join()
36            .await?;
37        Ok(Vc::<RouteTrees>::cell(all_routes).merge())
38    }
39
40    #[turbo_tasks::function]
41    fn get_children(&self) -> Vc<ContentSources> {
42        Vc::cell(self.sources.clone())
43    }
44}
45
46#[turbo_tasks::value_impl]
47impl Introspectable for CombinedContentSource {
48    #[turbo_tasks::function]
49    fn ty(&self) -> Vc<RcStr> {
50        Vc::cell("combined content source".into())
51    }
52
53    #[turbo_tasks::function]
54    async fn title(&self) -> Result<Vc<RcStr>> {
55        let titles = self
56            .sources
57            .iter()
58            .map(|&source| async move {
59                Ok(
60                    if let Some(source) =
61                        ResolvedVc::try_sidecast::<Box<dyn Introspectable>>(source)
62                    {
63                        Some(source.title().await?)
64                    } else {
65                        None
66                    },
67                )
68            })
69            .try_join()
70            .await?;
71        let mut titles = titles.into_iter().flatten().collect::<Vec<_>>();
72        titles.sort();
73        const NUMBER_OF_TITLES_TO_DISPLAY: usize = 5;
74        let mut titles = titles
75            .iter()
76            .map(|t| t.as_str())
77            .filter(|t| !t.is_empty())
78            .take(NUMBER_OF_TITLES_TO_DISPLAY + 1)
79            .collect::<Vec<_>>();
80        if titles.len() > NUMBER_OF_TITLES_TO_DISPLAY {
81            titles[NUMBER_OF_TITLES_TO_DISPLAY] = "...";
82        }
83        Ok(Vc::cell(titles.join(", ").into()))
84    }
85
86    #[turbo_tasks::function]
87    async fn children(&self) -> Result<Vc<IntrospectableChildren>> {
88        let source = ResolvedVc::cell("source".into());
89        Ok(Vc::cell(
90            self.sources
91                .iter()
92                .copied()
93                .map(|s| async move { Ok(ResolvedVc::try_sidecast::<Box<dyn Introspectable>>(s)) })
94                .try_join()
95                .await?
96                .into_iter()
97                .flatten()
98                .map(|i| (source, i))
99                .collect(),
100        ))
101    }
102}