diff --git a/.changeset/few-ways-jump.md b/.changeset/few-ways-jump.md new file mode 100644 index 000000000..7f046d263 --- /dev/null +++ b/.changeset/few-ways-jump.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Adds support for numeric route parameters in getStaticPaths() diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index bd45f5108..ffb62a15f 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -824,7 +824,7 @@ export interface Page { export type PaginateFunction = (data: [], args?: PaginateOptions) => GetStaticPathsResult; -export type Params = Record; +export type Params = Record; export type Props = Record; diff --git a/packages/astro/src/core/render/route-cache.ts b/packages/astro/src/core/render/route-cache.ts index f9ef8209d..b564a6ca6 100644 --- a/packages/astro/src/core/render/route-cache.ts +++ b/packages/astro/src/core/render/route-cache.ts @@ -10,6 +10,7 @@ import type { import { LogOptions, warn, debug } from '../logger/core.js'; import { generatePaginateFunction } from './paginate.js'; +import { stringifyParams } from '../routing/params.js'; import { validateGetStaticPathsModule, validateGetStaticPathsResult, @@ -17,11 +18,6 @@ import { 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 { mod: ComponentInstance; route: RouteData; diff --git a/packages/astro/src/core/routing/params.ts b/packages/astro/src/core/routing/params.ts index 04b242aa4..98071e069 100644 --- a/packages/astro/src/core/routing/params.ts +++ b/packages/astro/src/core/routing/params.ts @@ -1,3 +1,4 @@ +import { validateGetStaticPathsParameter } from './validation.js'; import type { Params } from '../../@types/astro'; /** @@ -20,3 +21,25 @@ export function getParams(array: string[]) { 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() + ); +} \ No newline at end of file diff --git a/packages/astro/src/core/routing/validation.ts b/packages/astro/src/core/routing/validation.ts index a82ba58a3..d0dafdc15 100644 --- a/packages/astro/src/core/routing/validation.ts +++ b/packages/astro/src/core/routing/validation.ts @@ -2,10 +2,21 @@ import type { ComponentInstance, GetStaticPathsResult } from '../../@types/astro import type { LogOptions } from '../logger/core'; import { warn } from '../logger/core.js'; +const VALID_PARAM_TYPES = ['string', 'number', 'undefined']; + interface ValidationOptions { 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 */ export function validateGetStaticPathsModule(mod: ComponentInstance, { ssr }: ValidationOptions) { if ((mod as any).createCollection) { diff --git a/packages/astro/test/astro-get-static-paths.test.js b/packages/astro/test/astro-get-static-paths.test.js index 3de3b58c9..c2ae40940 100644 --- a/packages/astro/test/astro-get-static-paths.test.js +++ b/packages/astro/test/astro-get-static-paths.test.js @@ -49,3 +49,25 @@ describe('getStaticPaths - 404 behavior', () => { 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); + }) +}) \ No newline at end of file diff --git a/packages/astro/test/fixtures/astro-get-static-paths/src/pages/blog/[year]/[slug].astro b/packages/astro/test/fixtures/astro-get-static-paths/src/pages/blog/[year]/[slug].astro index 12e686366..95902cf07 100644 --- a/packages/astro/test/fixtures/astro-get-static-paths/src/pages/blog/[year]/[slug].astro +++ b/packages/astro/test/fixtures/astro-get-static-paths/src/pages/blog/[year]/[slug].astro @@ -2,6 +2,7 @@ export async function getStaticPaths() { return [ { params: { year: '2022', slug: 'post-1' } }, + { params: { year: 2022, slug: 'post-2' } }, { params: { slug: 'post-2', year: '2022' } }, ] }