Skip to content

Commit fc7a8f5

Browse files
committed
Tests passing.
1 parent 225b414 commit fc7a8f5

File tree

3 files changed

+78
-65
lines changed

3 files changed

+78
-65
lines changed

src/lib/client/validateField.ts

Lines changed: 75 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type Validate<
3939

4040
const effectMapCache = new WeakMap<object, boolean>();
4141

42+
// @DCI-context
4243
export async function validateField<T extends AnyZodObject, M>(
4344
path: string[],
4445
formOptions: FormOptions<T, M>,
@@ -47,10 +48,48 @@ export async function validateField<T extends AnyZodObject, M>(
4748
Tainted: SuperForm<T, M>['tainted'],
4849
options: ValidateOptions<unknown> = {}
4950
): Promise<string[] | undefined> {
51+
function Errors_clear() {
52+
clearErrors(Errors, { undefinePath: path, clearFormLevelErrors: true });
53+
}
54+
55+
function Errors_update(errorMsgs: null | undefined | string | string[]) {
56+
if (typeof errorMsgs === 'string') errorMsgs = [errorMsgs];
57+
58+
if (options.update === true || options.update == 'errors') {
59+
Errors.update((errors) => {
60+
const error = traversePath(
61+
errors,
62+
path as FieldPath<typeof errors>,
63+
(node) => {
64+
if (isInvalidPath(path, node)) {
65+
throw new SuperFormError(
66+
'Errors can only be added to form fields, not to arrays or objects in the schema. Path: ' +
67+
node.path.slice(0, -1)
68+
);
69+
} else if (node.value === undefined) {
70+
node.parent[node.key] = {};
71+
return node.parent[node.key];
72+
} else {
73+
return node.value;
74+
}
75+
}
76+
);
77+
78+
if (!error)
79+
throw new SuperFormError(
80+
'Error path could not be created: ' + path
81+
);
82+
83+
error.parent[error.key] = errorMsgs ?? undefined;
84+
return errors;
85+
});
86+
}
87+
return errorMsgs ?? undefined;
88+
}
89+
5090
const errors = await _validateField(
5191
path,
5292
formOptions.validators,
53-
formOptions.defaultValidator,
5493
data,
5594
Errors,
5695
Tainted,
@@ -61,7 +100,6 @@ export async function validateField<T extends AnyZodObject, M>(
61100
const delayedErrors = await _validateField(
62101
path,
63102
formOptions.delayedValidators,
64-
formOptions.defaultValidator,
65103
data,
66104
Errors,
67105
Tainted,
@@ -71,11 +109,20 @@ export async function validateField<T extends AnyZodObject, M>(
71109
return delayedErrors.errors;
72110
}
73111

74-
// We validated the whole data structure, so clear all errors on success after delayed validators.
75-
// but also set the current path to undefined, so it will be used in the tainted+error
76-
// check in oninput.
77-
if (errors.validatedAll && !errors.errors) {
78-
clearErrors(Errors, { undefinePath: path, clearFormLevelErrors: true });
112+
if (errors.validated) {
113+
if (errors.validated === 'all' && !errors.errors) {
114+
// We validated the whole data structure, so clear all errors on success after delayed validators.
115+
// it will also set the current path to undefined, so it can be used in
116+
// the tainted+error check in oninput.
117+
Errors_clear();
118+
} else {
119+
return Errors_update(errors.errors);
120+
}
121+
} else if (
122+
errors.validated === false &&
123+
formOptions.defaultValidator == 'clear'
124+
) {
125+
return Errors_update(undefined);
79126
}
80127

81128
return errors.errors;
@@ -87,14 +134,14 @@ async function _validateField<T extends AnyZodObject, M>(
87134
validators:
88135
| FormOptions<T, M>['validators']
89136
| FormOptions<T, M>['delayedValidators'],
90-
defaultValidator: FormOptions<T, M>['defaultValidator'],
91137
data: SuperForm<T, M>['form'],
92138
Errors: SuperForm<T, M>['errors'],
93139
Tainted: SuperForm<T, M>['tainted'],
94140
options: ValidateOptions<unknown> = {}
95-
): Promise<{ validatedAll: boolean; errors: string[] | undefined }> {
141+
): Promise<{ validated: boolean | 'all'; errors: string[] | undefined }> {
96142
if (options.update === undefined) options.update = true;
97143
if (options.taint === undefined) options.taint = false;
144+
if (typeof options.errors == 'string') options.errors = [options.errors];
98145

99146
const Context = {
100147
value: options.value,
@@ -105,10 +152,7 @@ async function _validateField<T extends AnyZodObject, M>(
105152
};
106153

107154
async function defaultValidate() {
108-
if (defaultValidator == 'clear') {
109-
Errors_update(undefined);
110-
}
111-
return undefined;
155+
return { validated: false, errors: undefined } as const;
112156
}
113157

114158
function extractValidator(
@@ -159,40 +203,7 @@ async function _validateField<T extends AnyZodObject, M>(
159203
return mapErrors(errors.format());
160204
}
161205

162-
function Errors_update(errorMsgs: null | undefined | string | string[]) {
163-
if (typeof errorMsgs === 'string') errorMsgs = [errorMsgs];
164-
165-
if (options.update === true || options.update == 'errors') {
166-
Errors.update((errors) => {
167-
const error = traversePath(
168-
errors,
169-
path as FieldPath<typeof errors>,
170-
(node) => {
171-
if (isInvalidPath(path, node)) {
172-
throw new SuperFormError(
173-
'Errors can only be added to form fields, not to arrays or objects in the schema. Path: ' +
174-
node.path.slice(0, -1)
175-
);
176-
} else if (node.value === undefined) {
177-
node.parent[node.key] = {};
178-
return node.parent[node.key];
179-
} else {
180-
return node.value;
181-
}
182-
}
183-
);
184-
185-
if (!error)
186-
throw new SuperFormError(
187-
'Error path could not be created: ' + path
188-
);
189-
190-
error.parent[error.key] = errorMsgs ?? undefined;
191-
return errors;
192-
});
193-
}
194-
return errorMsgs ?? undefined;
195-
}
206+
///////////////////////////////////////////////////////////////////
196207

197208
if (!('value' in options)) {
198209
// Use value from data
@@ -220,7 +231,7 @@ async function _validateField<T extends AnyZodObject, M>(
220231
//console.log('🚀 ~ file: index.ts:871 ~ validate:', path, value);
221232

222233
if (typeof validators !== 'object') {
223-
return { validatedAll: false, errors: await defaultValidate() };
234+
return defaultValidate();
224235
}
225236

226237
if ('safeParseAsync' in validators) {
@@ -270,11 +281,11 @@ async function _validateField<T extends AnyZodObject, M>(
270281
if (!result.success) {
271282
const errors = result.error.format();
272283
return {
273-
validatedAll: false,
274-
errors: Errors_update(errors._errors)
284+
validated: true,
285+
errors: errors._errors
275286
};
276287
} else {
277-
return { validatedAll: false, errors: Errors_update(undefined) };
288+
return { validated: true, errors: undefined };
278289
}
279290
}
280291
}
@@ -293,13 +304,13 @@ async function _validateField<T extends AnyZodObject, M>(
293304
);
294305

295306
if (!result.success) {
307+
let currentErrors: ValidationErrors<UnwrapEffects<T>> = {};
296308
const newErrors = Errors_fromZod(result.error);
297309

298310
if (options.update === true || options.update == 'errors') {
299311
// Set errors for other (tainted) fields, that may have been changed
300312
const taintedFields = get(Tainted);
301-
const currentErrors = Errors_get();
302-
let updated = false;
313+
currentErrors = Errors_get();
303314

304315
// Special check for form level errors
305316
if (currentErrors._errors !== newErrors._errors) {
@@ -309,20 +320,18 @@ async function _validateField<T extends AnyZodObject, M>(
309320
currentErrors._errors.join('') != newErrors._errors.join('')
310321
) {
311322
currentErrors._errors = newErrors._errors;
312-
updated = true;
313323
}
314324
}
315325

316326
traversePaths(newErrors, (pathData) => {
317327
if (!Array.isArray(pathData.value)) return;
318328
if (Tainted_isPathTainted(pathData.path, taintedFields)) {
319329
setPaths(currentErrors, [pathData.path], pathData.value);
320-
updated = true;
321330
}
322331
return 'skip';
323332
});
324333

325-
if (updated) Errors_set(currentErrors);
334+
Errors_set(currentErrors);
326335
}
327336

328337
// Finally, set errors for the specific field
@@ -334,11 +343,11 @@ async function _validateField<T extends AnyZodObject, M>(
334343
);
335344

336345
return {
337-
validatedAll: true,
338-
errors: Errors_update(options.errors ?? current?.value)
346+
validated: true,
347+
errors: options.errors ?? current?.value
339348
};
340349
} else {
341-
return { validatedAll: true, errors: undefined };
350+
return { validated: true, errors: undefined };
342351
}
343352
} else {
344353
// SuperForms validator
@@ -353,12 +362,15 @@ async function _validateField<T extends AnyZodObject, M>(
353362
throw new SuperFormError('No Superforms validator found: ' + path);
354363
} else if (validator.value === undefined) {
355364
// No validator, use default
356-
return { validatedAll: false, errors: await defaultValidate() };
365+
return defaultValidate();
357366
} else {
358-
const result = await validator.value(Context.value);
367+
const result = (await validator.value(Context.value)) as
368+
| string[]
369+
| undefined;
370+
359371
return {
360-
validatedAll: false,
361-
errors: Errors_update(result ? options.errors ?? result : result)
372+
validated: true,
373+
errors: result ? options.errors ?? result : result
362374
};
363375
}
364376
}

src/routes/nested-validation/+page.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Zod validate:
2525

2626
<pre style="margin-top:3rem;">
2727
Superforms validate:
28-
{#if output}{output.join('\n')}{/if}
28+
{#if output2}{output2.join('\n')}{/if}
2929
</pre>
3030

3131
<style>

src/routes/nested-validation/TagForm.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
return !name.length ? 'Name is too short' : null;
2424
},
2525
tags: {
26-
id: (id) => (id < 3 ? 'Id must be larger than 2' : null),
26+
id: (id) =>
27+
id < 3 ? 'Number must be greater than or equal to 3' : null,
2728
name: (name) => {
2829
return name.length < 2
2930
? 'Tags must be at least two characters'

0 commit comments

Comments
 (0)