From ab5b216ed58836a19df62b7a66c3bf8fd09af104 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 8 Nov 2022 15:26:35 -0800 Subject: [PATCH 1/2] Add missing matcher support --- packages/next/src/utils.ts | 5 +++++ packages/routing-utils/src/superstatic.ts | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/next/src/utils.ts b/packages/next/src/utils.ts index af60f7dfb77..d8f338c35b7 100644 --- a/packages/next/src/utils.ts +++ b/packages/next/src/utils.ts @@ -2278,6 +2278,7 @@ interface EdgeFunctionInfoV2 extends BaseEdgeFunctionInfo { interface EdgeFunctionMatcher { regexp: string; has?: HasField; + missing?: HasField; } export async function getMiddlewareBundle({ @@ -2479,6 +2480,7 @@ export async function getMiddlewareBundle({ key: 'x-prerender-revalidate', value: prerenderBypassToken, }, + ...(matcher.missing || []), ], }; @@ -2609,6 +2611,9 @@ function getRouteMatchers( if (matcher.has) { m.has = normalizeHas(matcher.has); } + if (matcher.missing) { + m.missing = normalizeHas(matcher.missing); + } return m; }); } diff --git a/packages/routing-utils/src/superstatic.ts b/packages/routing-utils/src/superstatic.ts index cbb2c77acaa..ff548189922 100644 --- a/packages/routing-utils/src/superstatic.ts +++ b/packages/routing-utils/src/superstatic.ts @@ -70,6 +70,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)}`); @@ -97,6 +100,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)}`); @@ -140,6 +146,9 @@ export function convertHeaders(headers: Header[]): Route[] { if (h.has) { route.has = h.has; } + if (h.missing) { + route.missing = h.missing; + } return route; }); } From 405645c15feee2cf24f4c86db560853c0ce13b49 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 8 Nov 2022 15:42:54 -0800 Subject: [PATCH 2/2] Add tests --- packages/routing-utils/src/superstatic.ts | 22 ++- .../routing-utils/test/superstatic.spec.ts | 148 ++++++++++++++++++ 2 files changed, 166 insertions(+), 4 deletions(-) diff --git a/packages/routing-utils/src/superstatic.ts b/packages/routing-utils/src/superstatic.ts index ff548189922..2f54a0dc651 100644 --- a/packages/routing-utils/src/superstatic.ts +++ b/packages/routing-utils/src/superstatic.ts @@ -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; @@ -87,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, @@ -115,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 } = {}; @@ -202,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(); - - 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(); + for (const hasItem of has || []) { if (!hasItem.value && 'key' in hasItem) { hasSegments.add(hasItem.key); } diff --git a/packages/routing-utils/test/superstatic.spec.ts b/packages/routing-utils/test/superstatic.spec.ts index fd16f7ff1b4..578af4a61b3 100644 --- a/packages/routing-utils/test/superstatic.spec.ts +++ b/packages/routing-utils/test/superstatic.spec.ts @@ -224,6 +224,17 @@ test('convertRedirects', () => { ], permanent: false, }, + { + source: '/hello/:first', + destination: '/another', + missing: [ + { + type: 'host', + value: '(?.*)\\.(?.*)', + }, + ], + permanent: false, + }, { source: '/hello/:first', destination: @@ -384,6 +395,19 @@ test('convertRedirects', () => { src: '^\\/hello(?:\\/([^\\/]+?))$', status: 307, }, + { + missing: [ + { + type: 'host', + value: '(?.*)\\.(?.*)', + }, + ], + headers: { + Location: '/another', + }, + src: '^\\/hello(?:\\/([^\\/]+?))$', + status: 307, + }, { has: [ { @@ -453,6 +477,7 @@ test('convertRedirects', () => { ['/hello/world', '/hello/again'], ['/hello/world'], ['/hello/world'], + ['/hello/world'], ]; const mustNotMatch = [ @@ -474,6 +499,7 @@ test('convertRedirects', () => { ['/feature/first', '/feature'], ['/hello', '/hello/another/one'], ['/hellooo'], + ['/hellooo'], ['/helloooo'], ]; @@ -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: '(?.*)\\.(?.*)', + }, + { + type: 'header', + key: 'host', + value: '(?.*)\\.(?.*)', + }, + { + type: 'query', + key: 'username', + }, + { + type: 'header', + key: 'x-pathname', + value: '(?.*)', + }, + { + type: 'header', + key: 'X-Pathname', + value: '(?hello|world)', + }, + ], + }, { source: '/array-query-string/:id/:name', destination: 'https://example.com/?tag=1&tag=2', @@ -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: '(?.*)\\.(?.*)', + }, + { + key: 'host', + type: 'header', + value: '(?.*)\\.(?.*)', + }, + { + type: 'query', + key: 'username', + }, + { + type: 'header', + key: 'x-pathname', + value: '(?.*)', + }, + { + type: 'header', + key: 'x-pathname', + value: '(?hello|world)', + }, + ], + src: '^\\/hello(?:\\/([^\\/]+?))$', + }, { src: '^\\/array-query-string(?:\\/([^\\/]+?))(?:\\/([^\\/]+?))$', dest: 'https://example.com/?tag=1&tag=2&id=$1&name=$2', @@ -776,6 +887,7 @@ test('convertRewrites', () => { ['/hello/world', '/hello/again'], ['/hello/world'], ['/hello/world'], + ['/hello/world'], ['/array-query-string/10/email'], ['/en/hello'], ]; @@ -802,6 +914,7 @@ test('convertRewrites', () => { ['/hello', '/hello/another/one'], ['/hllooo'], ['/hllooo'], + ['/hllooo'], ['/array-query-string/10'], ['/en/hello/world', '/en/hello/'], ]; @@ -919,6 +1032,25 @@ test('convertHeaders', () => { }, ], }, + { + source: '/hello/:first', + missing: [ + { + type: 'host', + value: '(?.*)\\.(?.*)', + }, + ], + headers: [ + { + key: 'x-a', + value: 'a', + }, + { + key: 'x-b', + value: 'b', + }, + ], + }, { source: '/hello/:first', has: [ @@ -1057,6 +1189,20 @@ test('convertHeaders', () => { }, src: '^\\/hello(?:\\/([^\\/]+?))$', }, + { + continue: true, + missing: [ + { + type: 'host', + value: '(?.*)\\.(?.*)', + }, + ], + headers: { + 'x-a': 'a', + 'x-b': 'b', + }, + src: '^\\/hello(?:\\/([^\\/]+?))$', + }, { continue: true, has: [ @@ -1133,6 +1279,7 @@ test('convertHeaders', () => { ['/like/params/first', '/like/params/second'], ['/hello/world'], ['/hello/world'], + ['/hello/world'], ['/hello'], ]; @@ -1143,6 +1290,7 @@ test('convertHeaders', () => { ['/non-match', '/like/params', '/like/params/'], ['/hellooo'], ['/hellooo'], + ['/hellooo'], [], ];