Skip to content

Commit 8bff60b

Browse files
committed
Array errors were always added, even if the form wasn't tainted.
1 parent 64eb902 commit 8bff60b

File tree

6 files changed

+194
-5
lines changed

6 files changed

+194
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ Headlines: Added, Changed, Deprecated, Removed, Fixed, Security
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Fixed
11+
12+
- [Array errors](https://superforms.rocks/concepts/error-handling#form-level-and-array-errors) were always added, even if the form wasn't tainted.
13+
814
## [1.7.4] - 2023-09-29
915

1016
### Fixed

src/lib/client/clientValidation.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ async function _clientValidation<T extends AnyZodObject, M = unknown>(
223223
export async function validateObjectErrors<T extends AnyZodObject, M>(
224224
formOptions: FormOptions<T, M>,
225225
data: z.infer<T>,
226-
Errors: SuperForm<T, M>['errors']
226+
Errors: SuperForm<T, M>['errors'],
227+
tainted: TaintedFields<UnwrapEffects<T>> | undefined
227228
) {
228229
if (
229230
typeof formOptions.validators !== 'object' ||
@@ -252,7 +253,17 @@ export async function validateObjectErrors<T extends AnyZodObject, M>(
252253
// Add new object-level errors and tainted field errors
253254
traversePaths(newErrors, (pathData) => {
254255
if (pathData.key == '_errors') {
255-
return setPaths(currentErrors, [pathData.path], pathData.value);
256+
// Check if the parent path (the actual array) is tainted
257+
// Form-level errors are always "tainted"
258+
const taintedPath =
259+
pathData.path.length == 1
260+
? { value: true }
261+
: tainted &&
262+
traversePath(tainted, pathData.path.slice(0, -1) as any);
263+
264+
if (taintedPath && taintedPath.value) {
265+
return setPaths(currentErrors, [pathData.path], pathData.value);
266+
}
256267
}
257268
});
258269

@@ -387,7 +398,7 @@ async function _validateField<T extends ZodValidation<AnyZodObject>, M>(
387398
if (tainted === undefined) return false;
388399
const leaf = traversePath(tainted, path as FieldPath<typeof tainted>);
389400
if (!leaf) return false;
390-
return leaf.value === true;
401+
return leaf.value;
391402
}
392403

393404
function Errors_update(updater: Parameters<typeof Errors.update>[0]) {
@@ -468,14 +479,23 @@ async function _validateField<T extends ZodValidation<AnyZodObject>, M>(
468479

469480
// Add new object-level errors and tainted field errors
470481
traversePaths(newErrors, (pathData) => {
471-
if (pathData.key == '_errors') {
482+
if (
483+
pathData.key == '_errors' &&
484+
(pathData.path.length == 1 ||
485+
Tainted_isPathTainted(
486+
pathData.path.slice(0, -1),
487+
taintedFields
488+
))
489+
) {
472490
return setPaths(
473491
currentErrors,
474492
[pathData.path],
475493
pathData.value
476494
);
477495
}
496+
478497
if (!Array.isArray(pathData.value)) return;
498+
479499
if (Tainted_isPathTainted(pathData.path, taintedFields)) {
480500
setPaths(currentErrors, [pathData.path], pathData.value);
481501
}

src/lib/client/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,12 @@ export function superForm<
725725
updated = updated || (await Tainted__validate(path, taintOptions));
726726
}
727727
if (!updated) {
728-
await validateObjectErrors(options, get(Form), Errors);
728+
await validateObjectErrors(
729+
options,
730+
get(Form),
731+
Errors,
732+
get(Tainted)
733+
);
729734
}
730735
}
731736
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { superValidate, message, setError } from '$lib/server';
2+
import { fail } from '@sveltejs/kit';
3+
import { schema } from './schema';
4+
import type { Actions, PageServerLoad } from './$types';
5+
6+
///// Load function /////
7+
8+
export const load: PageServerLoad = async () => {
9+
const form = await superValidate(schema);
10+
return { form };
11+
};
12+
13+
///// Form actions /////
14+
15+
export const actions: Actions = {
16+
default: async ({ request }) => {
17+
const form = await superValidate(request, schema);
18+
19+
const data = form.data;
20+
21+
if (!data.days?.length) {
22+
setError(form, 'days._errors', 'You have to select at least one day!');
23+
}
24+
25+
console.log('POST', form);
26+
27+
if (!form.valid) return fail(400, { form });
28+
29+
return message(form, 'Form posted successfully!');
30+
}
31+
};
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<script lang="ts">
2+
import { page } from '$app/stores';
3+
import { superForm } from '$lib/client';
4+
import SuperDebug from '$lib/client/SuperDebug.svelte';
5+
import { schema } from './schema';
6+
7+
export let data;
8+
9+
const Days = [
10+
'Saturday',
11+
'Sunday',
12+
'Monday',
13+
'Tuesday',
14+
'Wednesday',
15+
'Thursday',
16+
'Friday'
17+
];
18+
19+
const { form, errors, message, tainted, enhance } = superForm(data.form, {
20+
validators: schema,
21+
taintedMessage: null
22+
});
23+
</script>
24+
25+
<SuperDebug data={{ $form, $tainted }} />
26+
27+
<h3>Superforms testing ground</h3>
28+
29+
{#if $message}
30+
<div
31+
class="status"
32+
class:error={$page.status >= 400}
33+
class:success={$page.status == 200}
34+
>
35+
{$message}
36+
</div>
37+
{/if}
38+
39+
<button on:click={() => ($form.days = [...$form.days, 3])}
40+
>Set Tuesday</button
41+
>
42+
43+
<form method="POST" use:enhance>
44+
<label>
45+
Name<br />
46+
<input
47+
name="name"
48+
aria-invalid={$errors.name ? 'true' : undefined}
49+
bind:value={$form.name}
50+
/>
51+
{#if $errors.name}<span class="invalid">{$errors.name}</span>{/if}
52+
</label>
53+
54+
<div class="checkboxes">
55+
{#each Days as day, i}
56+
<div class="checkbox">
57+
<input
58+
id="wday-{i}"
59+
type="checkbox"
60+
name="days"
61+
value={i}
62+
bind:group={$form.days}
63+
class="w-4 h-4 text-skin-accent bg-skin-fg rounded focus:ring-skin-accent focus:ring-2 cursor-pointer"
64+
/>
65+
<label for="wday-{i}" class="ml-2 text-sm font-medium cursor-pointer"
66+
>{day}</label
67+
>
68+
</div>
69+
{/each}
70+
</div>
71+
<p class="invalid">{$errors.days?._errors || 'No error'}</p>
72+
<button>Submit</button>
73+
</form>
74+
75+
<hr />
76+
<p>
77+
<a target="_blank" href="https://superforms.rocks/api">API Reference</a>
78+
</p>
79+
80+
<style>
81+
.invalid {
82+
color: red;
83+
}
84+
85+
.status {
86+
color: white;
87+
padding: 4px;
88+
padding-left: 8px;
89+
border-radius: 2px;
90+
font-weight: 500;
91+
}
92+
93+
.status.success {
94+
background-color: seagreen;
95+
}
96+
97+
.status.error {
98+
background-color: #ff2a02;
99+
}
100+
101+
input {
102+
background-color: #ddd;
103+
}
104+
105+
a {
106+
text-decoration: underline;
107+
}
108+
109+
hr {
110+
margin-top: 4rem;
111+
}
112+
113+
form {
114+
padding-top: 1rem;
115+
padding-bottom: 1rem;
116+
}
117+
.checkboxes,
118+
.checkbox {
119+
display: flex;
120+
}
121+
</style>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { z } from 'zod';
2+
3+
export const schema = z.object({
4+
name: z.string().min(2),
5+
days: z.number().min(0).max(6).array().nonempty()
6+
});

0 commit comments

Comments
 (0)