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']) {
|
function getPattern(segments: RoutePart[][], addTrailingSlash: AstroConfig['trailingSlash']) {
|
||||||
const pathname = segments
|
const pathname = segments
|
||||||
.map((segment) => {
|
.map((segment) => {
|
||||||
return segment[0].spread
|
if (segment.length === 1 && segment[0].spread) {
|
||||||
? '(?:\\/(.*?))?'
|
return '(?:\\/(.*?))?';
|
||||||
: '\\/' +
|
} else {
|
||||||
segment
|
return (
|
||||||
.map((part) => {
|
'\\/' +
|
||||||
if (part)
|
segment
|
||||||
return part.dynamic
|
.map((part) => {
|
||||||
? '([^/]+?)'
|
if (part.spread) {
|
||||||
: part.content
|
return '(.*?)';
|
||||||
.normalize()
|
} else if (part.dynamic) {
|
||||||
.replace(/\?/g, '%3F')
|
return '([^/]+?)';
|
||||||
.replace(/#/g, '%23')
|
} else {
|
||||||
.replace(/%5B/g, '[')
|
return part.content
|
||||||
.replace(/%5D/g, ']')
|
.normalize()
|
||||||
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
.replace(/\?/g, '%3F')
|
||||||
})
|
.replace(/#/g, '%23')
|
||||||
.join('');
|
.replace(/%5B/g, '[')
|
||||||
|
.replace(/%5D/g, ']')
|
||||||
|
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.join('');
|
.join('');
|
||||||
|
|
||||||
|
@ -117,7 +124,10 @@ function validateSegment(segment: string, file = '') {
|
||||||
if (countOccurrences('[', segment) !== countOccurrences(']', segment)) {
|
if (countOccurrences('[', segment) !== countOccurrences(']', segment)) {
|
||||||
throw new Error(`Invalid route ${file} \u2014 brackets are unbalanced`);
|
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`);
|
throw new Error(`Invalid route ${file} \u2014 rest parameter must be a standalone segment`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,23 +8,26 @@ export function getRouteGenerator(
|
||||||
) {
|
) {
|
||||||
const template = segments
|
const template = segments
|
||||||
.map((segment) => {
|
.map((segment) => {
|
||||||
return segment[0].spread
|
return (
|
||||||
? `/:${segment[0].content.slice(3)}(.*)?`
|
'/' +
|
||||||
: '/' +
|
segment
|
||||||
segment
|
.map((part) => {
|
||||||
.map((part) => {
|
if (part.spread) {
|
||||||
if (part)
|
return `:${part.content.slice(3)}(.*)?`;
|
||||||
return part.dynamic
|
} else if (part.dynamic) {
|
||||||
? `:${part.content}`
|
return `:${part.content}`;
|
||||||
: part.content
|
} else {
|
||||||
.normalize()
|
return part.content
|
||||||
.replace(/\?/g, '%3F')
|
.normalize()
|
||||||
.replace(/#/g, '%23')
|
.replace(/\?/g, '%3F')
|
||||||
.replace(/%5B/g, '[')
|
.replace(/#/g, '%23')
|
||||||
.replace(/%5D/g, ']')
|
.replace(/%5B/g, '[')
|
||||||
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
.replace(/%5D/g, ']')
|
||||||
})
|
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
.join('');
|
}
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.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',
|
url: '/empty-slug/undefined',
|
||||||
fourOhFour: true,
|
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) {
|
function appendForwardSlash(path) {
|
||||||
|
@ -123,9 +138,11 @@ describe('Routing priority', () => {
|
||||||
await fixture.build();
|
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 () => {
|
it(description, async () => {
|
||||||
const htmlFile = `${appendForwardSlash(url)}index.html`;
|
const htmlFile = isEndpoint ? url : `${appendForwardSlash(url)}index.html`;
|
||||||
|
|
||||||
if (fourOhFour) {
|
if (fourOhFour) {
|
||||||
expect(fixture.pathExists(htmlFile)).to.be.false;
|
expect(fixture.pathExists(htmlFile)).to.be.false;
|
||||||
|
@ -135,11 +152,17 @@ describe('Routing priority', () => {
|
||||||
const html = await fixture.readFile(htmlFile);
|
const html = await fixture.readFile(htmlFile);
|
||||||
const $ = cheerioLoad(html);
|
const $ = cheerioLoad(html);
|
||||||
|
|
||||||
expect($('h1').text()).to.equal(h1);
|
if (h1) {
|
||||||
|
expect($('h1').text()).to.equal(h1);
|
||||||
|
}
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
expect($('p').text()).to.equal(p);
|
expect($('p').text()).to.equal(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (htmlMatch) {
|
||||||
|
expect(html).to.equal(htmlMatch);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -160,7 +183,9 @@ describe('Routing priority', () => {
|
||||||
await devServer.stop();
|
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
|
// checks URLs as written above
|
||||||
it(description, async () => {
|
it(description, async () => {
|
||||||
const html = await fixture.fetch(url).then((res) => res.text());
|
const html = await fixture.fetch(url).then((res) => res.text());
|
||||||
|
@ -171,13 +196,22 @@ describe('Routing priority', () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect($('h1').text()).to.equal(h1);
|
if (h1) {
|
||||||
|
expect($('h1').text()).to.equal(h1);
|
||||||
|
}
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
expect($('p').text()).to.equal(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'
|
// checks with trailing slashes, ex: '/de/' instead of '/de'
|
||||||
it(`${description} (trailing slash)`, async () => {
|
it(`${description} (trailing slash)`, async () => {
|
||||||
const html = await fixture.fetch(appendForwardSlash(url)).then((res) => res.text());
|
const html = await fixture.fetch(appendForwardSlash(url)).then((res) => res.text());
|
||||||
|
@ -188,11 +222,17 @@ describe('Routing priority', () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect($('h1').text()).to.equal(h1);
|
if (h1) {
|
||||||
|
expect($('h1').text()).to.equal(h1);
|
||||||
|
}
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
expect($('p').text()).to.equal(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'
|
// checks with index.html, ex: '/de/index.html' instead of '/de'
|
||||||
|
@ -207,11 +247,17 @@ describe('Routing priority', () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect($('h1').text()).to.equal(h1);
|
if (h1) {
|
||||||
|
expect($('h1').text()).to.equal(h1);
|
||||||
|
}
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
expect($('p').text()).to.equal(p);
|
expect($('p').text()).to.equal(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (htmlMatch) {
|
||||||
|
expect(html).to.equal(htmlMatch);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue