Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

248 changes: 133 additions & 115 deletions crates/next-core/src/segment_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ use turbopack_core::{
};
use turbopack_ecmascript::{
EcmascriptInputTransforms, EcmascriptModuleAssetType,
analyzer::{ConstantNumber, ConstantValue, JsValue, ObjectPart, graph::EvalContext},
analyzer::{
Bump, ConstantNumber, ConstantValue, JsValue, ObjectPart, ThreadLocal, graph::EvalContext,
},
parse::{ParseResult, parse},
};

Expand Down Expand Up @@ -414,12 +416,25 @@ pub async fn parse_segment_config_from_source(
return Ok(Default::default());
};

// Arena for the `JsValue`s produced while evaluating config expressions;
// freed when this function returns.
let arena = ThreadLocal::new();
let config = WrapFuture::new(
async {
let mut config = NextSegmentConfig::default();

let mut parse = async |ident, init, span| {
parse_config_value(source, mode, &mut config, eval_context, ident, init, span).await
parse_config_value(
source,
mode,
&mut config,
eval_context,
&arena,
ident,
init,
span,
)
.await
};

for item in &module_ast.body {
Expand Down Expand Up @@ -576,7 +591,7 @@ async fn invalid_config(
key: &str,
span: Span,
error: RcStr,
value: Option<&JsValue>,
value: Option<&JsValue<'_>>,
severity: IssueSeverity,
) -> Result<()> {
let detail = if let Some(value) = value {
Expand Down Expand Up @@ -605,6 +620,7 @@ async fn parse_config_value(
mode: ParseSegmentMode,
config: &mut NextSegmentConfig,
eval_context: &EvalContext,
arena: &ThreadLocal<Bump>,
key: Cow<'_, str>,
init: Option<Cow<'_, Expr>>,
span: Span,
Expand All @@ -620,17 +636,18 @@ async fn parse_config_value(
| Some(Expr::TsSatisfies(TsSatisfiesExpr { expr, .. })) => Some(&**expr),
_ => init,
};
init.map(|init| eval_context.eval(init)).map(|v| {
// Special case, as we don't call `link` here: assume that `undefined` is a free
// variable.
if let JsValue::FreeVar(name) = &v
&& name == "undefined"
{
JsValue::Constant(ConstantValue::Undefined)
} else {
v
}
})
init.map(|init| eval_context.eval(arena.get_or_default(), init))
.map(|v| {
// Special case, as we don't call `link` here: assume that `undefined` is a free
// variable.
if let JsValue::FreeVar(name) = &v
&& name == "undefined"
{
JsValue::Constant(ConstantValue::Undefined)
} else {
v
}
})
};

match &*key {
Expand Down Expand Up @@ -1002,7 +1019,7 @@ async fn parse_static_string_or_array_from_js_value(
span: Span,
key: &str,
sub_key: &str,
value: &JsValue,
value: &JsValue<'_>,
) -> Result<Option<Vec<RcStr>>> {
Ok(match value {
// Single value is turned into a single-element Vec.
Expand Down Expand Up @@ -1055,129 +1072,130 @@ async fn parse_static_string_or_array_from_js_value(
async fn parse_route_matcher_from_js_value(
source: ResolvedVc<Box<dyn Source>>,
span: Span,
value: &JsValue,
value: &JsValue<'_>,
) -> Result<Option<Vec<MiddlewareMatcherKind>>> {
let parse_matcher_kind_matcher = async |value: &JsValue, sub_key: &str, matcher_idx: usize| {
let mut route_has = vec![];
if let JsValue::Array { items, .. } = value {
for (i, item) in items.iter().enumerate() {
if let JsValue::Object { parts, .. } = item {
let mut route_type = None;
let mut route_key = None;
let mut route_value = None;

for matcher_part in parts {
if let ObjectPart::KeyValue(part_key, part_value) = matcher_part {
match part_key.as_str() {
Some("type") => {
if let Some(part_value) = part_value.as_str().filter(|v| {
*v == "header"
|| *v == "cookie"
|| *v == "query"
|| *v == "host"
}) {
route_type = Some(part_value);
} else {
invalid_config(
source,
"config",
span,
format!(
"`matcher[{matcher_idx}].{sub_key}[{i}].type` \
must be one of the strings: 'header', 'cookie', \
'query', 'host'"
let parse_matcher_kind_matcher =
async |value: &JsValue<'_>, sub_key: &str, matcher_idx: usize| {
let mut route_has = vec![];
if let JsValue::Array { items, .. } = value {
for (i, item) in items.iter().enumerate() {
if let JsValue::Object { parts, .. } = item {
let mut route_type = None;
let mut route_key = None;
let mut route_value = None;

for matcher_part in parts {
if let ObjectPart::KeyValue(part_key, part_value) = matcher_part {
match part_key.as_str() {
Some("type") => {
if let Some(part_value) = part_value.as_str().filter(|v| {
*v == "header"
|| *v == "cookie"
|| *v == "query"
|| *v == "host"
}) {
route_type = Some(part_value);
} else {
invalid_config(
source,
"config",
span,
format!(
"`matcher[{matcher_idx}].{sub_key}[{i}].type` \
must be one of the strings: 'header', \
'cookie', 'query', 'host'"
)
.into(),
Some(part_value),
IssueSeverity::Error,
)
.into(),
Some(part_value),
IssueSeverity::Error,
)
.await?;
.await?;
}
}
}
Some("key") => {
if let Some(part_value) = part_value.as_str() {
route_key = Some(part_value);
} else {
invalid_config(
source,
"config",
span,
format!(
"`matcher[{matcher_idx}].{sub_key}[{i}].key` must \
be a string"
Some("key") => {
if let Some(part_value) = part_value.as_str() {
route_key = Some(part_value);
} else {
invalid_config(
source,
"config",
span,
format!(
"`matcher[{matcher_idx}].{sub_key}[{i}].key` \
must be a string"
)
.into(),
Some(part_value),
IssueSeverity::Error,
)
.into(),
Some(part_value),
IssueSeverity::Error,
)
.await?;
.await?;
}
}
}
Some("value") => {
if let Some(part_value) = part_value.as_str() {
route_value = Some(part_value);
} else {
Some("value") => {
if let Some(part_value) = part_value.as_str() {
route_value = Some(part_value);
} else {
invalid_config(
source,
"config",
span,
format!(
"`matcher[{matcher_idx}].{sub_key}[{i}].\
value` must be a string"
)
.into(),
Some(part_value),
IssueSeverity::Error,
)
.await?;
}
}
_ => {
invalid_config(
source,
"config",
span,
format!(
"`matcher[{matcher_idx}].{sub_key}[{i}].value` \
must be a string"
"Unexpected property in \
`matcher[{matcher_idx}].{sub_key}[{i}]` object"
)
.into(),
Some(part_value),
Some(part_key),
IssueSeverity::Error,
)
.await?;
}
}
_ => {
invalid_config(
source,
"config",
span,
format!(
"Unexpected property in \
`matcher[{matcher_idx}].{sub_key}[{i}]` object"
)
.into(),
Some(part_key),
IssueSeverity::Error,
)
.await?;
}
}
}
}
let r = match route_type {
Some("header") => route_key.map(|route_key| RouteHas::Header {
key: route_key.into(),
value: route_value.map(From::from),
}),
Some("cookie") => route_key.map(|route_key| RouteHas::Cookie {
key: route_key.into(),
value: route_value.map(From::from),
}),
Some("query") => route_key.map(|route_key| RouteHas::Query {
key: route_key.into(),
value: route_value.map(From::from),
}),
Some("host") => route_value.map(|route_value| RouteHas::Host {
value: route_value.into(),
}),
_ => None,
};

if let Some(r) = r {
route_has.push(r);
let r = match route_type {
Some("header") => route_key.map(|route_key| RouteHas::Header {
key: route_key.into(),
value: route_value.map(From::from),
}),
Some("cookie") => route_key.map(|route_key| RouteHas::Cookie {
key: route_key.into(),
value: route_value.map(From::from),
}),
Some("query") => route_key.map(|route_key| RouteHas::Query {
key: route_key.into(),
value: route_value.map(From::from),
}),
Some("host") => route_value.map(|route_value| RouteHas::Host {
value: route_value.into(),
}),
_ => None,
};

if let Some(r) = r {
route_has.push(r);
}
}
}
}
}

anyhow::Ok(route_has)
};
anyhow::Ok(route_has)
};

let mut matchers = vec![];

Expand Down
1 change: 1 addition & 0 deletions turbopack/crates/turbopack-ecmascript/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ serde_json = { workspace = true, features = ["raw_value"] }
swc_sourcemap = { workspace = true }
smallvec = { workspace = true }
strsim = { workspace = true }
thread_local = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
turbo-bincode = { workspace = true }
Expand Down
Loading
Loading