Better dynamic route regex error (#7339)
* feat: add helpful invalid route seg error * chore: add errors-data entry * refactor: add hint for non-spread errors * chore: add `undefined` to common cases * nit: if the param contains slashes * chore: changeset * chore: it's trivial my dear watson
This commit is contained in:
parent
52f0480d14
commit
e3271f8c16
3 changed files with 61 additions and 2 deletions
5
.changeset/kind-cycles-smile.md
Normal file
5
.changeset/kind-cycles-smile.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add readable error message for invalid dynamic routes.
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
ComponentInstance,
|
ComponentInstance,
|
||||||
EndpointHandler,
|
EndpointHandler,
|
||||||
EndpointOutput,
|
EndpointOutput,
|
||||||
|
GetStaticPathsItem,
|
||||||
ImageTransform,
|
ImageTransform,
|
||||||
MiddlewareResponseHandler,
|
MiddlewareResponseHandler,
|
||||||
RouteData,
|
RouteData,
|
||||||
|
@ -36,7 +37,7 @@ import { runHookBuildGenerated } from '../../integrations/index.js';
|
||||||
import { isServerLikeOutput } from '../../prerender/utils.js';
|
import { isServerLikeOutput } from '../../prerender/utils.js';
|
||||||
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
|
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
|
||||||
import { callEndpoint, createAPIContext, throwIfRedirectNotAllowed } from '../endpoint/index.js';
|
import { callEndpoint, createAPIContext, throwIfRedirectNotAllowed } from '../endpoint/index.js';
|
||||||
import { AstroError } from '../errors/index.js';
|
import { AstroError, AstroErrorData } from '../errors/index.js';
|
||||||
import { debug, info } from '../logger/core.js';
|
import { debug, info } from '../logger/core.js';
|
||||||
import { callMiddleware } from '../middleware/callMiddleware.js';
|
import { callMiddleware } from '../middleware/callMiddleware.js';
|
||||||
import {
|
import {
|
||||||
|
@ -306,7 +307,16 @@ async function getPathsForRoute(
|
||||||
opts.routeCache.set(route, result);
|
opts.routeCache.set(route, result);
|
||||||
|
|
||||||
paths = result.staticPaths
|
paths = result.staticPaths
|
||||||
.map((staticPath) => staticPath.params && route.generate(staticPath.params))
|
.map((staticPath) => {
|
||||||
|
try {
|
||||||
|
return route.generate(staticPath.params);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError) {
|
||||||
|
throw getInvalidRouteSegmentError(e, route, staticPath);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
})
|
||||||
.filter((staticPath) => {
|
.filter((staticPath) => {
|
||||||
// The path hasn't been built yet, include it
|
// The path hasn't been built yet, include it
|
||||||
if (!builtPaths.has(removeTrailingForwardSlash(staticPath))) {
|
if (!builtPaths.has(removeTrailingForwardSlash(staticPath))) {
|
||||||
|
@ -331,6 +341,37 @@ async function getPathsForRoute(
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getInvalidRouteSegmentError(
|
||||||
|
e: TypeError,
|
||||||
|
route: RouteData,
|
||||||
|
staticPath: GetStaticPathsItem
|
||||||
|
): AstroError {
|
||||||
|
const invalidParam = e.message.match(/^Expected "([^"]+)"/)?.[1];
|
||||||
|
const received = invalidParam ? staticPath.params[invalidParam] : undefined;
|
||||||
|
let hint =
|
||||||
|
'Learn about dynamic routes at https://docs.astro.build/en/core-concepts/routing/#dynamic-routes';
|
||||||
|
if (invalidParam && typeof received === 'string') {
|
||||||
|
const matchingSegment = route.segments.find(
|
||||||
|
(segment) => segment[0]?.content === invalidParam
|
||||||
|
)?.[0];
|
||||||
|
const mightBeMissingSpread = matchingSegment?.dynamic && !matchingSegment?.spread;
|
||||||
|
if (mightBeMissingSpread) {
|
||||||
|
hint = `If the param contains slashes, try using a rest parameter: **[...${invalidParam}]**. Learn more at https://docs.astro.build/en/core-concepts/routing/#dynamic-routes`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new AstroError({
|
||||||
|
...AstroErrorData.InvalidDynamicRoute,
|
||||||
|
message: invalidParam
|
||||||
|
? AstroErrorData.InvalidDynamicRoute.message(
|
||||||
|
route.route,
|
||||||
|
JSON.stringify(invalidParam),
|
||||||
|
JSON.stringify(received)
|
||||||
|
)
|
||||||
|
: `Generated path for ${route.route} is invalid.`,
|
||||||
|
hint,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
interface GeneratePathOptions {
|
interface GeneratePathOptions {
|
||||||
pageData: PageBuildData;
|
pageData: PageBuildData;
|
||||||
internals: BuildInternals;
|
internals: BuildInternals;
|
||||||
|
|
|
@ -760,6 +760,19 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
|
||||||
title: 'A redirect must be given a location with the `Location` header.',
|
title: 'A redirect must be given a location with the `Location` header.',
|
||||||
code: 3037,
|
code: 3037,
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* @docs
|
||||||
|
* @see
|
||||||
|
* - [Dynamic routes](https://docs.astro.build/en/core-concepts/routing/#dynamic-routes)
|
||||||
|
* @description
|
||||||
|
* A dynamic route param is invalid. This is often caused by an `undefined` parameter or a missing [rest parameter](https://docs.astro.build/en/core-concepts/routing/#rest-parameters).
|
||||||
|
*/
|
||||||
|
InvalidDynamicRoute: {
|
||||||
|
title: 'Invalid dynamic route.',
|
||||||
|
code: 3038,
|
||||||
|
message: (route: string, invalidParam: string, received: string) =>
|
||||||
|
`The ${invalidParam} param for route ${route} is invalid. Received **${received}**.`,
|
||||||
|
},
|
||||||
// No headings here, that way Vite errors are merged with Astro ones in the docs, which makes more sense to users.
|
// No headings here, that way Vite errors are merged with Astro ones in the docs, which makes more sense to users.
|
||||||
// Vite Errors - 4xxx
|
// Vite Errors - 4xxx
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue