Skip to content

Commit

Permalink
feat(next-core): support parsing matcher config object
Browse files Browse the repository at this point in the history
  • Loading branch information
kwonoj committed Apr 17, 2024
1 parent 048697b commit 26bb562
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 79 deletions.
11 changes: 7 additions & 4 deletions packages/next-swc/crates/next-api/src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use next_core::{
next_edge::entry::wrap_edge_entry,
next_manifests::{EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2},
next_server::{get_server_runtime_entries, ServerContextType},
util::parse_config_from_source,
util::{parse_config_from_source, MiddlewareMatcherKind},
};
use tracing::Instrument;
use turbo_tasks::{Completion, Value, Vc};
Expand Down Expand Up @@ -138,9 +138,12 @@ impl MiddlewareEndpoint {
let matchers = if let Some(matchers) = config.await?.matcher.as_ref() {
matchers
.iter()
.map(|matcher| MiddlewareMatcher {
original_source: matcher.to_string(),
..Default::default()
.map(|matcher| match matcher {
MiddlewareMatcherKind::Str(matchers) => MiddlewareMatcher {
original_source: matchers.to_string(),
..Default::default()
},
MiddlewareMatcherKind::Matcher(matcher) => matcher.clone(),
})
.collect()
} else {
Expand Down
16 changes: 14 additions & 2 deletions packages/next-swc/crates/next-core/src/next_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anyhow::{bail, Context, Result};
use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value as JsonValue;
use turbo_tasks::{trace::TraceRawVcs, Vc};
use turbo_tasks::{trace::TraceRawVcs, TaskInput, Vc};
use turbopack_binding::{
turbo::{tasks_env::EnvMap, tasks_fs::FileSystemPath},
turbopack::{
Expand Down Expand Up @@ -193,7 +193,19 @@ pub enum OutputType {
Export,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
#[derive(
Debug,
Clone,
Hash,
Eq,
PartialEq,
Ord,
PartialOrd,
TaskInput,
TraceRawVcs,
Serialize,
Deserialize,
)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum RouteHas {
Header {
Expand Down
40 changes: 15 additions & 25 deletions packages/next-swc/crates/next-core/src/next_manifests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use indexmap::IndexSet;
use serde::{Deserialize, Serialize};
use turbo_tasks::{trace::TraceRawVcs, TaskInput};

use crate::next_config::{CrossOriginConfig, Rewrites};
use crate::next_config::{CrossOriginConfig, Rewrites, RouteHas};

#[derive(Serialize, Default, Debug)]
pub struct PagesManifest {
Expand Down Expand Up @@ -44,30 +44,20 @@ impl Default for MiddlewaresManifest {
}
}

#[derive(Serialize, Debug)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum RouteHas {
Header {
key: String,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<String>,
},
Cookie {
key: String,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<String>,
},
Query {
key: String,
#[serde(skip_serializing_if = "Option::is_none")]
value: Option<String>,
},
Host {
value: String,
},
}

#[derive(Serialize, Default, Debug)]
#[derive(
Debug,
Clone,
Hash,
Eq,
PartialEq,
Ord,
PartialOrd,
TaskInput,
TraceRawVcs,
Serialize,
Deserialize,
Default,
)]
#[serde(rename_all = "camelCase")]
pub struct MiddlewareMatcher {
// When skipped next.js with fill that during merging.
Expand Down
207 changes: 159 additions & 48 deletions packages/next-swc/crates/next-core/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ use turbopack_binding::{
},
};

use crate::{next_config::NextConfig, next_import_map::get_next_package};
use crate::{
next_config::{NextConfig, RouteHas},
next_import_map::get_next_package,
next_manifests::MiddlewareMatcher,
};

const NEXT_TEMPLATE_PATH: &str = "dist/esm/build/templates";

Expand Down Expand Up @@ -151,13 +155,20 @@ impl NextRuntime {
}
}

#[turbo_tasks::value]
#[derive(Debug, Clone)]
pub enum MiddlewareMatcherKind {
Str(String),
Matcher(MiddlewareMatcher),
}

#[turbo_tasks::value]
#[derive(Default, Clone)]
pub struct NextSourceConfig {
pub runtime: NextRuntime,

/// Middleware router matchers
pub matcher: Option<Vec<String>>,
pub matcher: Option<Vec<MiddlewareMatcherKind>>,
}

#[turbo_tasks::value_impl]
Expand Down Expand Up @@ -215,6 +226,139 @@ impl Issue for NextSourceConfigParsingIssue {
}
}

fn emit_invalid_config_warning(ident: Vc<AssetIdent>, detail: &str, value: &JsValue) {
let (explainer, hints) = value.explain(2, 0);
NextSourceConfigParsingIssue {
ident,
detail: StyledString::Text(format!("{detail} Got {explainer}.{hints}")).cell(),
}
.cell()
.emit()
}

fn parse_route_matcher_from_js_value(
ident: Vc<AssetIdent>,
value: &JsValue,
) -> Option<Vec<MiddlewareMatcherKind>> {
let parse_matcher_kind_matcher = |value: &JsValue| {
let mut route_has = vec![];
if let JsValue::Array { items, .. } = value {
for item in items {
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") => {
route_type = part_value.as_str().map(|v| v.to_string())
}
Some("key") => {
route_key = part_value.as_str().map(|v| v.to_string())
}
Some("value") => {
route_value = part_value.as_str().map(|v| v.to_string())
}
_ => {}
}
}
}
let r = match route_type.as_deref() {
Some("header") => route_key.map(|route_key| RouteHas::Header {
key: route_key,
value: route_value,
}),
Some("cookie") => route_key.map(|route_key| RouteHas::Cookie {
key: route_key,
value: route_value,
}),
Some("query") => route_key.map(|route_key| RouteHas::Query {
key: route_key,
value: route_value,
}),
Some("host") => {
route_value.map(|route_value| RouteHas::Host { value: route_value })
}
_ => None,
};

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

route_has
};

let mut matchers = vec![];

match value {
JsValue::Constant(matcher) => {
if let Some(matcher) = matcher.as_str() {
matchers.push(MiddlewareMatcherKind::Str(matcher.to_string()));
} else {
emit_invalid_config_warning(
ident,
"The matcher property must be a string or array of strings",
value,
);
}
}
JsValue::Array { items, .. } => {
for item in items {
if let Some(matcher) = item.as_str() {
matchers.push(MiddlewareMatcherKind::Str(matcher.to_string()));
} else if let JsValue::Object { parts, .. } = item {
let mut matcher = MiddlewareMatcher::default();
for matcher_part in parts {
if let ObjectPart::KeyValue(key, value) = matcher_part {
match key.as_str() {
Some("source") => {
if let Some(value) = value.as_str() {
matcher.original_source = value.to_string();
}
}
Some("missing") => {
matcher.missing = Some(parse_matcher_kind_matcher(value))
}
Some("has") => {
matcher.has = Some(parse_matcher_kind_matcher(value))
}
_ => {
//noop
}
}
}
}

matchers.push(MiddlewareMatcherKind::Matcher(matcher));
} else {
emit_invalid_config_warning(
ident,
"The matcher property must be a string or array of strings",
value,
);
}
}
}
_ => emit_invalid_config_warning(
ident,
"The matcher property must be a string or array of strings",
value,
),
}

if matchers.is_empty() {
None
} else {
Some(matchers)
}
}

#[turbo_tasks::function]
pub async fn parse_config_from_source(module: Vc<Box<dyn Module>>) -> Result<Vc<NextSourceConfig>> {
if let Some(ecmascript_asset) =
Expand Down Expand Up @@ -323,19 +467,12 @@ pub async fn parse_config_from_source(module: Vc<Box<dyn Module>>) -> Result<Vc<

fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> NextSourceConfig {
let mut config = NextSourceConfig::default();
let invalid_config = |detail: &str, value: &JsValue| {
let (explainer, hints) = value.explain(2, 0);
NextSourceConfigParsingIssue {
ident: module.ident(),
detail: StyledString::Text(format!("{detail} Got {explainer}.{hints}")).cell(),
}
.cell()
.emit()
};

if let JsValue::Object { parts, .. } = value {
for part in parts {
match part {
ObjectPart::Spread(_) => invalid_config(
ObjectPart::Spread(_) => emit_invalid_config_warning(
module.ident(),
"Spread properties are not supported in the config export.",
value,
),
Expand All @@ -352,7 +489,8 @@ fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> N
config.runtime = NextRuntime::NodeJs;
}
_ => {
invalid_config(
emit_invalid_config_warning(
module.ident(),
"The runtime property must be either \"nodejs\" \
or \"edge\".",
value,
Expand All @@ -361,48 +499,20 @@ fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> N
}
}
} else {
invalid_config(
emit_invalid_config_warning(
module.ident(),
"The runtime property must be a constant string.",
value,
);
}
}
if key == "matcher" {
let mut matchers = vec![];
match value {
JsValue::Constant(matcher) => {
if let Some(matcher) = matcher.as_str() {
matchers.push(matcher.to_string());
} else {
invalid_config(
"The matcher property must be a string or array of \
strings",
value,
);
}
}
JsValue::Array { items, .. } => {
for item in items {
if let Some(matcher) = item.as_str() {
matchers.push(matcher.to_string());
} else {
invalid_config(
"The matcher property must be a string or array \
of strings",
value,
);
}
}
}
_ => invalid_config(
"The matcher property must be a string or array of strings",
value,
),
}
config.matcher = Some(matchers);
config.matcher =
parse_route_matcher_from_js_value(module.ident(), value);
}
} else {
invalid_config(
emit_invalid_config_warning(
module.ident(),
"The exported config object must not contain non-constant strings.",
key,
);
Expand All @@ -411,7 +521,8 @@ fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> N
}
}
} else {
invalid_config(
emit_invalid_config_warning(
module.ident(),
"The exported config object must be a valid object literal.",
value,
);
Expand Down
1 change: 1 addition & 0 deletions run-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ async function main() {
if (tests.length)
testPatternRegex = new RegExp(tests.map(escapeRegexp).join('|'))


console.log('Running related tests:', testPatternRegex.toString())
}

Expand Down

0 comments on commit 26bb562

Please # to comment.