Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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