Skip to content

Commit 71ed93a

Browse files
authored
Param validators (#4334)
* remove fallthrough * changeset * remove fallthrough documentation * tweak docs * simplify * simplify * simplify a tiny bit * add failing test of param validators * client-side route parsing * tidy up * add validators to manifest data * client-side validation * simplify * server-side param validation * lint * oops * clarify * docs * minor fixes * fixes * ease debugging * vanquish SPA reloading bug * simplify * lint * windows fix * changeset * throw error if validator module is missing a validate export * update configuration.md * Update documentation/docs/01-routing.md * tighten up validator naming requirements * disallow $ in both param names and types * changeset * point fallthrough users at validation docs * add some JSDoc commentsd
1 parent 1f0d822 commit 71ed93a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+499
-187
lines changed

.changeset/poor-horses-return.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
Add param validators

.changeset/tender-plants-smell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
[breaking] disallow \$ character in dynamic parameters

documentation/docs/01-routing.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,27 @@ A route can have multiple dynamic parameters, for example `src/routes/[category]
318318

319319
> `src/routes/a/[...rest]/z.svelte` will match `/a/z` as well as `/a/b/z` and `/a/b/c/z` and so on. Make sure you check that the value of the rest parameter is valid.
320320
321+
#### Validation
322+
323+
A route like `src/routes/archive/[page]` would match `/archive/3`, but it would also match `/archive/potato`. We don't want that. You can ensure that route parameters are well-formed by adding a _validator_ — which takes the parameter string (`"3"` or `"potato"`) and returns `true` if it is valid — to your [`params`](/docs/configuration#files) directory...
324+
325+
```js
326+
/// file: src/params/integer.js
327+
/** @type {import('@sveltejs/kit').ParamValidator} */
328+
export function validate(param) {
329+
return /^\d+$/.test(param);
330+
}
331+
```
332+
333+
...and augmenting your routes:
334+
335+
```diff
336+
-src/routes/archive/[page]
337+
+src/routes/archive/[page=integer]
338+
```
339+
340+
If the pathname doesn't validate, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404.
341+
321342
#### Sorting
322343

323344
It's possible for multiple routes to match a given path. For example each of these routes would match `/foo-abc`:
@@ -333,6 +354,7 @@ SvelteKit needs to know which route is being requested. To do so, it sorts them
333354

334355
- More specific routes are higher priority
335356
- Standalone endpoints have higher priority than pages with the same specificity
357+
- Parameters with [validators](#validation) (`[name=type]`) are higher priority than those without (`[name]`)
336358
- Rest parameters have lowest priority
337359
- Ties are resolved alphabetically
338360

documentation/docs/13-configuration.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const config = {
3434
assets: 'static',
3535
hooks: 'src/hooks',
3636
lib: 'src/lib',
37+
params: 'src/params',
3738
routes: 'src/routes',
3839
serviceWorker: 'src/service-worker',
3940
template: 'src/app.html'
@@ -148,6 +149,7 @@ An object containing zero or more of the following `string` values:
148149
- `assets` — a place to put static files that should have stable URLs and undergo no processing, such as `favicon.ico` or `manifest.json`
149150
- `hooks` — the location of your hooks module (see [Hooks](/docs/hooks))
150151
- `lib` — your app's internal library, accessible throughout the codebase as `$lib`
152+
- `params` — a directory containing [parameter validators](/docs/routing#advanced-routing-validation)
151153
- `routes` — the files that define the structure of your app (see [Routing](/docs/routing))
152154
- `serviceWorker` — the location of your service worker's entry point (see [Service workers](/docs/service-workers))
153155
- `template` — the location of the template for HTML responses

packages/adapter-static/test/apps/prerendered/svelte.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ const config = {
77

88
prerender: {
99
default: true
10+
},
11+
12+
vite: {
13+
build: {
14+
minify: false
15+
}
1016
}
1117
}
1218
};

packages/adapter-static/test/apps/spa/svelte.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ const config = {
55
kit: {
66
adapter: adapter({
77
fallback: '200.html'
8-
})
8+
}),
9+
10+
vite: {
11+
build: {
12+
minify: false
13+
}
14+
}
915
}
1016
};
1117

packages/kit/rollup.config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export default [
3131
format: 'esm',
3232
chunkFileNames: 'chunks/[name].js'
3333
},
34-
external: ['svelte', 'svelte/store', '__GENERATED__/root.svelte', '__GENERATED__/manifest.js'],
34+
external: [
35+
'svelte',
36+
'svelte/store',
37+
'__GENERATED__/root.svelte',
38+
'__GENERATED__/client-manifest.js'
39+
],
3540
plugins: [
3641
resolve({
3742
extensions: ['.mjs', '.js', '.ts']

packages/kit/src/core/build/build_server.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export async function build_server(
152152
}
153153
});
154154

155-
// ...and every component used by pages
155+
// ...and every component used by pages...
156156
manifest_data.components.forEach((file) => {
157157
const resolved = path.resolve(cwd, file);
158158
const relative = path.relative(config.kit.files.routes, resolved);
@@ -163,6 +163,12 @@ export async function build_server(
163163
input[name] = resolved;
164164
});
165165

166+
// ...and every validator
167+
Object.entries(manifest_data.validators).forEach(([key, file]) => {
168+
const name = posixify(path.join('entries/validators', key));
169+
input[name] = path.resolve(cwd, file);
170+
});
171+
166172
/** @type {(file: string) => string} */
167173
const app_relative = (file) => {
168174
const relative_file = path.relative(build_dir, path.resolve(cwd, file));

packages/kit/src/core/config/index.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,10 @@ export async function load_config({ cwd = process.cwd() } = {}) {
4242

4343
validated.kit.outDir = path.resolve(cwd, validated.kit.outDir);
4444

45-
validated.kit.files.assets = path.resolve(cwd, validated.kit.files.assets);
46-
validated.kit.files.hooks = path.resolve(cwd, validated.kit.files.hooks);
47-
validated.kit.files.lib = path.resolve(cwd, validated.kit.files.lib);
48-
validated.kit.files.routes = path.resolve(cwd, validated.kit.files.routes);
49-
validated.kit.files.serviceWorker = path.resolve(cwd, validated.kit.files.serviceWorker);
50-
validated.kit.files.template = path.resolve(cwd, validated.kit.files.template);
45+
for (const key in validated.kit.files) {
46+
// @ts-expect-error this is typescript at its stupidest
47+
validated.kit.files[key] = path.resolve(cwd, validated.kit.files[key]);
48+
}
5149

5250
return validated;
5351
}

packages/kit/src/core/config/index.spec.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const get_defaults = (prefix = '') => ({
5959
assets: join(prefix, 'static'),
6060
hooks: join(prefix, 'src/hooks'),
6161
lib: join(prefix, 'src/lib'),
62+
params: join(prefix, 'src/params'),
6263
routes: join(prefix, 'src/routes'),
6364
serviceWorker: join(prefix, 'src/service-worker'),
6465
template: join(prefix, 'src/app.html')

0 commit comments

Comments
 (0)