Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare RouterContentSource for basePath #5218

Merged
merged 3 commits into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 128 additions & 35 deletions crates/turbopack-dev-server/src/source/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,105 @@ use crate::source::ContentSourcesVc;

/// Binds different ContentSources to different subpaths. A fallback
/// ContentSource will serve all other subpaths.
// TODO(WEB-1151): Remove this and migrate all users to PrefixedRouterContentSource.
#[turbo_tasks::value(shared)]
pub struct RouterContentSource {
pub routes: Vec<(String, ContentSourceVc)>,
pub fallback: ContentSourceVc,
}

impl RouterContentSource {
fn get_source<'s, 'a>(&'s self, path: &'a str) -> (&'s ContentSourceVc, &'a str) {
for (route, source) in self.routes.iter() {
if path.starts_with(route) {
let path = &path[route.len()..];
return (source, path);
/// Binds different ContentSources to different subpaths. The request path must
/// begin with the prefix, which will be stripped (along with the subpath)
/// before querying the ContentSource. A fallback ContentSource will serve all
/// other subpaths, including if the request path does not include the prefix.
#[turbo_tasks::value(shared)]
pub struct PrefixedRouterContentSource {
prefix: StringVc,
routes: Vec<(String, ContentSourceVc)>,
fallback: ContentSourceVc,
}

#[turbo_tasks::value_impl]
impl PrefixedRouterContentSourceVc {
#[turbo_tasks::function]
async fn new(
prefix: StringVc,
routes: Vec<(String, ContentSourceVc)>,
fallback: ContentSourceVc,
) -> Result<Self> {
if cfg!(debug_assertions) {
let prefix_string = prefix.await?;
debug_assert!(prefix_string.is_empty() || prefix_string.ends_with('/'));
debug_assert!(prefix_string.starts_with('/'));
}
Comment on lines +36 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also be achieved with a BasePath newtype (BasePathVc), which would make these assertions in its String -> BasePath constructor.

But I don't think the newtype Vc story is good enough to warrant this for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could generalize this to a PathStr type, which would have match these same rules. All ContentSource's would receive this as the path param. PrefixedRouterContentSource and StaticAssetsContentSource could receive them as the prefix as well.

Ok(PrefixedRouterContentSource {
prefix,
routes,
fallback,
}
.cell())
}
}

/// If the `path` starts with `prefix`, then it will search each route to see if
/// any subpath matches. If so, the remaining path (after removing the prefix
/// and subpath) is used to query the matching ContentSource. If no match is
/// found, then the fallback is queried with the original path.
async fn get(
routes: &[(String, ContentSourceVc)],
fallback: &ContentSourceVc,
prefix: &str,
path: &str,
data: Value<ContentSourceData>,
) -> Result<ContentSourceResultVc> {
let mut found = None;

if let Some(path) = path.strip_prefix(prefix) {
for (subpath, source) in routes {
if let Some(path) = path.strip_prefix(subpath) {
found = Some((source, path));
break;
}
}
(&self.fallback, path)
}

let (source, path) = found.unwrap_or((fallback, path));
Ok(source.resolve().await?.get(path, data))
}

fn get_children(
routes: &[(String, ContentSourceVc)],
fallback: &ContentSourceVc,
) -> ContentSourcesVc {
ContentSourcesVc::cell(
routes
.iter()
.map(|r| r.1)
.chain(std::iter::once(*fallback))
.collect(),
)
}

async fn get_introspection_children(
routes: &[(String, ContentSourceVc)],
fallback: &ContentSourceVc,
) -> Result<IntrospectableChildrenVc> {
Ok(IntrospectableChildrenVc::cell(
routes
.iter()
.cloned()
.chain(std::iter::once((String::new(), *fallback)))
.map(|(path, source)| async move {
Ok(IntrospectableVc::resolve_from(source)
.await?
.map(|i| (StringVc::cell(path), i)))
})
.try_join()
.await?
.into_iter()
.flatten()
.collect(),
))
}

#[turbo_tasks::value_impl]
Expand All @@ -33,51 +116,61 @@ impl ContentSource for RouterContentSource {
path: &str,
data: Value<ContentSourceData>,
) -> Result<ContentSourceResultVc> {
let (source, path) = self.get_source(path);
Ok(source.resolve().await?.get(path, data))
get(&self.routes, &self.fallback, "", path, data).await
}

#[turbo_tasks::function]
fn get_children(&self) -> ContentSourcesVc {
let mut sources = Vec::with_capacity(self.routes.len() + 1);
get_children(&self.routes, &self.fallback)
}
}

sources.extend(self.routes.iter().map(|r| r.1));
sources.push(self.fallback);
#[turbo_tasks::value_impl]
impl Introspectable for RouterContentSource {
#[turbo_tasks::function]
fn ty(&self) -> StringVc {
StringVc::cell("router content source".to_string())
}

ContentSourcesVc::cell(sources)
#[turbo_tasks::function]
async fn children(&self) -> Result<IntrospectableChildrenVc> {
get_introspection_children(&self.routes, &self.fallback).await
}
}

#[turbo_tasks::function]
fn introspectable_type() -> StringVc {
StringVc::cell("router content source".to_string())
#[turbo_tasks::value_impl]
impl ContentSource for PrefixedRouterContentSource {
#[turbo_tasks::function]
async fn get(
&self,
path: &str,
data: Value<ContentSourceData>,
) -> Result<ContentSourceResultVc> {
let prefix = self.prefix.await?;
get(&self.routes, &self.fallback, &prefix, path, data).await
}

#[turbo_tasks::function]
fn get_children(&self) -> ContentSourcesVc {
get_children(&self.routes, &self.fallback)
}
}

#[turbo_tasks::value_impl]
impl Introspectable for RouterContentSource {
impl Introspectable for PrefixedRouterContentSource {
#[turbo_tasks::function]
fn ty(&self) -> StringVc {
introspectable_type()
StringVc::cell("prefixed router content source".to_string())
}

#[turbo_tasks::function]
async fn details(&self) -> Result<StringVc> {
let prefix = self.prefix.await?;
Ok(StringVc::cell(format!("prefix: '{}'", prefix)))
}

#[turbo_tasks::function]
async fn children(&self) -> Result<IntrospectableChildrenVc> {
Ok(IntrospectableChildrenVc::cell(
self.routes
.iter()
.cloned()
.chain(std::iter::once((String::new(), self.fallback)))
.map(|(path, source)| (StringVc::cell(path), source))
.map(|(path, source)| async move {
Ok(IntrospectableVc::resolve_from(source)
.await?
.map(|i| (path, i)))
})
.try_join()
.await?
.into_iter()
.flatten()
.collect(),
))
get_introspection_children(&self.routes, &self.fallback).await
}
}
2 changes: 2 additions & 0 deletions crates/turbopack-dev-server/src/source/static_assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct StaticAssetsContentSource {

#[turbo_tasks::value_impl]
impl StaticAssetsContentSourceVc {
// TODO(WEB-1151): Remove this method and migrate users to `with_prefix`.
#[turbo_tasks::function]
pub fn new(prefix: String, dir: FileSystemPathVc) -> StaticAssetsContentSourceVc {
StaticAssetsContentSourceVc::with_prefix(StringVc::cell(prefix), dir)
Expand All @@ -35,6 +36,7 @@ impl StaticAssetsContentSourceVc {
if cfg!(debug_assertions) {
let prefix_string = prefix.await?;
debug_assert!(prefix_string.is_empty() || prefix_string.ends_with('/'));
debug_assert!(!prefix_string.starts_with('/'));
}
Ok(StaticAssetsContentSource { prefix, dir }.cell())
}
Expand Down