11import { debounce , isEqual , get , set , omit , merge } from 'lodash-es'
22import { ValidationCallback , Config , NamedInputEvent , SimpleValidationErrors , ValidationErrors , Validator as TValidator , ValidatorListeners , ValidationConfig } from './types.js'
33import { client , isFile } from './client.js'
4- import { isAxiosError } from 'axios'
4+ import { isAxiosError , isCancel , mergeConfig } from 'axios'
55
66export const createValidator = ( callback : ValidationCallback , initialData : Record < string , unknown > = { } ) : TValidator => {
77 /**
@@ -169,15 +169,33 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
169169 /**
170170 * Create a debounced validation callback.
171171 */
172- const createValidator = ( ) => debounce ( ( ) => {
172+ const createValidator = ( ) => debounce ( ( instanceConfig : Config ) => {
173173 callback ( {
174- get : ( url , data = { } , config = { } ) => client . get ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
175- post : ( url , data = { } , config = { } ) => client . post ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
176- patch : ( url , data = { } , config = { } ) => client . patch ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
177- put : ( url , data = { } , config = { } ) => client . put ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
178- delete : ( url , data = { } , config = { } ) => client . delete ( url , parseData ( data ) , resolveConfig ( config , data ) ) ,
174+ get : ( url , data = { } , globalConfig = { } ) => client . get ( url , parseData ( data ) , resolveConfig ( globalConfig , instanceConfig , data ) ) ,
175+ post : ( url , data = { } , globalConfig = { } ) => client . post ( url , parseData ( data ) , resolveConfig ( globalConfig , instanceConfig , data ) ) ,
176+ patch : ( url , data = { } , globalConfig = { } ) => client . patch ( url , parseData ( data ) , resolveConfig ( globalConfig , instanceConfig , data ) ) ,
177+ put : ( url , data = { } , globalConfig = { } ) => client . put ( url , parseData ( data ) , resolveConfig ( globalConfig , instanceConfig , data ) ) ,
178+ delete : ( url , data = { } , globalConfig = { } ) => client . delete ( url , parseData ( data ) , resolveConfig ( globalConfig , instanceConfig , data ) ) ,
179+ } ) . catch ( ( error ) => {
180+ // Precognition can often cancel in-flight requests. Instead of
181+ // throwing an exception for this expected behaviour, we silently
182+ // discard cancelled request errors to not flood the console with
183+ // expected errors.
184+ if ( isCancel ( error ) ) {
185+ return null
186+ }
187+
188+ // Unlike other status codes, 422 responses are expected and
189+ // regularly occur with Precognition requests. We silently ignore
190+ // these so we do not flood the console with expected errors. If
191+ // needed, they can be intercepted by the `onValidationError`
192+ // config option instead.
193+ if ( isAxiosError ( error ) && error . response ?. status === 422 ) {
194+ return null
195+ }
196+
197+ return Promise . reject ( error )
179198 } )
180- . catch ( error => isAxiosError ( error ) ? null : Promise . reject ( error ) )
181199 } , debounceTimeoutDuration , { leading : true , trailing : true } )
182200
183201 /**
@@ -188,11 +206,24 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
188206 /**
189207 * Resolve the configuration.
190208 */
191- const resolveConfig = ( config : ValidationConfig , data : Record < string , unknown > = { } ) : Config => {
209+ const resolveConfig = (
210+ globalConfig : ValidationConfig ,
211+ instanceConfig : ValidationConfig ,
212+ data : Record < string , unknown > = { }
213+ ) : Config => {
214+ const config : ValidationConfig = {
215+ ...globalConfig ,
216+ ...instanceConfig ,
217+ }
218+
192219 const validate = Array . from ( config . validate ?? touched )
193220
194221 return {
195- ...config ,
222+ ...instanceConfig ,
223+ // Axios has special rules for merging global and local config. We
224+ // use their merge function here to make sure things like headers
225+ // merge in an expected way.
226+ ...mergeConfig ( globalConfig , instanceConfig ) ,
196227 validate,
197228 timeout : config . timeout ?? 5000 ,
198229 onValidationError : ( response , axiosError ) => {
@@ -205,8 +236,12 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
205236 ? config . onValidationError ( response , axiosError )
206237 : Promise . reject ( axiosError )
207238 } ,
208- onSuccess : ( ) => {
239+ onSuccess : ( response ) => {
209240 setValidated ( [ ...validated , ...validate ] ) . forEach ( listener => listener ( ) )
241+
242+ return config . onSuccess
243+ ? config . onSuccess ( response )
244+ : response
210245 } ,
211246 onPrecognitionSuccess : ( response ) => {
212247 [
@@ -219,11 +254,11 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
219254 : response
220255 } ,
221256 onBefore : ( ) => {
222- const beforeValidationResult = ( config . onBeforeValidation ?? ( ( previous , next ) => {
223- return ! isEqual ( previous , next )
224- } ) ) ( { data , touched } , { data : oldData , touched : oldTouched } )
257+ const beforeValidationHandler = config . onBeforeValidation ?? ( ( newRequest , oldRequest ) => {
258+ return newRequest . touched . length > 0 && ! isEqual ( newRequest , oldRequest )
259+ } )
225260
226- if ( beforeValidationResult === false ) {
261+ if ( beforeValidationHandler ( { data , touched } , { data : oldData , touched : oldTouched } ) === false ) {
227262 return false
228263 }
229264
@@ -261,9 +296,9 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
261296 /**
262297 * Validate the given input.
263298 */
264- const validate = ( name ?: string | NamedInputEvent , value ?: unknown ) => {
299+ const validate = ( name ?: string | NamedInputEvent , value ?: unknown , config ?: Config ) : void => {
265300 if ( typeof name === 'undefined' ) {
266- validator ( )
301+ validator ( config ?? { } )
267302
268303 return
269304 }
@@ -280,11 +315,7 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
280315 setTouched ( [ name , ...touched ] ) . forEach ( listener => listener ( ) )
281316 }
282317
283- if ( touched . length === 0 ) {
284- return
285- }
286-
287- validator ( )
318+ validator ( config ?? { } )
288319 }
289320
290321 /**
@@ -299,8 +330,13 @@ export const createValidator = (callback: ValidationCallback, initialData: Recor
299330 */
300331 const form : TValidator = {
301332 touched : ( ) => touched ,
302- validate ( input , value ) {
303- validate ( input , value )
333+ validate ( name , value , config ) {
334+ if ( typeof name === 'object' && ! ( 'target' in name ) ) {
335+ config = name
336+ name = value = undefined
337+ }
338+
339+ validate ( name , value , config )
304340
305341 return form
306342 } ,
0 commit comments