Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support spread parameters for server endpoints #5106

Merged
merged 1 commit into from
Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gold-roses-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Support spread parameters for server endpoints
46 changes: 28 additions & 18 deletions packages/astro/src/core/routing/manifest/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('');

Expand Down Expand Up @@ -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`);
}
}
Expand Down
37 changes: 20 additions & 17 deletions packages/astro/src/core/routing/manifest/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('');

Expand Down
Original file line number Diff line number Diff line change
@@ -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' } }];
}
Original file line number Diff line number Diff line change
@@ -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' } }];
}
60 changes: 53 additions & 7 deletions packages/astro/test/routing-priority.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand All @@ -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);
}
});
});
});
Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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'
Expand All @@ -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);
}
});
});
});
Expand Down