Skip to content

Commit

Permalink
[breaking] rename invalid(...) and ValidationError (#8012)
Browse files Browse the repository at this point in the history
* rename invalid() and ValidationError - closes #7982

* help people migrate
  • Loading branch information
Rich-Harris committed Dec 9, 2022
1 parent ef8915f commit 3b98005
Show file tree
Hide file tree
Showing 15 changed files with 55 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changeset/loud-phones-chew.md
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[breaking] rename invalid() to fail() and ValidationError to ActionFailure
16 changes: 8 additions & 8 deletions documentation/docs/20-core-concepts/30-form-actions.md
Expand Up @@ -140,12 +140,12 @@ export const actions = {

### Validation errors

If the request couldn't be processed because of invalid data, you can return validation errors — along with the previously submitted form values — back to the user so that they can try again. The `invalid` function lets you return an HTTP status code (typically 400 or 422, in the case of validation errors) along with the data. The status code is available through `$page.status` and the data through `form`:
If the request couldn't be processed because of invalid data, you can return validation errors — along with the previously submitted form values — back to the user so that they can try again. The `fail` function lets you return an HTTP status code (typically 400 or 422, in the case of validation errors) along with the data. The status code is available through `$page.status` and the data through `form`:

```diff
// @errors: 2339 2304
/// file: src/routes/login/+page.server.js
+import { invalid } from '@sveltejs/kit';
+import { fail } from '@sveltejs/kit';

/** @type {import('./$types').Actions} */
export const actions = {
Expand All @@ -155,13 +155,13 @@ export const actions = {
const password = data.get('password');

+ if (!email) {
+ return invalid(400, { email, missing: true });
+ return fail(400, { email, missing: true });
+ }

const user = await db.getUser(email);

+ if (!user || user.password !== hash(password)) {
+ return invalid(400, { email, incorrect: true });
+ return fail(400, { email, incorrect: true });
+ }

cookies.set('sessionid', await db.createSession(user));
Expand Down Expand Up @@ -199,7 +199,7 @@ Redirects (and errors) work exactly the same as in [`load`](/docs/load#redirects
```diff
// @errors: 2339 2304
/// file: src/routes/login/+page.server.js
+import { invalid, redirect } from '@sveltejs/kit';
+import { fail, redirect } from '@sveltejs/kit';

/** @type {import('./$types').Actions} */
export const actions = {
Expand All @@ -210,11 +210,11 @@ export const actions = {

const user = await db.getUser(email);
if (!user) {
return invalid(400, { email, missing: true });
return fail(400, { email, missing: true });
}

if (user.password !== hash(password)) {
return invalid(400, { email, incorrect: true });
return fail(400, { email, incorrect: true });
}

cookies.set('sessionid', await db.createSession(user));
Expand Down Expand Up @@ -377,7 +377,7 @@ If you provide your own callbacks, you may need to reproduce part of the default

The behaviour of `applyAction(result)` depends on `result.type`:

- `success`, `invalid` — sets `$page.status` to `result.status` and updates `form` and `$page.form` to `result.data` (regardless of where you are submitting from, in contrast to `update` from `enhance`)
- `success`, `failure` — sets `$page.status` to `result.status` and updates `form` and `$page.form` to `result.data` (regardless of where you are submitting from, in contrast to `update` from `enhance`)
- `redirect` — calls `goto(result.location)`
- `error` — renders the nearest `+error` boundary with `result.error`

Expand Down
@@ -1,4 +1,4 @@
import { invalid } from '@sveltejs/kit';
import { fail } from '@sveltejs/kit';
import { Game } from './game';
import type { PageServerLoad, Actions } from './$types';

Expand Down Expand Up @@ -59,7 +59,7 @@ export const actions = {
const guess = /** @type {string[]} */ data.getAll('guess') /***/ as string[];

if (!game.enter(guess)) {
return invalid(400, { badGuess: true });
return fail(400, { badGuess: true });
}

cookies.set('sverdle', game.toString());
Expand Down
13 changes: 9 additions & 4 deletions packages/kit/src/exports/index.js
@@ -1,4 +1,4 @@
import { HttpError, Redirect, ValidationError } from '../runtime/control.js';
import { HttpError, Redirect, ActionFailure } from '../runtime/control.js';

// For some reason we need to type the params as well here,
// JSdoc doesn't seem to like @type with function overloads
Expand Down Expand Up @@ -46,10 +46,15 @@ export function json(data, init) {
}

/**
* Generates a `ValidationError` object.
* Generates an `ActionFailure` object.
* @param {number} status
* @param {Record<string, any> | undefined} [data]
*/
export function invalid(status, data) {
return new ValidationError(status, data);
export function fail(status, data) {
return new ActionFailure(status, data);
}

// TODO remove for 1.0
export function invalid() {
throw new Error('invalid(...) is now fail(...)');
}
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/app/forms.js
Expand Up @@ -47,7 +47,7 @@ export function enhance(form, submit = () => {}) {
await invalidateAll();
}

// For success/invalid results, only apply action if it belongs to the
// For success/failure results, only apply action if it belongs to the
// current page, otherwise `form` will be updated erroneously
if (
location.origin + location.pathname === action.origin + action.pathname ||
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/control.js
Expand Up @@ -33,7 +33,7 @@ export class Redirect {
/**
* @template {Record<string, unknown> | undefined} [T=undefined]
*/
export class ValidationError {
export class ActionFailure {
/**
* @param {number} status
* @param {T} [data]
Expand Down
22 changes: 11 additions & 11 deletions packages/kit/src/runtime/server/page/actions.js
Expand Up @@ -2,7 +2,7 @@ import * as devalue from 'devalue';
import { error, json } from '../../../exports/index.js';
import { normalize_error } from '../../../utils/error.js';
import { is_form_content_type, negotiate } from '../../../utils/http.js';
import { HttpError, Redirect, ValidationError } from '../../control.js';
import { HttpError, Redirect, ActionFailure } from '../../control.js';
import { handle_error_and_jsonify } from '../utils.js';

/** @param {import('types').RequestEvent} event */
Expand Down Expand Up @@ -50,9 +50,9 @@ export async function handle_action_json_request(event, options, server) {
try {
const data = await call_action(event, actions);

if (data instanceof ValidationError) {
if (data instanceof ActionFailure) {
return action_json({
type: 'invalid',
type: 'failure',
status: data.status,
// @ts-expect-error we assign a string to what is supposed to be an object. That's ok
// because we don't use the object outside, and this way we have better code navigation
Expand Down Expand Up @@ -81,7 +81,7 @@ export async function handle_action_json_request(event, options, server) {
return action_json(
{
type: 'error',
error: await handle_error_and_jsonify(event, options, check_incorrect_invalid_use(error))
error: await handle_error_and_jsonify(event, options, check_incorrect_fail_use(error))
},
{
status: error instanceof HttpError ? error.status : 500
Expand All @@ -93,9 +93,9 @@ export async function handle_action_json_request(event, options, server) {
/**
* @param {HttpError | Error} error
*/
function check_incorrect_invalid_use(error) {
return error instanceof ValidationError
? new Error(`Cannot "throw invalid()". Use "return invalid()"`)
function check_incorrect_fail_use(error) {
return error instanceof ActionFailure
? new Error(`Cannot "throw fail()". Use "return fail()"`)
: error;
}

Expand Down Expand Up @@ -142,8 +142,8 @@ export async function handle_action_request(event, server) {
try {
const data = await call_action(event, actions);

if (data instanceof ValidationError) {
return { type: 'invalid', status: data.status, data: data.data };
if (data instanceof ActionFailure) {
return { type: 'failure', status: data.status, data: data.data };
} else {
return {
type: 'success',
Expand All @@ -164,7 +164,7 @@ export async function handle_action_request(event, server) {

return {
type: 'error',
error: check_incorrect_invalid_use(error)
error: check_incorrect_fail_use(error)
};
}
}
Expand All @@ -183,7 +183,7 @@ function check_named_default_separate(actions) {
/**
* @param {import('types').RequestEvent} event
* @param {NonNullable<import('types').SSRNode['server']['actions']>} actions
* @throws {Redirect | ValidationError | HttpError | Error}
* @throws {Redirect | ActionFailure | HttpError | Error}
*/
export async function call_action(event, actions) {
const url = new URL(event.request.url);
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/page/index.js
Expand Up @@ -68,7 +68,7 @@ export async function render_page(event, route, page, options, state, resolve_op
const error = action_result.error;
status = error instanceof HttpError ? error.status : 500;
}
if (action_result?.type === 'invalid') {
if (action_result?.type === 'failure') {
status = action_result.status;
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/page/render.js
Expand Up @@ -67,7 +67,7 @@ export async function render_response({
let rendered;

const form_value =
action_result?.type === 'success' || action_result?.type === 'invalid'
action_result?.type === 'success' || action_result?.type === 'failure'
? action_result.data ?? null
: null;

Expand Down
@@ -1,4 +1,4 @@
import { invalid } from '@sveltejs/kit';
import { fail } from '@sveltejs/kit';

/**
* @type {import('./$types').Actions}
Expand All @@ -7,7 +7,7 @@ export const actions = {
default: async ({ request }) => {
const fields = await request.formData();
fields.delete('password');
return invalid(400, {
return fail(400, {
values: Object.fromEntries(fields),
errors: {
message: 'invalid credentials'
Expand Down
@@ -1,8 +1,8 @@
import { invalid } from '@sveltejs/kit';
import { fail } from '@sveltejs/kit';

/** @type {import('./$types').Actions} */
export const actions = {
default: async () => {
return invalid(400, { errors: { message: 'an error occurred' } });
return fail(400, { errors: { message: 'an error occurred' } });
}
};
Expand Up @@ -5,7 +5,7 @@
export let form;
let count = 0;
/** @param {'success' | 'invalid'} type */
/** @param {'success' | 'failure'} type */
function update(type) {
applyAction({ type, status: 200, data: { count: count++ } });
}
Expand All @@ -19,7 +19,7 @@

<pre>{JSON.stringify(form)}</pre>
<button class="increment-success" on:click={() => update('success')}>Increment (success)</button>
<button class="increment-invalid" on:click={() => update('invalid')}>Increment (invalid)</button>
<button class="increment-invalid" on:click={() => update('failure')}>Increment (invalid)</button>
<button class="invalidateAll" on:click={invalidateAll}>Invalidate</button>
<button class="redirect" on:click={redirect}>Redirect</button>
<button class="error" on:click={error}>Error</button>
Expand Down
@@ -1,4 +1,4 @@
import { invalid } from '@sveltejs/kit';
import { fail } from '@sveltejs/kit';

export function load() {
return {
Expand All @@ -10,7 +10,7 @@ export function load() {
export const actions = {
default: async ({ request }) => {
const fields = await request.formData();
return invalid(400, {
return fail(400, {
errors: { post_message: `echo: ${fields.get('message')}` }
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/test/types/actions.test.ts
Expand Up @@ -3,7 +3,7 @@ import Kit from '@sveltejs/kit';
// Test: Action types inferred correctly and transformed into a union
type Actions = {
foo: () => Promise<void>;
bar: () => Promise<{ success: boolean } | Kit.ValidationError<{ message: string }>>;
bar: () => Promise<{ success: boolean } | Kit.ActionFailure<{ message: string }>>;
};

let form: Kit.AwaitedActions<Actions> = null as any;
Expand Down
14 changes: 7 additions & 7 deletions packages/kit/types/index.d.ts
Expand Up @@ -61,7 +61,7 @@ type OptionalUnion<
A extends keyof U = U extends U ? keyof U : never
> = U extends unknown ? { [P in Exclude<A, keyof U>]?: never } & U : never;

type UnpackValidationError<T> = T extends ValidationError<infer X>
type UnpackValidationError<T> = T extends ActionFailure<infer X>
? X
: T extends void
? undefined // needs to be undefined, because void will corrupt union type
Expand Down Expand Up @@ -1073,7 +1073,7 @@ export type ActionResult<
Invalid extends Record<string, unknown> | undefined = Record<string, any>
> =
| { type: 'success'; status: number; data?: Success }
| { type: 'invalid'; status: number; data?: Invalid }
| { type: 'failure'; status: number; data?: Invalid }
| { type: 'redirect'; status: number; location: string }
| { type: 'error'; error: any };

Expand Down Expand Up @@ -1128,17 +1128,17 @@ export interface Redirect {
export function json(data: any, init?: ResponseInit): Response;

/**
* Create a `ValidationError` object.
* Create an `ActionFailure` object.
*/
export function invalid<T extends Record<string, unknown> | undefined>(
export function fail<T extends Record<string, unknown> | undefined>(
status: number,
data?: T
): ValidationError<T>;
): ActionFailure<T>;

/**
* The object returned by the [`invalid`](https://kit.svelte.dev/docs/modules#sveltejs-kit-invalid) function
* The object returned by the [`fail`](https://kit.svelte.dev/docs/modules#sveltejs-kit-fail) function
*/
export interface ValidationError<T extends Record<string, unknown> | undefined = undefined>
export interface ActionFailure<T extends Record<string, unknown> | undefined = undefined>
extends UniqueInterface {
status: number;
data: T;
Expand Down

0 comments on commit 3b98005

Please sign in to comment.