Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[breaking] rename invalid(...) and ValidationError #8012

Merged
merged 2 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/loud-phones-chew.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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: 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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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' } });
}
};
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -1074,7 +1074,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 @@ -1129,17 +1129,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