Skip to content

Commit c251de8

Browse files
authored
feat(next-core): support parsing matcher config object (#64678)
### What - closes #63896 PR implements parsing JSValue for the matcher config if given item is an object. We had those types already declared in place but somehow parsing ignores it.
1 parent 6370155 commit c251de8

File tree

6 files changed

+213
-79
lines changed

6 files changed

+213
-79
lines changed

packages/next-swc/crates/next-api/src/middleware.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use next_core::{
55
next_edge::entry::wrap_edge_entry,
66
next_manifests::{EdgeFunctionDefinition, MiddlewareMatcher, MiddlewaresManifestV2},
77
next_server::{get_server_runtime_entries, ServerContextType},
8-
util::parse_config_from_source,
8+
util::{parse_config_from_source, MiddlewareMatcherKind},
99
};
1010
use tracing::Instrument;
1111
use turbo_tasks::{Completion, Value, Vc};
@@ -138,9 +138,12 @@ impl MiddlewareEndpoint {
138138
let matchers = if let Some(matchers) = config.await?.matcher.as_ref() {
139139
matchers
140140
.iter()
141-
.map(|matcher| MiddlewareMatcher {
142-
original_source: matcher.to_string(),
143-
..Default::default()
141+
.map(|matcher| match matcher {
142+
MiddlewareMatcherKind::Str(matchers) => MiddlewareMatcher {
143+
original_source: matchers.to_string(),
144+
..Default::default()
145+
},
146+
MiddlewareMatcherKind::Matcher(matcher) => matcher.clone(),
144147
})
145148
.collect()
146149
} else {

packages/next-swc/crates/next-core/src/next_config.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use anyhow::{bail, Context, Result};
44
use indexmap::IndexMap;
55
use serde::{Deserialize, Deserializer, Serialize};
66
use serde_json::Value as JsonValue;
7-
use turbo_tasks::{trace::TraceRawVcs, Vc};
7+
use turbo_tasks::{trace::TraceRawVcs, TaskInput, Vc};
88
use turbopack_binding::{
99
turbo::{tasks_env::EnvMap, tasks_fs::FileSystemPath},
1010
turbopack::{
@@ -197,7 +197,19 @@ pub enum OutputType {
197197
Export,
198198
}
199199

200-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TraceRawVcs)]
200+
#[derive(
201+
Debug,
202+
Clone,
203+
Hash,
204+
Eq,
205+
PartialEq,
206+
Ord,
207+
PartialOrd,
208+
TaskInput,
209+
TraceRawVcs,
210+
Serialize,
211+
Deserialize,
212+
)]
201213
#[serde(tag = "type", rename_all = "kebab-case")]
202214
pub enum RouteHas {
203215
Header {

packages/next-swc/crates/next-core/src/next_manifests/mod.rs

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use indexmap::IndexSet;
88
use serde::{Deserialize, Serialize};
99
use turbo_tasks::{trace::TraceRawVcs, TaskInput};
1010

11-
use crate::next_config::{CrossOriginConfig, Rewrites};
11+
use crate::next_config::{CrossOriginConfig, Rewrites, RouteHas};
1212

1313
#[derive(Serialize, Default, Debug)]
1414
pub struct PagesManifest {
@@ -44,30 +44,20 @@ impl Default for MiddlewaresManifest {
4444
}
4545
}
4646

47-
#[derive(Serialize, Debug)]
48-
#[serde(tag = "type", rename_all = "kebab-case")]
49-
pub enum RouteHas {
50-
Header {
51-
key: String,
52-
#[serde(skip_serializing_if = "Option::is_none")]
53-
value: Option<String>,
54-
},
55-
Cookie {
56-
key: String,
57-
#[serde(skip_serializing_if = "Option::is_none")]
58-
value: Option<String>,
59-
},
60-
Query {
61-
key: String,
62-
#[serde(skip_serializing_if = "Option::is_none")]
63-
value: Option<String>,
64-
},
65-
Host {
66-
value: String,
67-
},
68-
}
69-
70-
#[derive(Serialize, Default, Debug)]
47+
#[derive(
48+
Debug,
49+
Clone,
50+
Hash,
51+
Eq,
52+
PartialEq,
53+
Ord,
54+
PartialOrd,
55+
TaskInput,
56+
TraceRawVcs,
57+
Serialize,
58+
Deserialize,
59+
Default,
60+
)]
7161
#[serde(rename_all = "camelCase")]
7262
pub struct MiddlewareMatcher {
7363
// When skipped next.js with fill that during merging.

packages/next-swc/crates/next-core/src/util.rs

Lines changed: 159 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ use turbopack_binding::{
2828
},
2929
};
3030

31-
use crate::{next_config::NextConfig, next_import_map::get_next_package};
31+
use crate::{
32+
next_config::{NextConfig, RouteHas},
33+
next_import_map::get_next_package,
34+
next_manifests::MiddlewareMatcher,
35+
};
3236

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

@@ -151,13 +155,20 @@ impl NextRuntime {
151155
}
152156
}
153157

158+
#[turbo_tasks::value]
159+
#[derive(Debug, Clone)]
160+
pub enum MiddlewareMatcherKind {
161+
Str(String),
162+
Matcher(MiddlewareMatcher),
163+
}
164+
154165
#[turbo_tasks::value]
155166
#[derive(Default, Clone)]
156167
pub struct NextSourceConfig {
157168
pub runtime: NextRuntime,
158169

159170
/// Middleware router matchers
160-
pub matcher: Option<Vec<String>>,
171+
pub matcher: Option<Vec<MiddlewareMatcherKind>>,
161172
}
162173

163174
#[turbo_tasks::value_impl]
@@ -215,6 +226,139 @@ impl Issue for NextSourceConfigParsingIssue {
215226
}
216227
}
217228

229+
fn emit_invalid_config_warning(ident: Vc<AssetIdent>, detail: &str, value: &JsValue) {
230+
let (explainer, hints) = value.explain(2, 0);
231+
NextSourceConfigParsingIssue {
232+
ident,
233+
detail: StyledString::Text(format!("{detail} Got {explainer}.{hints}")).cell(),
234+
}
235+
.cell()
236+
.emit()
237+
}
238+
239+
fn parse_route_matcher_from_js_value(
240+
ident: Vc<AssetIdent>,
241+
value: &JsValue,
242+
) -> Option<Vec<MiddlewareMatcherKind>> {
243+
let parse_matcher_kind_matcher = |value: &JsValue| {
244+
let mut route_has = vec![];
245+
if let JsValue::Array { items, .. } = value {
246+
for item in items {
247+
if let JsValue::Object { parts, .. } = item {
248+
let mut route_type = None;
249+
let mut route_key = None;
250+
let mut route_value = None;
251+
252+
for matcher_part in parts {
253+
if let ObjectPart::KeyValue(part_key, part_value) = matcher_part {
254+
match part_key.as_str() {
255+
Some("type") => {
256+
route_type = part_value.as_str().map(|v| v.to_string())
257+
}
258+
Some("key") => {
259+
route_key = part_value.as_str().map(|v| v.to_string())
260+
}
261+
Some("value") => {
262+
route_value = part_value.as_str().map(|v| v.to_string())
263+
}
264+
_ => {}
265+
}
266+
}
267+
}
268+
let r = match route_type.as_deref() {
269+
Some("header") => route_key.map(|route_key| RouteHas::Header {
270+
key: route_key,
271+
value: route_value,
272+
}),
273+
Some("cookie") => route_key.map(|route_key| RouteHas::Cookie {
274+
key: route_key,
275+
value: route_value,
276+
}),
277+
Some("query") => route_key.map(|route_key| RouteHas::Query {
278+
key: route_key,
279+
value: route_value,
280+
}),
281+
Some("host") => {
282+
route_value.map(|route_value| RouteHas::Host { value: route_value })
283+
}
284+
_ => None,
285+
};
286+
287+
if let Some(r) = r {
288+
route_has.push(r);
289+
}
290+
}
291+
}
292+
}
293+
294+
route_has
295+
};
296+
297+
let mut matchers = vec![];
298+
299+
match value {
300+
JsValue::Constant(matcher) => {
301+
if let Some(matcher) = matcher.as_str() {
302+
matchers.push(MiddlewareMatcherKind::Str(matcher.to_string()));
303+
} else {
304+
emit_invalid_config_warning(
305+
ident,
306+
"The matcher property must be a string or array of strings",
307+
value,
308+
);
309+
}
310+
}
311+
JsValue::Array { items, .. } => {
312+
for item in items {
313+
if let Some(matcher) = item.as_str() {
314+
matchers.push(MiddlewareMatcherKind::Str(matcher.to_string()));
315+
} else if let JsValue::Object { parts, .. } = item {
316+
let mut matcher = MiddlewareMatcher::default();
317+
for matcher_part in parts {
318+
if let ObjectPart::KeyValue(key, value) = matcher_part {
319+
match key.as_str() {
320+
Some("source") => {
321+
if let Some(value) = value.as_str() {
322+
matcher.original_source = value.to_string();
323+
}
324+
}
325+
Some("missing") => {
326+
matcher.missing = Some(parse_matcher_kind_matcher(value))
327+
}
328+
Some("has") => {
329+
matcher.has = Some(parse_matcher_kind_matcher(value))
330+
}
331+
_ => {
332+
//noop
333+
}
334+
}
335+
}
336+
}
337+
338+
matchers.push(MiddlewareMatcherKind::Matcher(matcher));
339+
} else {
340+
emit_invalid_config_warning(
341+
ident,
342+
"The matcher property must be a string or array of strings",
343+
value,
344+
);
345+
}
346+
}
347+
}
348+
_ => emit_invalid_config_warning(
349+
ident,
350+
"The matcher property must be a string or array of strings",
351+
value,
352+
),
353+
}
354+
355+
if matchers.is_empty() {
356+
None
357+
} else {
358+
Some(matchers)
359+
}
360+
}
361+
218362
#[turbo_tasks::function]
219363
pub async fn parse_config_from_source(module: Vc<Box<dyn Module>>) -> Result<Vc<NextSourceConfig>> {
220364
if let Some(ecmascript_asset) =
@@ -323,19 +467,12 @@ pub async fn parse_config_from_source(module: Vc<Box<dyn Module>>) -> Result<Vc<
323467

324468
fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> NextSourceConfig {
325469
let mut config = NextSourceConfig::default();
326-
let invalid_config = |detail: &str, value: &JsValue| {
327-
let (explainer, hints) = value.explain(2, 0);
328-
NextSourceConfigParsingIssue {
329-
ident: module.ident(),
330-
detail: StyledString::Text(format!("{detail} Got {explainer}.{hints}")).cell(),
331-
}
332-
.cell()
333-
.emit()
334-
};
470+
335471
if let JsValue::Object { parts, .. } = value {
336472
for part in parts {
337473
match part {
338-
ObjectPart::Spread(_) => invalid_config(
474+
ObjectPart::Spread(_) => emit_invalid_config_warning(
475+
module.ident(),
339476
"Spread properties are not supported in the config export.",
340477
value,
341478
),
@@ -352,7 +489,8 @@ fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> N
352489
config.runtime = NextRuntime::NodeJs;
353490
}
354491
_ => {
355-
invalid_config(
492+
emit_invalid_config_warning(
493+
module.ident(),
356494
"The runtime property must be either \"nodejs\" \
357495
or \"edge\".",
358496
value,
@@ -361,48 +499,20 @@ fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> N
361499
}
362500
}
363501
} else {
364-
invalid_config(
502+
emit_invalid_config_warning(
503+
module.ident(),
365504
"The runtime property must be a constant string.",
366505
value,
367506
);
368507
}
369508
}
370509
if key == "matcher" {
371-
let mut matchers = vec![];
372-
match value {
373-
JsValue::Constant(matcher) => {
374-
if let Some(matcher) = matcher.as_str() {
375-
matchers.push(matcher.to_string());
376-
} else {
377-
invalid_config(
378-
"The matcher property must be a string or array of \
379-
strings",
380-
value,
381-
);
382-
}
383-
}
384-
JsValue::Array { items, .. } => {
385-
for item in items {
386-
if let Some(matcher) = item.as_str() {
387-
matchers.push(matcher.to_string());
388-
} else {
389-
invalid_config(
390-
"The matcher property must be a string or array \
391-
of strings",
392-
value,
393-
);
394-
}
395-
}
396-
}
397-
_ => invalid_config(
398-
"The matcher property must be a string or array of strings",
399-
value,
400-
),
401-
}
402-
config.matcher = Some(matchers);
510+
config.matcher =
511+
parse_route_matcher_from_js_value(module.ident(), value);
403512
}
404513
} else {
405-
invalid_config(
514+
emit_invalid_config_warning(
515+
module.ident(),
406516
"The exported config object must not contain non-constant strings.",
407517
key,
408518
);
@@ -411,7 +521,8 @@ fn parse_config_from_js_value(module: Vc<Box<dyn Module>>, value: &JsValue) -> N
411521
}
412522
}
413523
} else {
414-
invalid_config(
524+
emit_invalid_config_warning(
525+
module.ident(),
415526
"The exported config object must be a valid object literal.",
416527
value,
417528
);

test/e2e/middleware-custom-matchers/app/middleware.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,13 @@ export const config = {
7777
},
7878
],
7979
},
80+
{
81+
source:
82+
'/((?!api|monitoring|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|manifest|icon|source-match|has-match-1|has-match-2|has-match-3|has-match-4|has-match-5|missing-match-1|missing-match-2|routes).*)',
83+
missing: [
84+
{ type: 'header', key: 'next-router-prefetch' },
85+
{ type: 'header', key: 'purpose', value: 'prefetch' },
86+
],
87+
},
8088
],
8189
}

0 commit comments

Comments
 (0)