summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Bjorn Lu <bjornlu.dev@gmail.com> 2022-10-18 13:16:24 +0800
committerGravatar GitHub <noreply@github.com> 2022-10-18 13:16:24 +0800
commitef0c5431631665c9f6648ee5daee65a3ebf8e9ee (patch)
treee2539735a29b9e60f8d70c4ecfb2c5e3172322e6
parent0edfdd325932b0b493b2228e3a121d217c38a727 (diff)
downloadastro-ef0c5431631665c9f6648ee5daee65a3ebf8e9ee.tar.gz
astro-ef0c5431631665c9f6648ee5daee65a3ebf8e9ee.tar.zst
astro-ef0c5431631665c9f6648ee5daee65a3ebf8e9ee.zip
Support spread parameters for server endpoints (#5106)
-rw-r--r--.changeset/gold-roses-argue.md5
-rw-r--r--packages/astro/src/core/routing/manifest/create.ts46
-rw-r--r--packages/astro/src/core/routing/manifest/generator.ts37
-rw-r--r--packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[...slug].json.ts13
-rw-r--r--packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[foo]-[bar].json.ts14
-rw-r--r--packages/astro/test/routing-priority.test.js60
6 files changed, 133 insertions, 42 deletions
diff --git a/.changeset/gold-roses-argue.md b/.changeset/gold-roses-argue.md
new file mode 100644
index 000000000..05c93789f
--- /dev/null
+++ b/.changeset/gold-roses-argue.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Support spread parameters for server endpoints
diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts
index c5b19bf1a..6a28b33d2 100644
--- a/packages/astro/src/core/routing/manifest/create.ts
+++ b/packages/astro/src/core/routing/manifest/create.ts
@@ -63,23 +63,30 @@ function getParts(part: string, file: string) {
function getPattern(segments: RoutePart[][], addTrailingSlash: AstroConfig['trailingSlash']) {
const pathname = segments
.map((segment) => {
- return segment[0].spread
- ? '(?:\\/(.*?))?'
- : '\\/' +
- segment
- .map((part) => {
- if (part)
- return part.dynamic
- ? '([^/]+?)'
- : part.content
- .normalize()
- .replace(/\?/g, '%3F')
- .replace(/#/g, '%23')
- .replace(/%5B/g, '[')
- .replace(/%5D/g, ']')
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- })
- .join('');
+ if (segment.length === 1 && segment[0].spread) {
+ return '(?:\\/(.*?))?';
+ } else {
+ return (
+ '\\/' +
+ segment
+ .map((part) => {
+ if (part.spread) {
+ return '(.*?)';
+ } else if (part.dynamic) {
+ return '([^/]+?)';
+ } else {
+ return part.content
+ .normalize()
+ .replace(/\?/g, '%3F')
+ .replace(/#/g, '%23')
+ .replace(/%5B/g, '[')
+ .replace(/%5D/g, ']')
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ }
+ })
+ .join('')
+ );
+ }
})
.join('');
@@ -117,7 +124,10 @@ function validateSegment(segment: string, file = '') {
if (countOccurrences('[', segment) !== countOccurrences(']', segment)) {
throw new Error(`Invalid route ${file} \u2014 brackets are unbalanced`);
}
- if (/.+\[\.\.\.[^\]]+\]/.test(segment) || /\[\.\.\.[^\]]+\].+/.test(segment)) {
+ if (
+ (/.+\[\.\.\.[^\]]+\]/.test(segment) || /\[\.\.\.[^\]]+\].+/.test(segment)) &&
+ file.endsWith('.astro')
+ ) {
throw new Error(`Invalid route ${file} \u2014 rest parameter must be a standalone segment`);
}
}
diff --git a/packages/astro/src/core/routing/manifest/generator.ts b/packages/astro/src/core/routing/manifest/generator.ts
index 6df4806fd..4945ea9f1 100644
--- a/packages/astro/src/core/routing/manifest/generator.ts
+++ b/packages/astro/src/core/routing/manifest/generator.ts
@@ -8,23 +8,26 @@ export function getRouteGenerator(
) {
const template = segments
.map((segment) => {
- return segment[0].spread
- ? `/:${segment[0].content.slice(3)}(.*)?`
- : '/' +
- segment
- .map((part) => {
- if (part)
- return part.dynamic
- ? `:${part.content}`
- : part.content
- .normalize()
- .replace(/\?/g, '%3F')
- .replace(/#/g, '%23')
- .replace(/%5B/g, '[')
- .replace(/%5D/g, ']')
- .replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
- })
- .join('');
+ return (
+ '/' +
+ segment
+ .map((part) => {
+ if (part.spread) {
+ return `:${part.content.slice(3)}(.*)?`;
+ } else if (part.dynamic) {
+ return `:${part.content}`;
+ } else {
+ return part.content
+ .normalize()
+ .replace(/\?/g, '%3F')
+ .replace(/#/g, '%23')
+ .replace(/%5B/g, '[')
+ .replace(/%5D/g, ']')
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ }
+ })
+ .join('')
+ );
})
.join('');
diff --git a/packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[...slug].json.ts b/packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[...slug].json.ts
new file mode 100644
index 000000000..142b11711
--- /dev/null
+++ b/packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[...slug].json.ts
@@ -0,0 +1,13 @@
+import type { APIRoute } from 'astro';
+
+export const get: APIRoute = async ({ params }) => {
+ return {
+ body: JSON.stringify({
+ path: params.slug,
+ }),
+ };
+};
+
+export function getStaticPaths() {
+ return [{ params: { slug: 'a' } }, { params: { slug: 'b/c' } }];
+}
diff --git a/packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[foo]-[bar].json.ts b/packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[foo]-[bar].json.ts
new file mode 100644
index 000000000..2e66a22ae
--- /dev/null
+++ b/packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[foo]-[bar].json.ts
@@ -0,0 +1,14 @@
+import type { APIRoute } from 'astro';
+
+export const get: APIRoute = async ({ params }) => {
+ return {
+ body: JSON.stringify({
+ foo: params.foo,
+ bar: params.bar,
+ }),
+ };
+};
+
+export function getStaticPaths() {
+ return [{ params: { foo: 'a', bar: 'b' } }];
+}
diff --git a/packages/astro/test/routing-priority.test.js b/packages/astro/test/routing-priority.test.js
index dba4094a2..586978d4f 100644
--- a/packages/astro/test/routing-priority.test.js
+++ b/packages/astro/test/routing-priority.test.js
@@ -106,6 +106,21 @@ const routes = [
url: '/empty-slug/undefined',
fourOhFour: true,
},
+ {
+ description: 'matches /api/catch/a.json to api/catch/[...slug].json.ts',
+ url: '/api/catch/a.json',
+ htmlMatch: JSON.stringify({ path: 'a' }),
+ },
+ {
+ description: 'matches /api/catch/b/c.json to api/catch/[...slug].json.ts',
+ url: '/api/catch/b/c.json',
+ htmlMatch: JSON.stringify({ path: 'b/c' }),
+ },
+ {
+ description: 'matches /api/catch/a-b.json to api/catch/[foo]-[bar].json.ts',
+ url: '/api/catch/a-b.json',
+ htmlMatch: JSON.stringify({ foo: 'a', bar: 'b' }),
+ },
];
function appendForwardSlash(path) {
@@ -123,9 +138,11 @@ describe('Routing priority', () => {
await fixture.build();
});
- routes.forEach(({ description, url, fourOhFour, h1, p }) => {
+ routes.forEach(({ description, url, fourOhFour, h1, p, htmlMatch }) => {
+ const isEndpoint = htmlMatch && !h1 && !p;
+
it(description, async () => {
- const htmlFile = `${appendForwardSlash(url)}index.html`;
+ const htmlFile = isEndpoint ? url : `${appendForwardSlash(url)}index.html`;
if (fourOhFour) {
expect(fixture.pathExists(htmlFile)).to.be.false;
@@ -135,11 +152,17 @@ describe('Routing priority', () => {
const html = await fixture.readFile(htmlFile);
const $ = cheerioLoad(html);
- expect($('h1').text()).to.equal(h1);
+ if (h1) {
+ expect($('h1').text()).to.equal(h1);
+ }
if (p) {
expect($('p').text()).to.equal(p);
}
+
+ if (htmlMatch) {
+ expect(html).to.equal(htmlMatch);
+ }
});
});
});
@@ -160,7 +183,9 @@ describe('Routing priority', () => {
await devServer.stop();
});
- routes.forEach(({ description, url, fourOhFour, h1, p }) => {
+ routes.forEach(({ description, url, fourOhFour, h1, p, htmlMatch }) => {
+ const isEndpoint = htmlMatch && !h1 && !p;
+
// checks URLs as written above
it(description, async () => {
const html = await fixture.fetch(url).then((res) => res.text());
@@ -171,13 +196,22 @@ describe('Routing priority', () => {
return;
}
- expect($('h1').text()).to.equal(h1);
+ if (h1) {
+ expect($('h1').text()).to.equal(h1);
+ }
if (p) {
expect($('p').text()).to.equal(p);
}
+
+ if (htmlMatch) {
+ expect(html).to.equal(htmlMatch);
+ }
});
+ // skip for endpoint page test
+ if (isEndpoint) return;
+
// checks with trailing slashes, ex: '/de/' instead of '/de'
it(`${description} (trailing slash)`, async () => {
const html = await fixture.fetch(appendForwardSlash(url)).then((res) => res.text());
@@ -188,11 +222,17 @@ describe('Routing priority', () => {
return;
}
- expect($('h1').text()).to.equal(h1);
+ if (h1) {
+ expect($('h1').text()).to.equal(h1);
+ }
if (p) {
expect($('p').text()).to.equal(p);
}
+
+ if (htmlMatch) {
+ expect(html).to.equal(htmlMatch);
+ }
});
// checks with index.html, ex: '/de/index.html' instead of '/de'
@@ -207,11 +247,17 @@ describe('Routing priority', () => {
return;
}
- expect($('h1').text()).to.equal(h1);
+ if (h1) {
+ expect($('h1').text()).to.equal(h1);
+ }
if (p) {
expect($('p').text()).to.equal(p);
}
+
+ if (htmlMatch) {
+ expect(html).to.equal(htmlMatch);
+ }
});
});
});