Skip to content

Commit b5bffd8

Browse files
committed
validate will now validate the whole form when it's called with no arguments.
1 parent e5c8507 commit b5bffd8

File tree

8 files changed

+240
-170
lines changed

8 files changed

+240
-170
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- For type safety, you cannot send `null` or `undefined` to `superForm` anymore. Use `superValidate`, or pass a complete data object to `superForm`. Default values can be added with the `defaultValues` function.
1313
- The `valid` option is removed from `message`, any status >= 400 will return a fail.
1414

15+
### Added
16+
17+
- `validate` will now validate the whole form when it's called with no arguments.
18+
1519
## [1.0.0-rc.2]
1620

1721
### Changed

src/lib/client/clientValidation.ts

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import {
2+
type SuperValidated,
3+
type ValidationErrors,
4+
type Validator,
5+
type Validators,
6+
type FieldPath,
7+
type ZodValidation,
8+
type FormPathLeaves,
9+
SuperFormError
10+
} from '../index.js';
11+
import type { z, AnyZodObject } from 'zod';
12+
import { traversePath, traversePathsAsync } from '../traversal.js';
13+
import type { FormOptions } from './index.js';
14+
import { errorShape, mapErrors } from '../errors.js';
15+
import type { ValidateOptions } from './validateField.js';
16+
import type { FormPathType } from '$lib/stringPath.js';
17+
18+
export function validateForm<T extends AnyZodObject>(): Promise<
19+
SuperValidated<ZodValidation<T>>
20+
>;
21+
22+
export function validateForm<T extends AnyZodObject>(
23+
path: FormPathLeaves<z.infer<T>>,
24+
opts?: ValidateOptions<
25+
FormPathType<z.infer<T>, FormPathLeaves<z.infer<T>>>
26+
>
27+
): Promise<string[] | undefined>;
28+
29+
export function validateForm<T extends AnyZodObject>(
30+
path?: FormPathLeaves<z.infer<T>>,
31+
opts?: ValidateOptions<
32+
FormPathType<z.infer<T>, FormPathLeaves<z.infer<T>>>
33+
>
34+
) {
35+
// See the validate function inside superForm for implementation.
36+
throw new SuperFormError(
37+
'validateForm can only be used as superForm.validate.'
38+
);
39+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
40+
return { path, opts } as any;
41+
}
42+
43+
export async function clientValidation<T extends AnyZodObject, M = unknown>(
44+
options: FormOptions<T, M>,
45+
checkData: z.infer<T>,
46+
formId: string | undefined,
47+
constraints: SuperValidated<ZodValidation<T>>['constraints'],
48+
posted: boolean
49+
) {
50+
return _clientValidation(
51+
options.validators,
52+
checkData,
53+
formId,
54+
constraints,
55+
posted
56+
);
57+
}
58+
59+
async function _clientValidation<T extends AnyZodObject, M = unknown>(
60+
validators: FormOptions<T, M>['validators'],
61+
checkData: z.infer<T>,
62+
formId: string | undefined,
63+
constraints: SuperValidated<ZodValidation<T>>['constraints'],
64+
posted: boolean
65+
): Promise<SuperValidated<ZodValidation<T>>> {
66+
if (validators) {
67+
let valid: boolean;
68+
let clientErrors: ValidationErrors<T> = {};
69+
70+
if ('safeParseAsync' in validators) {
71+
// Zod validator
72+
const validator = validators as AnyZodObject;
73+
const result = await validator.safeParseAsync(checkData);
74+
75+
valid = result.success;
76+
77+
if (!result.success) {
78+
clientErrors = mapErrors<T>(
79+
result.error.format(),
80+
errorShape(validator)
81+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
82+
) as any;
83+
}
84+
} else {
85+
// SuperForms validator
86+
87+
valid = true;
88+
89+
const validator = validators as Validators<T>;
90+
const newErrors: {
91+
path: string[];
92+
errors: string[] | undefined;
93+
}[] = [];
94+
95+
await traversePathsAsync(checkData, async ({ value, path }) => {
96+
// Filter out array indices, the validator structure doesn't contain these.
97+
const validationPath = path.filter((p) => isNaN(parseInt(p)));
98+
const maybeValidator = traversePath(
99+
validator,
100+
validationPath as FieldPath<typeof validator>
101+
);
102+
103+
if (typeof maybeValidator?.value === 'function') {
104+
const check = maybeValidator.value as Validator<unknown>;
105+
106+
if (Array.isArray(value)) {
107+
for (const key in value) {
108+
const errors = await check(value[key]);
109+
if (errors) {
110+
valid = false;
111+
newErrors.push({
112+
path: path.concat([key]),
113+
errors:
114+
typeof errors === 'string'
115+
? [errors]
116+
: errors ?? undefined
117+
});
118+
}
119+
}
120+
} else {
121+
const errors = await check(value);
122+
if (errors) {
123+
valid = false;
124+
newErrors.push({
125+
path,
126+
errors:
127+
typeof errors === 'string' ? [errors] : errors ?? undefined
128+
});
129+
}
130+
}
131+
}
132+
});
133+
134+
for (const { path, errors } of newErrors) {
135+
const errorPath = traversePath(
136+
clientErrors,
137+
path as FieldPath<typeof clientErrors>,
138+
({ parent, key, value }) => {
139+
if (value === undefined) parent[key] = {};
140+
return parent[key];
141+
}
142+
);
143+
144+
if (errorPath) {
145+
const { parent, key } = errorPath;
146+
parent[key] = errors;
147+
}
148+
}
149+
}
150+
151+
if (!valid) {
152+
return {
153+
valid: false,
154+
posted,
155+
errors: clientErrors,
156+
data: checkData,
157+
constraints,
158+
message: undefined,
159+
id: formId
160+
};
161+
}
162+
}
163+
164+
return {
165+
valid: true,
166+
posted,
167+
errors: {},
168+
data: checkData,
169+
constraints,
170+
message: undefined,
171+
id: formId
172+
};
173+
}

src/lib/client/formEnhance.ts

Lines changed: 3 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,14 @@ import { browser } from '$app/environment';
99
import {
1010
SuperFormError,
1111
type TaintedFields,
12-
type SuperValidated,
13-
type ValidationErrors,
14-
type Validator,
15-
type Validators,
16-
type FieldPath,
17-
type ZodValidation
12+
type SuperValidated
1813
} from '../index.js';
1914
import type { z, AnyZodObject } from 'zod';
2015
import { stringify } from 'devalue';
21-
import { traversePath, traversePathsAsync } from '../traversal.js';
2216
import type { Entity } from '../schemaEntity.js';
2317
import { validateField } from './validateField.js';
2418
import type { FormOptions, SuperForm } from './index.js';
25-
import { errorShape, mapErrors } from '../errors.js';
19+
import { clientValidation } from './clientValidation.js';
2620

2721
enum FetchStatus {
2822
Idle = 0,
@@ -359,7 +353,7 @@ export function formEnhance<T extends AnyZodObject, M>(
359353
(typeof options.SPA === 'boolean'
360354
? undefined
361355
: options.SPA?.failStatus) ?? 400,
362-
data: { form: validation.result }
356+
data: { form: validation }
363357
};
364358

365359
setTimeout(() => validationResponse({ result }), 0);
@@ -564,133 +558,3 @@ export function formEnhance<T extends AnyZodObject, M>(
564558
return validationResponse;
565559
});
566560
}
567-
568-
async function clientValidation<T extends AnyZodObject>(
569-
options: FormOptions<T, never>,
570-
checkData: z.infer<T>,
571-
formId: string | undefined,
572-
constraints: SuperValidated<ZodValidation<T>>['constraints'],
573-
posted: boolean
574-
) {
575-
let validationResult: Awaited<ReturnType<typeof _clientValidation<T>>>;
576-
577-
if (options.validators) {
578-
validationResult = await _clientValidation(
579-
options.validators,
580-
checkData,
581-
formId,
582-
constraints,
583-
posted
584-
);
585-
} else {
586-
validationResult = { valid: true as const };
587-
}
588-
589-
return validationResult;
590-
}
591-
592-
async function _clientValidation<T extends AnyZodObject>(
593-
validators: FormOptions<T, unknown>['validators'],
594-
checkData: z.infer<T>,
595-
formId: string | undefined,
596-
constraints: SuperValidated<ZodValidation<T>>['constraints'],
597-
posted: boolean
598-
) {
599-
if (!validators) return { valid: true as const };
600-
601-
let valid: boolean;
602-
let clientErrors: ValidationErrors<T> = {};
603-
604-
if ('safeParseAsync' in validators) {
605-
// Zod validator
606-
const validator = validators as AnyZodObject;
607-
const result = await validator.safeParseAsync(checkData);
608-
609-
valid = result.success;
610-
611-
if (!result.success) {
612-
clientErrors = mapErrors<T>(
613-
result.error.format(),
614-
errorShape(validator)
615-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
616-
) as any;
617-
}
618-
} else {
619-
// SuperForms validator
620-
621-
valid = true;
622-
623-
const validator = validators as Validators<T>;
624-
const newErrors: {
625-
path: string[];
626-
errors: string[] | undefined;
627-
}[] = [];
628-
629-
await traversePathsAsync(checkData, async ({ value, path }) => {
630-
// Filter out array indices, the validator structure doesn't contain these.
631-
const validationPath = path.filter((p) => isNaN(parseInt(p)));
632-
const maybeValidator = traversePath(
633-
validator,
634-
validationPath as FieldPath<typeof validator>
635-
);
636-
637-
if (typeof maybeValidator?.value === 'function') {
638-
const check = maybeValidator.value as Validator<unknown>;
639-
640-
if (Array.isArray(value)) {
641-
for (const key in value) {
642-
const errors = await check(value[key]);
643-
if (errors) {
644-
valid = false;
645-
newErrors.push({
646-
path: path.concat([key]),
647-
errors:
648-
typeof errors === 'string' ? [errors] : errors ?? undefined
649-
});
650-
}
651-
}
652-
} else {
653-
const errors = await check(value);
654-
if (errors) {
655-
valid = false;
656-
newErrors.push({
657-
path,
658-
errors:
659-
typeof errors === 'string' ? [errors] : errors ?? undefined
660-
});
661-
}
662-
}
663-
}
664-
});
665-
666-
for (const { path, errors } of newErrors) {
667-
const errorPath = traversePath(
668-
clientErrors,
669-
path as FieldPath<typeof clientErrors>,
670-
({ parent, key, value }) => {
671-
if (value === undefined) parent[key] = {};
672-
return parent[key];
673-
}
674-
);
675-
676-
if (errorPath) {
677-
const { parent, key } = errorPath;
678-
parent[key] = errors;
679-
}
680-
}
681-
}
682-
683-
if (valid) return { valid: true as const };
684-
685-
const result: SuperValidated<T> = {
686-
valid,
687-
posted,
688-
errors: clientErrors,
689-
data: checkData,
690-
constraints,
691-
message: undefined,
692-
id: formId
693-
};
694-
695-
return { valid: false as const, result };
696-
}

0 commit comments

Comments
 (0)