Skip to content

Commit

Permalink
[next][routing-utils] Add missing matcher support (#8874)
Browse files Browse the repository at this point in the history
### Related Issues

This adds handling to ensure we pass through the `missing` route field
correctly for custom routes and middleware matchers. Tests are also
added in the complimentary Next.js PR for this, example deployment can
be seen here
https://vtest314-e2e-tests-jj4-vtest314-next-e2e-tests.vercel.app/

x-ref: [slack
thread](https://vercel.slack.com/archives/C03S8ED1DKM/p1667935428788529?thread_ts=1667850697.542269&cid=C03S8ED1DKM)
x-ref: vercel/next.js#42660

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with
tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a
reviewer
- [ ] Issue from task tracker has a link to this PR
  • Loading branch information
ijjk committed Nov 9, 2022
1 parent fbb8bba commit c2d0887
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 4 deletions.
5 changes: 5 additions & 0 deletions packages/next/src/utils.ts
Expand Up @@ -2278,6 +2278,7 @@ interface EdgeFunctionInfoV2 extends BaseEdgeFunctionInfo {
interface EdgeFunctionMatcher {
regexp: string;
has?: HasField;
missing?: HasField;
}

export async function getMiddlewareBundle({
Expand Down Expand Up @@ -2479,6 +2480,7 @@ export async function getMiddlewareBundle({
key: 'x-prerender-revalidate',
value: prerenderBypassToken,
},
...(matcher.missing || []),
],
};

Expand Down Expand Up @@ -2609,6 +2611,9 @@ function getRouteMatchers(
if (matcher.has) {
m.has = normalizeHas(matcher.has);
}
if (matcher.missing) {
m.missing = normalizeHas(matcher.missing);
}
return m;
});
}
Expand Down
31 changes: 27 additions & 4 deletions packages/routing-utils/src/superstatic.ts
Expand Up @@ -51,6 +51,9 @@ export function convertRedirects(
return redirects.map(r => {
const { src, segments } = sourceToRegex(r.source);
const hasSegments = collectHasSegments(r.has);
normalizeHasKeys(r.has);
normalizeHasKeys(r.missing);

try {
const loc = replaceSegments(segments, hasSegments, r.destination, true);
let status: number;
Expand All @@ -70,6 +73,9 @@ export function convertRedirects(
if (r.has) {
route.has = r.has;
}
if (r.missing) {
route.missing = r.missing;
}
return route;
} catch (e) {
throw new Error(`Failed to parse redirect: ${JSON.stringify(r)}`);
Expand All @@ -84,6 +90,9 @@ export function convertRewrites(
return rewrites.map(r => {
const { src, segments } = sourceToRegex(r.source);
const hasSegments = collectHasSegments(r.has);
normalizeHasKeys(r.has);
normalizeHasKeys(r.missing);

try {
const dest = replaceSegments(
segments,
Expand All @@ -97,6 +106,9 @@ export function convertRewrites(
if (r.has) {
route.has = r.has;
}
if (r.missing) {
route.missing = r.missing;
}
return route;
} catch (e) {
throw new Error(`Failed to parse rewrite: ${JSON.stringify(r)}`);
Expand All @@ -109,6 +121,9 @@ export function convertHeaders(headers: Header[]): Route[] {
const obj: { [key: string]: string } = {};
const { src, segments } = sourceToRegex(h.source);
const hasSegments = collectHasSegments(h.has);
normalizeHasKeys(h.has);
normalizeHasKeys(h.missing);

const namedSegments = segments.filter(name => name !== UN_NAMED_SEGMENT);
const indexes: { [k: string]: string } = {};

Expand Down Expand Up @@ -140,6 +155,9 @@ export function convertHeaders(headers: Header[]): Route[] {
if (h.has) {
route.has = h.has;
}
if (h.missing) {
route.missing = h.missing;
}
return route;
});
}
Expand Down Expand Up @@ -193,14 +211,19 @@ export function sourceToRegex(source: string): {

const namedGroupsRegex = /\(\?<([a-zA-Z][a-zA-Z0-9]*)>/g;

export function collectHasSegments(has?: HasField) {
const hasSegments = new Set<string>();

for (const hasItem of has || []) {
const normalizeHasKeys = (hasItems: HasField = []) => {
for (const hasItem of hasItems) {
if ('key' in hasItem && hasItem.type === 'header') {
hasItem.key = hasItem.key.toLowerCase();
}
}
return hasItems;
};

export function collectHasSegments(has?: HasField) {
const hasSegments = new Set<string>();

for (const hasItem of has || []) {
if (!hasItem.value && 'key' in hasItem) {
hasSegments.add(hasItem.key);
}
Expand Down
148 changes: 148 additions & 0 deletions packages/routing-utils/test/superstatic.spec.ts
Expand Up @@ -224,6 +224,17 @@ test('convertRedirects', () => {
],
permanent: false,
},
{
source: '/hello/:first',
destination: '/another',
missing: [
{
type: 'host',
value: '(?<a>.*)\\.(?<b>.*)',
},
],
permanent: false,
},
{
source: '/hello/:first',
destination:
Expand Down Expand Up @@ -384,6 +395,19 @@ test('convertRedirects', () => {
src: '^\\/hello(?:\\/([^\\/]+?))$',
status: 307,
},
{
missing: [
{
type: 'host',
value: '(?<a>.*)\\.(?<b>.*)',
},
],
headers: {
Location: '/another',
},
src: '^\\/hello(?:\\/([^\\/]+?))$',
status: 307,
},
{
has: [
{
Expand Down Expand Up @@ -453,6 +477,7 @@ test('convertRedirects', () => {
['/hello/world', '/hello/again'],
['/hello/world'],
['/hello/world'],
['/hello/world'],
];

const mustNotMatch = [
Expand All @@ -474,6 +499,7 @@ test('convertRedirects', () => {
['/feature/first', '/feature'],
['/hello', '/hello/another/one'],
['/hellooo'],
['/hellooo'],
['/helloooo'],
];

Expand Down Expand Up @@ -586,6 +612,48 @@ test('convertRewrites', () => {
},
],
},
{
source: '/hello/:first',
destination: '/another',
missing: [
{
type: 'header',
key: 'x-rewrite',
},
{
type: 'cookie',
key: 'loggedIn',
value: '1',
},
{
type: 'host',
value: 'vercel.com',
},
{
type: 'host',
value: '(?<a>.*)\\.(?<b>.*)',
},
{
type: 'header',
key: 'host',
value: '(?<c>.*)\\.(?<d>.*)',
},
{
type: 'query',
key: 'username',
},
{
type: 'header',
key: 'x-pathname',
value: '(?<pathname>.*)',
},
{
type: 'header',
key: 'X-Pathname',
value: '(?<another>hello|world)',
},
],
},
{
source: '/array-query-string/:id/:name',
destination: 'https://example.com/?tag=1&tag=2',
Expand Down Expand Up @@ -740,6 +808,49 @@ test('convertRewrites', () => {
],
src: '^\\/hello(?:\\/([^\\/]+?))$',
},
{
check: true,
dest: '/another?first=$1',
missing: [
{
key: 'x-rewrite',
type: 'header',
},
{
key: 'loggedIn',
type: 'cookie',
value: '1',
},
{
type: 'host',
value: 'vercel.com',
},
{
type: 'host',
value: '(?<a>.*)\\.(?<b>.*)',
},
{
key: 'host',
type: 'header',
value: '(?<c>.*)\\.(?<d>.*)',
},
{
type: 'query',
key: 'username',
},
{
type: 'header',
key: 'x-pathname',
value: '(?<pathname>.*)',
},
{
type: 'header',
key: 'x-pathname',
value: '(?<another>hello|world)',
},
],
src: '^\\/hello(?:\\/([^\\/]+?))$',
},
{
src: '^\\/array-query-string(?:\\/([^\\/]+?))(?:\\/([^\\/]+?))$',
dest: 'https://example.com/?tag=1&tag=2&id=$1&name=$2',
Expand Down Expand Up @@ -776,6 +887,7 @@ test('convertRewrites', () => {
['/hello/world', '/hello/again'],
['/hello/world'],
['/hello/world'],
['/hello/world'],
['/array-query-string/10/email'],
['/en/hello'],
];
Expand All @@ -802,6 +914,7 @@ test('convertRewrites', () => {
['/hello', '/hello/another/one'],
['/hllooo'],
['/hllooo'],
['/hllooo'],
['/array-query-string/10'],
['/en/hello/world', '/en/hello/'],
];
Expand Down Expand Up @@ -919,6 +1032,25 @@ test('convertHeaders', () => {
},
],
},
{
source: '/hello/:first',
missing: [
{
type: 'host',
value: '(?<a>.*)\\.(?<b>.*)',
},
],
headers: [
{
key: 'x-a',
value: 'a',
},
{
key: 'x-b',
value: 'b',
},
],
},
{
source: '/hello/:first',
has: [
Expand Down Expand Up @@ -1057,6 +1189,20 @@ test('convertHeaders', () => {
},
src: '^\\/hello(?:\\/([^\\/]+?))$',
},
{
continue: true,
missing: [
{
type: 'host',
value: '(?<a>.*)\\.(?<b>.*)',
},
],
headers: {
'x-a': 'a',
'x-b': 'b',
},
src: '^\\/hello(?:\\/([^\\/]+?))$',
},
{
continue: true,
has: [
Expand Down Expand Up @@ -1133,6 +1279,7 @@ test('convertHeaders', () => {
['/like/params/first', '/like/params/second'],
['/hello/world'],
['/hello/world'],
['/hello/world'],
['/hello'],
];

Expand All @@ -1143,6 +1290,7 @@ test('convertHeaders', () => {
['/non-match', '/like/params', '/like/params/'],
['/hellooo'],
['/hellooo'],
['/hellooo'],
[],
];

Expand Down

0 comments on commit c2d0887

Please sign in to comment.