import Ajv , { Options } from 'ajv' ; import ajvErrors from 'ajv-errors' ; import toPath from 'lodash.topath' ; const i18n = require ( 'ajv-i18n' ) ; import { CreateInstanceOptions , ConstantCreateInstanceOptions } from './types' ; import defaultOptions from './instance-default-options' ; import { Schema , ErrorSchema } from '../types' ; import { isObject } from '../utils' ; export * from './types' ; function mergeMaybeArray ( a1 , a2 ) { const f1 = a1 ? ( Array . isArray ( a1 ) ? a1 : [ a1 ] ) : [ ] ; const f2 = a2 ? ( Array . isArray ( a2 ) ? a2 : [ a2 ] ) : [ ] ; return [ ... f1 , ... f2 ] ; } function mergerOptions ( op1 , op2 ) { const ajvOption = { ... op1 . options , ... op2 . options , } ; const formats = mergeMaybeArray ( op1 . formats , op2 . formats ) ; const keywords = mergeMaybeArray ( op1 . keywords , op2 . keywords ) ; return { options : ajvOption , formats , keywords , } ; } export function createInstance ( opts = { } ) { const options = mergerOptions ( defaultOptions , opts ) ; const { options : instanceOptions , formats , keywords } = options ; const ajv = new Ajv ( instanceOptions ) ; ajvErrors ( ajv as any ) ; formats . forEach ( ( { name , definition } ) => ajv . addFormat ( name , definition ) ) ; keywords . forEach ( ( { name , definition } ) => ajv . addKeyword ( name , definition ) ) ; return ajv ; } const defaultInstance = createInstance ( { } ) ; export function validateData ( schema , data ) { const valid = defaultInstance . validate ( schema , data ) ; return { valid , errors : defaultInstance . errors , } ; } function toErrorSchema ( errors ) { if ( ! errors . length ) { return { } ; } return errors . reduce ( ( errorSchema , error ) => { const { property , message } = error ; const path = toPath ( property ) ; let parent = errorSchema ; if ( path . length > 0 && path [ 0 ] === '' ) { path . splice ( 0 , 1 ) ; } for ( const segment of path . slice ( 0 ) ) { if ( ! ( segment in parent ) ) { ; ( parent as any ) [ segment ] = { } ; } parent = parent [ segment ] ; } if ( Array . isArray ( parent . __errors ) ) { parent . __errors = parent . __errors . concat ( message || '' ) ; } else { if ( message ) { parent . __errors = [ message ] ; } } return errorSchema ; } , { } as ErrorSchema ) ; } export interface FormatedErrorObject { name : string ; property : string ; message ? : string ; params : Ajv . ErrorParameters ; stack : string ; schemaPath : string ; } /**
 * Transforming the error output from ajv to format used by jsonschema.
 * At some point, components should be updated to support ajv.
 */ function transformAjvErrors ( errors = [ ] ) { if ( errors === null || errors === undefined ) { return [ ] ; } return errors . map ( e => { const { dataPath , keyword , message , params , schemaPath } = e ; let property = `${ dataPath }` ; return { name : keyword , property , message , params , stack : `${ property } ${ message }` . trim ( ) , schemaPath , } ; } ) ; } /**
 * This function processes the formData with a user `validate` contributed
 * function, which receives the form data and an `errorHandler` object that
 * will be used to add custom validation errors for each field.
 */ export function validateFormData ( formData , schema , instance , locale = 'zh' , customValidate ? , transformErrors ? ) { const rootSchema = schema ; const ajv = instance ; let validationError = null ; try { ajv . validate ( schema , formData ) ; } catch ( err ) { validationError = err ; } i18n [ locale ] ( ajv . errors ) ; let errors = transformAjvErrors ( ajv . errors ) ; ajv . errors = null ; const noProperMetaSchema = validationError && validationError . message && typeof validationError . message === 'string' && validationError . message . includes ( 'no schema with key or ref ' ) ; if ( noProperMetaSchema ) { errors = [ ... errors , { stack : validationError . message , } as FormatedErrorObject , ] ; } if ( typeof transformErrors === 'function' ) { errors = transformErrors ( errors ) ; } let errorSchema = toErrorSchema ( errors ) ; if ( noProperMetaSchema ) { errorSchema = { ... errorSchema , ... { $schema : { __errors : [ validationError . message ] , } , } , } ; } if ( typeof customValidate !== 'function' ) { return { errors , errorSchema } ; } const customErrorSchemaProxy = createErrorHandlerProxy ( { } ) ; customValidate ( formData , customErrorSchemaProxy ) ; const userErrorSchema = toRaw ( customErrorSchemaProxy ) as ErrorSchema ; const newErrorSchema = mergeObjects ( errorSchema , userErrorSchema , true ) ; const newErrors = toErrorList ( newErrorSchema ) ; return { errors : newErrors , errorSchema : newErrorSchema , } ; } export function toErrorList ( errorSchema , fieldName = 'root' ) { let errorList = [ ] ; if ( '__errors' in errorSchema ) { errorList = errorList . concat ( errorSchema . __errors ! . map ( stack => { return { stack : `${ fieldName }: ${ stack }` , } as FormatedErrorObject ; } ) ) ; } return Object . keys ( errorSchema ) . reduce ( ( acc , key ) => { if ( key !== '__errors' ) { acc = acc . concat ( toErrorList ( errorSchema [ key ] , key ) ) ; } return acc ; } , errorList ) ; } export function mergeObjects ( obj1 , obj2 , concatArrays = false ) { var acc = Object . assign ( { } , obj1 ) ; return Object . keys ( obj2 ) . reduce ( ( acc , key ) => { const left = obj1 ? obj1 [ key ] : { } , right = obj2 [ key ] ; if ( obj1 && obj1 . hasOwnProperty ( key ) && isObject ( right ) ) { acc [ key ] = mergeObjects ( left , right , concatArrays ) ; } else if ( concatArrays && Array . isArray ( left ) && Array . isArray ( right ) ) { acc [ key ] = left . concat ( right ) ; } else { acc [ key ] = right ; } return acc ; } , acc ) ; } function toRaw ( p ) { return p . __get_raw__ ; } function createErrorHandlerProxy ( raw = { } ) { return new Proxy ( raw , { get ( target , key , receiver ) { if ( key === 'addError' ) { return ( message ) => { const t = target ; if ( t . __errors ) t . __errors . push ( message ) ; else t . __errors = [ message ] ; } ; } if ( key === '__get_raw__' ) { return raw ; } /**
             * since we only use this method to handle customValidate
             * we know that the `raw` is always `{}`
             */ const res = Reflect . get ( target , key , receiver ) ; if ( res === undefined ) { const proxy = createErrorHandlerProxy ( { } ) ; ( target as any ) [ key ] = proxy ; return proxy ; } return res ; } , } ) ; }