1- import type { z , AnyZodObject , ZodTypeAny , ZodArray , ZodError } from 'zod' ;
1+ import type { z , AnyZodObject , ZodTypeAny , ZodError } from 'zod' ;
22import { get } from 'svelte/store' ;
33import type { FormOptions , SuperForm , TaintOption } from './index.js' ;
44import {
55 SuperFormError ,
66 type FieldPath ,
77 type TaintedFields ,
8- type ValidationErrors ,
98 type UnwrapEffects ,
109 type Validators
1110} from '../index.js' ;
@@ -15,8 +14,6 @@ import {
1514 traversePath ,
1615 traversePaths
1716} from '../traversal.js' ;
18- import { hasEffects , type ZodTypeInfo } from '../schemaEntity.js' ;
19- import { unwrapZodType } from '../schemaEntity.js' ;
2017import type { FormPathLeaves } from '../stringPath.js' ;
2118import { clearErrors , clone } from '../utils.js' ;
2219import { errorShape , mapErrors } from '../errors.js' ;
@@ -36,7 +33,63 @@ export type Validate<
3633 opts ?: ValidateOptions < unknown >
3734) => Promise < string [ ] | undefined > ;
3835
39- const effectMapCache = new WeakMap < object , boolean > ( ) ;
36+ export async function validateObjectErrors < T extends AnyZodObject , M > (
37+ formOptions : FormOptions < T , M > ,
38+ data : z . infer < T > ,
39+ Errors : SuperForm < T , M > [ 'errors' ]
40+ ) {
41+ if (
42+ typeof formOptions . validators !== 'object' ||
43+ ! ( 'safeParseAsync' in formOptions . validators )
44+ ) {
45+ return ;
46+ }
47+
48+ const validators = formOptions . validators as AnyZodObject ;
49+ const result = await validators . safeParseAsync ( data ) ;
50+
51+ if ( ! result . success ) {
52+ const newErrors = mapErrors (
53+ result . error . format ( ) ,
54+ errorShape ( validators as AnyZodObject )
55+ ) ;
56+
57+ Errors . update ( ( currentErrors ) => {
58+ // Clear current object-level errors
59+ traversePaths ( currentErrors , ( pathData ) => {
60+ if ( pathData . key == '_errors' ) {
61+ return pathData . set ( undefined ) ;
62+ }
63+ } ) ;
64+
65+ // Add new object-level errors and tainted field errors
66+ traversePaths ( newErrors , ( pathData ) => {
67+ if ( pathData . key == '_errors' ) {
68+ return setPaths ( currentErrors , [ pathData . path ] , pathData . value ) ;
69+ }
70+ /*
71+ if (!Array.isArray(pathData.value)) return;
72+ if (Tainted_isPathTainted(pathData.path, taintedFields)) {
73+ setPaths(currentErrors, [pathData.path], pathData.value);
74+ }
75+ return 'skip';
76+ */
77+ } ) ;
78+
79+ return currentErrors ;
80+ } ) ;
81+ } else {
82+ Errors . update ( ( currentErrors ) => {
83+ // Clear current object-level errors
84+ traversePaths ( currentErrors , ( pathData ) => {
85+ if ( pathData . key == '_errors' ) {
86+ return pathData . set ( undefined ) ;
87+ }
88+ } ) ;
89+ return currentErrors ;
90+ } ) ;
91+ }
92+ }
4093
4194// @DCI -context
4295export async function validateField < T extends AnyZodObject , M > (
@@ -139,30 +192,6 @@ async function _validateField<T extends AnyZodObject, M>(
139192 return { validated : false , errors : undefined } as const ;
140193 }
141194
142- function extractValidator (
143- data : ZodTypeInfo ,
144- key : string
145- ) : ZodTypeAny | undefined {
146- if ( data . effects ) return undefined ;
147-
148- // No effects, check if ZodObject or ZodArray, which are the
149- // "allowed" objects in the path above the leaf.
150- const type = data . zodType ;
151-
152- if ( type . _def . typeName == 'ZodObject' ) {
153- const nextType = ( type as AnyZodObject ) . _def . shape ( ) [ key ] ;
154- const unwrapped = unwrapZodType ( nextType ) ;
155- return unwrapped . effects ? undefined : unwrapped . zodType ;
156- } else if ( type . _def . typeName == 'ZodArray' ) {
157- const array = type as ZodArray < ZodTypeAny > ;
158- const unwrapped = unwrapZodType ( array . element ) ;
159- if ( unwrapped . effects ) return undefined ;
160- return extractValidator ( unwrapped , key ) ;
161- } else {
162- throw new SuperFormError ( 'Invalid validator' ) ;
163- }
164- }
165-
166195 ///// Roles ///////////////////////////////////////////////////////
167196
168197 function Tainted_isPathTainted (
@@ -175,12 +204,8 @@ async function _validateField<T extends AnyZodObject, M>(
175204 return leaf . value === true ;
176205 }
177206
178- function Errors_get ( ) {
179- return get ( Errors ) ;
180- }
181-
182- function Errors_set ( newErrors : ValidationErrors < UnwrapEffects < T > > ) {
183- Errors . set ( newErrors ) ;
207+ function Errors_update ( updater : Parameters < typeof Errors . update > [ 0 ] ) {
208+ Errors . update ( updater ) ;
184209 }
185210
186211 function Errors_clearFormLevelErrors ( ) {
@@ -230,63 +255,6 @@ async function _validateField<T extends AnyZodObject, M>(
230255
231256 if ( 'safeParseAsync' in validators ) {
232257 // Zod validator
233- // Check if any effects exist for the path, then parse the entire schema.
234- if ( ! effectMapCache . has ( validators ) ) {
235- effectMapCache . set ( validators , hasEffects ( validators as ZodTypeAny ) ) ;
236- }
237-
238- const effects = effectMapCache . get ( validators ) ;
239-
240- const perFieldValidator = effects
241- ? undefined
242- : traversePath (
243- validators ,
244- Context . validationPath as FieldPath < typeof validators > ,
245- ( pathData ) => {
246- return extractValidator (
247- unwrapZodType ( pathData . parent ) ,
248- pathData . key
249- ) ;
250- }
251- ) ;
252-
253- if ( perFieldValidator ) {
254- const validator = extractValidator (
255- unwrapZodType ( perFieldValidator . parent ) ,
256- perFieldValidator . key
257- ) ;
258- if ( validator ) {
259- // Check if validator is ZodArray and the path is an array access
260- // in that case validate the whole array.
261- if (
262- Context . currentData &&
263- validator . _def . typeName == 'ZodArray' &&
264- ! isNaN ( parseInt ( path [ path . length - 1 ] ) )
265- ) {
266- const validateArray = traversePath (
267- Context . currentData ,
268- path . slice ( 0 , - 1 ) as FieldPath < typeof Context . currentData >
269- ) ;
270- Context . value = validateArray ?. value ;
271- }
272-
273- //console.log('🚀 ~ file: index.ts:972 ~ no effects:', validator);
274- const result = await validator . safeParseAsync ( Context . value ) ;
275- if ( ! result . success ) {
276- const errors = result . error . format ( ) ;
277- return {
278- validated : true ,
279- errors : errors . _errors
280- } ;
281- } else {
282- return { validated : true , errors : undefined } ;
283- }
284- }
285- }
286-
287- //console.log('🚀 ~ file: index.ts:983 ~ Effects found, validating all');
288-
289- // Effects are found, validate entire data, unfortunately
290258 if ( ! Context . shouldUpdate ) {
291259 // If value shouldn't update, clone and set the new value
292260 Context . currentData = clone ( Context . currentData ?? get ( data ) ) ;
@@ -298,7 +266,6 @@ async function _validateField<T extends AnyZodObject, M>(
298266 ) ;
299267
300268 if ( ! result . success ) {
301- let currentErrors : ValidationErrors < UnwrapEffects < T > > = { } ;
302269 const newErrors = Errors_fromZod (
303270 result . error ,
304271 validators as AnyZodObject
@@ -307,28 +274,33 @@ async function _validateField<T extends AnyZodObject, M>(
307274 if ( options . update === true || options . update == 'errors' ) {
308275 // Set errors for other (tainted) fields, that may have been changed
309276 const taintedFields = get ( Tainted ) ;
310- currentErrors = Errors_get ( ) ;
311-
312- // Special check for form level errors
313- if ( currentErrors . _errors !== newErrors . _errors ) {
314- if (
315- ! currentErrors . _errors ||
316- ! newErrors . _errors ||
317- currentErrors . _errors . join ( '' ) != newErrors . _errors . join ( '' )
318- ) {
319- currentErrors . _errors = newErrors . _errors ;
320- }
321- }
322277
323- traversePaths ( newErrors , ( pathData ) => {
324- if ( ! Array . isArray ( pathData . value ) ) return ;
325- if ( Tainted_isPathTainted ( pathData . path , taintedFields ) ) {
326- setPaths ( currentErrors , [ pathData . path ] , pathData . value ) ;
327- }
328- return 'skip' ;
329- } ) ;
278+ Errors_update ( ( currentErrors ) => {
279+ // Clear current object-level errors
280+ traversePaths ( currentErrors , ( pathData ) => {
281+ if ( pathData . key == '_errors' ) {
282+ return pathData . set ( undefined ) ;
283+ }
284+ } ) ;
285+
286+ // Add new object-level errors and tainted field errors
287+ traversePaths ( newErrors , ( pathData ) => {
288+ if ( pathData . key == '_errors' ) {
289+ return setPaths (
290+ currentErrors ,
291+ [ pathData . path ] ,
292+ pathData . value
293+ ) ;
294+ }
295+ if ( ! Array . isArray ( pathData . value ) ) return ;
296+ if ( Tainted_isPathTainted ( pathData . path , taintedFields ) ) {
297+ setPaths ( currentErrors , [ pathData . path ] , pathData . value ) ;
298+ }
299+ return 'skip' ;
300+ } ) ;
330301
331- Errors_set ( currentErrors ) ;
302+ return currentErrors ;
303+ } ) ;
332304 }
333305
334306 // Finally, set errors for the specific field
0 commit comments