Support spread parameters for server endpoints (#5106)
This commit is contained in:
parent
0edfdd3259
commit
ef0c543163
6 changed files with 133 additions and 42 deletions
5
.changeset/gold-roses-argue.md
Normal file
5
.changeset/gold-roses-argue.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Support spread parameters for server endpoints
|
|
@ -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`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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('');
|
||||
|
||||
|
|
13
packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[...slug].json.ts
vendored
Normal file
13
packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[...slug].json.ts
vendored
Normal file
|
@ -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' } }];
|
||||
}
|
14
packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[foo]-[bar].json.ts
vendored
Normal file
14
packages/astro/test/fixtures/routing-priority/src/pages/api/catch/[foo]-[bar].json.ts
vendored
Normal file
|
@ -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' } }];
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue