Skip to content

Commit

Permalink
Matchers (#4358)
Browse files Browse the repository at this point in the history
* rename validators to matchers

* changeset

* oops

* fix docs

* not sure how that snuck in there
  • Loading branch information
Rich-Harris committed Mar 16, 2022
1 parent 1bde07d commit 2d6f0d2
Show file tree
Hide file tree
Showing 30 changed files with 82 additions and 77 deletions.
5 changes: 5 additions & 0 deletions .changeset/spicy-apples-return.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[breaking] Rename validators to matchers
10 changes: 5 additions & 5 deletions documentation/docs/01-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,14 +318,14 @@ A route can have multiple dynamic parameters, for example `src/routes/[category]

> `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.
#### Validation
#### Matching

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...
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 _matcher_ — which takes the parameter string (`"3"` or `"potato"`) and returns `true` if it is valid — to your [`params`](/docs/configuration#files) directory...

```js
/// file: src/params/integer.js
/** @type {import('@sveltejs/kit').ParamValidator} */
export function validate(param) {
/** @type {import('@sveltejs/kit').ParamMatcher} */
export function match(param) {
return /^\d+$/.test(param);
}
```
Expand All @@ -337,7 +337,7 @@ export function validate(param) {
+src/routes/archive/[page=integer]
```

If the pathname doesn't validate, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404.
If the pathname doesn't match, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404.

#### Sorting

Expand Down
6 changes: 3 additions & 3 deletions packages/kit/src/core/build/build_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@ export async function build_server(
input[name] = resolved;
});

// ...and every validator
Object.entries(manifest_data.validators).forEach(([key, file]) => {
const name = posixify(path.join('entries/validators', key));
// ...and every matcher
Object.entries(manifest_data.matchers).forEach(([key, file]) => {
const name = posixify(path.join('entries/matchers', key));
input[name] = path.resolve(cwd, file);
});

Expand Down
18 changes: 9 additions & 9 deletions packages/kit/src/core/dev/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,23 +137,23 @@ export async function create_plugin(config, cwd) {
}
};
}),
validators: async () => {
/** @type {Record<string, import('types').ParamValidator>} */
const validators = {};
matchers: async () => {
/** @type {Record<string, import('types').ParamMatcher>} */
const matchers = {};

for (const key in manifest_data.validators) {
const file = manifest_data.validators[key];
for (const key in manifest_data.matchers) {
const file = manifest_data.matchers[key];
const url = path.resolve(cwd, file);
const module = await vite.ssrLoadModule(url);

if (module.validate) {
validators[key] = module.validate;
if (module.match) {
matchers[key] = module.match;
} else {
throw new Error(`${file} does not export a \`validate\` function`);
throw new Error(`${file} does not export a \`match\` function`);
}
}

return validators;
return matchers;
}
}
};
Expand Down
10 changes: 5 additions & 5 deletions packages/kit/src/core/generate_manifest/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function generate_manifest({ build_data, relative_path, routes, format =
/** @param {string} id */
const get_index = (id) => id && /** @type {LookupEntry} */ (bundled_nodes.get(id)).index;

const validators = new Set();
const matchers = new Set();

// prettier-ignore
return `{
Expand All @@ -75,7 +75,7 @@ export function generate_manifest({ build_data, relative_path, routes, format =
const { pattern, names, types } = parse_route_id(route.id);
types.forEach(type => {
if (type) validators.add(type);
if (type) matchers.add(type);
});
if (route.type === 'page') {
Expand Down Expand Up @@ -108,9 +108,9 @@ export function generate_manifest({ build_data, relative_path, routes, format =
}
}).filter(Boolean).join(',\n\t\t\t\t')}
],
validators: async () => {
${Array.from(validators).map(type => `const { validate: ${type} } = await ${load(`${relative_path}/entries/validators/${type}.js`)}`).join('\n\t\t\t\t')}
return { ${Array.from(validators).join(', ')} };
matchers: async () => {
${Array.from(matchers).map(type => `const { match: ${type} } = await ${load(`${relative_path}/entries/matchers/${type}.js`)}`).join('\n\t\t\t\t')}
return { ${Array.from(matchers).join(', ')} };
}
}
}`.replace(/^\t/gm, '');
Expand Down
6 changes: 3 additions & 3 deletions packages/kit/src/core/sync/create_manifest_data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,14 @@ export default function create_manifest_data({
const params_base = path.relative(cwd, config.kit.files.params);

/** @type {Record<string, string>} */
const validators = {};
const matchers = {};
if (fs.existsSync(config.kit.files.params)) {
for (const file of fs.readdirSync(config.kit.files.params)) {
const ext = path.extname(file);
const type = file.slice(0, -ext.length);

if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(type)) {
validators[type] = path.join(params_base, file);
matchers[type] = path.join(params_base, file);
} else {
throw new Error(
`Validator names must match /^[a-zA-Z_][a-zA-Z0-9_]*$/ — "${file}" is invalid`
Expand All @@ -314,7 +314,7 @@ export default function create_manifest_data({
error,
components,
routes,
validators
matchers
};
}

Expand Down
6 changes: 3 additions & 3 deletions packages/kit/src/core/sync/create_manifest_data/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,10 +532,10 @@ test('errors on encountering an illegal __file', () => {
);
});

test('creates param validators', () => {
const { validators } = create('samples/basic'); // directory doesn't matter for the test
test('creates param matchers', () => {
const { matchers } = create('samples/basic'); // directory doesn't matter for the test

assert.equal(validators, {
assert.equal(matchers, {
foo: path.join('params', 'foo.js'),
bar: path.join('params', 'bar.js')
});
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/core/sync/write_manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function write_manifest(manifest_data, base, output) {
write_if_changed(
`${output}/client-manifest.js`,
trim(`
export { validators } from './client-validators.js';
export { matchers } from './client-matchers.js';
export const components = ${components};
Expand Down
16 changes: 8 additions & 8 deletions packages/kit/src/core/sync/write_validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ import { write_if_changed } from './utils.js';
*/
export function write_validators(manifest_data, output) {
const imports = [];
const validators = [];
const matchers = [];

for (const key in manifest_data.validators) {
const src = manifest_data.validators[key];
for (const key in manifest_data.matchers) {
const src = manifest_data.matchers[key];

imports.push(`import { validate as ${key} } from ${s(path.relative(output, src))};`);
validators.push(key);
imports.push(`import { match as ${key} } from ${s(path.relative(output, src))};`);
matchers.push(key);
}

const module = imports.length
? `${imports.join('\n')}\n\nexport const validators = { ${validators.join(', ')} };`
: 'export const validators = {};';
? `${imports.join('\n')}\n\nexport const matchers = { ${matchers.join(', ')} };`
: 'export const matchers = {};';

write_if_changed(`${output}/client-validators.js`, module);
write_if_changed(`${output}/client-matchers.js`, module);
}
4 changes: 2 additions & 2 deletions packages/kit/src/runtime/client/ambient.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
declare module '__GENERATED__/client-manifest.js' {
import { CSRComponentLoader, ParamValidator } from 'types';
import { CSRComponentLoader, ParamMatcher } from 'types';

/**
* A list of all the layout/pages components used in the app
Expand All @@ -12,5 +12,5 @@ declare module '__GENERATED__/client-manifest.js' {
*/
export const dictionary: Record<string, [number[], number[], 1]>;

export const validators: Record<string, ParamValidator>;
export const matchers: Record<string, ParamMatcher>;
}
6 changes: 3 additions & 3 deletions packages/kit/src/runtime/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import {
import { parse } from './parse.js';

import Root from '__GENERATED__/root.svelte';
import { components, dictionary, validators } from '__GENERATED__/client-manifest.js';
import { components, dictionary, matchers } from '__GENERATED__/client-manifest.js';

const SCROLL_KEY = 'sveltekit:scroll';
const INDEX_KEY = 'sveltekit:index';

const routes = parse(components, dictionary, validators);
const routes = parse(components, dictionary, matchers);

// we import the root layout/error components eagerly, so that
// connectivity errors after initialisation don't nuke the app
Expand Down Expand Up @@ -660,7 +660,7 @@ export function create_client({ target, session, base, trailing_slash }) {
// @ts-expect-error
if (node.loaded.fallthrough) {
throw new Error(
'fallthrough is no longer supported. Use validators instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
);
}

Expand Down
6 changes: 3 additions & 3 deletions packages/kit/src/runtime/client/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { exec, parse_route_id } from '../../utils/routing.js';
/**
* @param {import('types').CSRComponentLoader[]} components
* @param {Record<string, [number[], number[], 1?]>} dictionary
* @param {Record<string, (param: string) => boolean>} validators
* @param {Record<string, (param: string) => boolean>} matchers
* @returns {import('types').CSRRoute[]}
*/
export function parse(components, dictionary, validators) {
export function parse(components, dictionary, matchers) {
const routes = Object.entries(dictionary).map(([id, [a, b, has_shadow]]) => {
const { pattern, names, types } = parse_route_id(id);

Expand All @@ -15,7 +15,7 @@ export function parse(components, dictionary, validators) {
/** @param {string} path */
exec: (path) => {
const match = pattern.exec(path);
if (match) return exec(match, names, types, validators);
if (match) return exec(match, names, types, matchers);
},
a: a.map((n) => components[n]),
b: b.map((n) => components[n]),
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export async function render_endpoint(event, mod) {
// @ts-expect-error
if (response.fallthrough) {
throw new Error(
'fallthrough is no longer supported. Use validators instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-matching'
);
}

Expand Down
6 changes: 3 additions & 3 deletions packages/kit/src/runtime/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export async function respond(request, options, state) {
/** @type {Record<string, string>} */
let params = {};

if (options.paths.base) {
if (options.paths.base && !state.prerender?.fallback) {
if (!decoded.startsWith(options.paths.base)) {
return new Response(undefined, { status: 404 });
}
Expand All @@ -81,13 +81,13 @@ export async function respond(request, options, state) {
}

if (!state.prerender || !state.prerender.fallback) {
const validators = await options.manifest._.validators();
const matchers = await options.manifest._.matchers();

for (const candidate of options.manifest._.routes) {
const match = candidate.pattern.exec(decoded);
if (!match) continue;

const matched = exec(match, candidate.names, candidate.types, validators);
const matched = exec(match, candidate.names, candidate.types, matchers);
if (matched) {
route = candidate;
params = decode_params(matched);
Expand Down
6 changes: 3 additions & 3 deletions packages/kit/src/runtime/server/page/load_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ export async function load_node({
// @ts-expect-error
if (loaded.fallthrough) {
throw new Error(
'fallthrough is no longer supported. Use validators instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-matching'
);
}
} else if (shadow.body) {
Expand Down Expand Up @@ -407,7 +407,7 @@ async function load_shadow_data(route, event, options, prerender) {
// @ts-expect-error
if (result.fallthrough) {
throw new Error(
'fallthrough is no longer supported. Use validators instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-matching'
);
}

Expand Down Expand Up @@ -438,7 +438,7 @@ async function load_shadow_data(route, event, options, prerender) {
// @ts-expect-error
if (result.fallthrough) {
throw new Error(
'fallthrough is no longer supported. Use validators instead: https://kit.svelte.dev/docs/routing#advanced-routing-validation'
'fallthrough is no longer supported. Use matchers instead: https://kit.svelte.dev/docs/routing#advanced-routing-matching'
);
}

Expand Down
10 changes: 5 additions & 5 deletions packages/kit/src/utils/routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ export function parse_route_id(key) {
* @param {RegExpMatchArray} match
* @param {string[]} names
* @param {string[]} types
* @param {Record<string, import('types').ParamValidator>} validators
* @param {Record<string, import('types').ParamMatcher>} matchers
*/
export function exec(match, names, types, validators) {
export function exec(match, names, types, matchers) {
/** @type {Record<string, string>} */
const params = {};

Expand All @@ -52,10 +52,10 @@ export function exec(match, names, types, validators) {
const value = match[i + 1] || '';

if (type) {
const validator = validators[type];
if (!validator) throw new Error(`Missing "${type}" param validator`); // TODO do this ahead of time?
const matcher = matchers[type];
if (!matcher) throw new Error(`Missing "${type}" param matcher`); // TODO do this ahead of time?

if (!validator(value)) return;
if (!matcher(value)) return;
}

params[name] = value;
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/apps/basics/src/params/lowercase.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function validate(param) {
export function match(param) {
return /^[a-z]+$/.test(param);
}
2 changes: 1 addition & 1 deletion packages/kit/test/apps/basics/src/params/numeric.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function validate(param) {
export function match(param) {
return !isNaN(param);
}
2 changes: 1 addition & 1 deletion packages/kit/test/apps/basics/src/params/uppercase.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function validate(param) {
export function match(param) {
return /^[A-Z]+$/.test(param);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<a href="/routing/matched/a">/routing/matched/a</a>
<a href="/routing/matched/B">/routing/matched/B</a>
<a href="/routing/matched/1">/routing/matched/1</a>
<a href="/routing/matched/everything-else">/routing/matched/everything-else</a>

<slot />
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Matchers</h1>

This file was deleted.

This file was deleted.

14 changes: 7 additions & 7 deletions packages/kit/test/apps/basics/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2229,20 +2229,20 @@ test.describe.parallel('Static files', () => {
});
});

test.describe.parallel('Validators', () => {
test('Validates parameters', async ({ page, clicknav }) => {
await page.goto('/routing/validated');
test.describe.parallel('Matchers', () => {
test('Matches parameters', async ({ page, clicknav }) => {
await page.goto('/routing/matched');

await clicknav('[href="/routing/validated/a"]');
await clicknav('[href="/routing/matched/a"]');
expect(await page.textContent('h1')).toBe('lowercase: a');

await clicknav('[href="/routing/validated/B"]');
await clicknav('[href="/routing/matched/B"]');
expect(await page.textContent('h1')).toBe('uppercase: B');

await clicknav('[href="/routing/validated/1"]');
await clicknav('[href="/routing/matched/1"]');
expect(await page.textContent('h1')).toBe('number: 1');

await clicknav('[href="/routing/validated/everything-else"]');
await clicknav('[href="/routing/matched/everything-else"]');
expect(await page.textContent('h1')).toBe('fallback: everything-else');
});
});
Expand Down
Loading

0 comments on commit 2d6f0d2

Please sign in to comment.