Fix/numeric path params (#3087)
* adding support for numeric params in getStaticPaths() * chore: adding changeset * ignore undefined params in type validation
This commit is contained in:
parent
fa573f0e97
commit
e0f838ca39
7 changed files with 64 additions and 6 deletions
5
.changeset/few-ways-jump.md
Normal file
5
.changeset/few-ways-jump.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Adds support for numeric route parameters in getStaticPaths()
|
|
@ -824,7 +824,7 @@ export interface Page<T = any> {
|
||||||
|
|
||||||
export type PaginateFunction = (data: [], args?: PaginateOptions) => GetStaticPathsResult;
|
export type PaginateFunction = (data: [], args?: PaginateOptions) => GetStaticPathsResult;
|
||||||
|
|
||||||
export type Params = Record<string, string | undefined>;
|
export type Params = Record<string, string | number | undefined>;
|
||||||
|
|
||||||
export type Props = Record<string, unknown>;
|
export type Props = Record<string, unknown>;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
import { LogOptions, warn, debug } from '../logger/core.js';
|
import { LogOptions, warn, debug } from '../logger/core.js';
|
||||||
|
|
||||||
import { generatePaginateFunction } from './paginate.js';
|
import { generatePaginateFunction } from './paginate.js';
|
||||||
|
import { stringifyParams } from '../routing/params.js';
|
||||||
import {
|
import {
|
||||||
validateGetStaticPathsModule,
|
validateGetStaticPathsModule,
|
||||||
validateGetStaticPathsResult,
|
validateGetStaticPathsResult,
|
||||||
|
@ -17,11 +18,6 @@ import {
|
||||||
|
|
||||||
type RSSFn = (...args: any[]) => any;
|
type RSSFn = (...args: any[]) => any;
|
||||||
|
|
||||||
function stringifyParams(params: Params) {
|
|
||||||
// Always sort keys before stringifying to make sure objects match regardless of parameter ordering
|
|
||||||
return JSON.stringify(params, Object.keys(params).sort());
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CallGetStaticPathsOptions {
|
interface CallGetStaticPathsOptions {
|
||||||
mod: ComponentInstance;
|
mod: ComponentInstance;
|
||||||
route: RouteData;
|
route: RouteData;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { validateGetStaticPathsParameter } from './validation.js';
|
||||||
import type { Params } from '../../@types/astro';
|
import type { Params } from '../../@types/astro';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,3 +21,25 @@ export function getParams(array: string[]) {
|
||||||
|
|
||||||
return fn;
|
return fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* given a route's Params object, validate parameter
|
||||||
|
* values and create a stringified key for the route
|
||||||
|
* that can be used to match request routes
|
||||||
|
*/
|
||||||
|
export function stringifyParams(params: Params) {
|
||||||
|
// validate parameter values then stringify each value
|
||||||
|
const validatedParams = Object.entries(params)
|
||||||
|
.reduce((acc, next) => {
|
||||||
|
validateGetStaticPathsParameter(next);
|
||||||
|
const [key, value] = next;
|
||||||
|
acc[key] = `${value}`;
|
||||||
|
return acc;
|
||||||
|
}, {} as Params);
|
||||||
|
|
||||||
|
// Always sort keys before stringifying to make sure objects match regardless of parameter ordering
|
||||||
|
return JSON.stringify(
|
||||||
|
validatedParams,
|
||||||
|
Object.keys(params).sort()
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,10 +2,21 @@ import type { ComponentInstance, GetStaticPathsResult } from '../../@types/astro
|
||||||
import type { LogOptions } from '../logger/core';
|
import type { LogOptions } from '../logger/core';
|
||||||
import { warn } from '../logger/core.js';
|
import { warn } from '../logger/core.js';
|
||||||
|
|
||||||
|
const VALID_PARAM_TYPES = ['string', 'number', 'undefined'];
|
||||||
|
|
||||||
interface ValidationOptions {
|
interface ValidationOptions {
|
||||||
ssr: boolean;
|
ssr: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Throws error for invalid parameter in getStaticPaths() response */
|
||||||
|
export function validateGetStaticPathsParameter([key, value]: [string, any]) {
|
||||||
|
if (!VALID_PARAM_TYPES.includes(typeof value)) {
|
||||||
|
throw new Error(
|
||||||
|
`[getStaticPaths] invalid route parameter for "${key}". Expected a string or number, received \`${value}\` ("${typeof value}")`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Throw error for deprecated/malformed APIs */
|
/** Throw error for deprecated/malformed APIs */
|
||||||
export function validateGetStaticPathsModule(mod: ComponentInstance, { ssr }: ValidationOptions) {
|
export function validateGetStaticPathsModule(mod: ComponentInstance, { ssr }: ValidationOptions) {
|
||||||
if ((mod as any).createCollection) {
|
if ((mod as any).createCollection) {
|
||||||
|
|
|
@ -49,3 +49,25 @@ describe('getStaticPaths - 404 behavior', () => {
|
||||||
expect(res.status).to.equal(404);
|
expect(res.status).to.equal(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getStaticPaths - route params type validation', () => {
|
||||||
|
let fixture;
|
||||||
|
let devServer;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({ root: './fixtures/astro-get-static-paths/' });
|
||||||
|
devServer = await fixture.startDevServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves 200 on mathcing static path - string params', async () => {
|
||||||
|
// route provided with { params: { year: "2022", slug: "post-2" }}
|
||||||
|
const res = await fixture.fetch('/blog/2022/post-1');
|
||||||
|
expect(res.status).to.equal(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves 200 on matching static path - numeric params', async () => {
|
||||||
|
// route provided with { params: { year: 2022, slug: "post-2" }}
|
||||||
|
const res = await fixture.fetch('/blog/2022/post-2');
|
||||||
|
expect(res.status).to.equal(200);
|
||||||
|
})
|
||||||
|
})
|
|
@ -2,6 +2,7 @@
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
return [
|
return [
|
||||||
{ params: { year: '2022', slug: 'post-1' } },
|
{ params: { year: '2022', slug: 'post-1' } },
|
||||||
|
{ params: { year: 2022, slug: 'post-2' } },
|
||||||
{ params: { slug: 'post-2', year: '2022' } },
|
{ params: { slug: 'post-2', year: '2022' } },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue