import type { KeywordCxt } from "." ; import type { AnySchema , SchemaValidateFunction , AnyValidateFunction , AddedKeywordDefinition , MacroKeywordDefinition , FuncKeywordDefinition , } from "../../types" ; import type { SchemaObjCxt } from ".." ; import { _ , nil , not , stringify , Code , Name , CodeGen } from "../codegen" ; import N from "../names" ; import type { JSONType } from "../rules" ; import { callValidateCode } from "../../vocabularies/code" ; import { extendErrors } from "../errors" ; type KeywordCompilationResult = AnySchema | SchemaValidateFunction | AnyValidateFunction ; export function macroKeywordCode ( cxt , def ) { const { gen , keyword , schema , parentSchema , it } = cxt ; const macroSchema = def . macro . call ( it . self , schema , parentSchema , it ) ; const schemaRef = useKeyword ( gen , keyword , macroSchema ) ; if ( it . opts . validateSchema !== false ) it . self . validateSchema ( macroSchema , true ) ; const valid = gen . name ( "valid" ) ; cxt . subschema ( { schema : macroSchema , schemaPath : nil , errSchemaPath : `${ it . errSchemaPath }/${ keyword }` , topSchemaRef : schemaRef , compositeRule : true , } , valid ) ; cxt . pass ( valid , ( ) => cxt . error ( true ) ) ; } export function funcKeywordCode ( cxt , def ) { const { gen , keyword , schema , parentSchema , $data , it } = cxt ; checkAsyncKeyword ( it , def ) ; const validate = ! $data && def . compile ? def . compile . call ( it . self , schema , parentSchema , it ) : def . validate ; const validateRef = useKeyword ( gen , keyword , validate ) ; const valid = gen . let ( "valid" ) ; cxt . block$data ( valid , validateKeyword ) ; cxt . ok ( def . valid ?? valid ) ; function validateKeyword ( ) { if ( def . errors === false ) { assignValid ( ) ; if ( def . modifying ) modifyData ( cxt ) ; reportErrs ( ( ) => cxt . error ( ) ) ; } else { const ruleErrs = def . async ? validateAsync ( ) : validateSync ( ) ; if ( def . modifying ) modifyData ( cxt ) ; reportErrs ( ( ) => addErrs ( cxt , ruleErrs ) ) ; } } function validateAsync ( ) { const ruleErrs = gen . let ( "ruleErrs" , null ) ; gen . try ( ( ) => assignValid ( _ `await ` ) , ( e ) => gen . assign ( valid , false ) . if ( _ `${ e } instanceof ${ it . ValidationError as Name }` , ( ) => gen . assign ( ruleErrs , _ `${ e }.errors` ) , ( ) => gen . throw ( e ) ) ) ; return ruleErrs ; } function validateSync ( ) { const validateErrs = _ `${ validateRef }.errors` ; gen . assign ( validateErrs , null ) ; assignValid ( nil ) ; return validateErrs ; } function assignValid ( _await = def . async ? _ `await ` : nil ) { const passCxt = it . opts . passContext ? N . this : N . self ; const passSchema = ! ( ( "compile" in def && ! $data ) || def . schema === false ) ; gen . assign ( valid , _ `${ _await }${ callValidateCode ( cxt , validateRef , passCxt , passSchema ) }` , def . modifying ) ; } function reportErrs ( errors ) { gen . if ( not ( def . valid ?? valid ) , errors ) ; } } function modifyData ( cxt ) { const { gen , data , it } = cxt ; gen . if ( it . parentData , ( ) => gen . assign ( data , _ `${ it . parentData }[${ it . parentDataProperty }]` ) ) ; } function addErrs ( cxt , errs ) { const { gen } = cxt ; gen . if ( _ `Array.isArray(${ errs })` , ( ) => { gen . assign ( N . vErrors , _ `${ N . vErrors } === null ? ${ errs } : ${ N . vErrors }.concat(${ errs })` ) . assign ( N . errors , _ `${ N . vErrors }.length` ) ; extendErrors ( cxt ) ; } , ( ) => cxt . error ( ) ) ; } function checkAsyncKeyword ( { schemaEnv } , def ) { if ( def . async && ! schemaEnv . $async ) throw new Error ( "async keyword in sync schema" ) ; } function useKeyword ( gen , keyword , result ? ) { if ( result === undefined ) throw new Error ( `keyword "${ keyword }" failed to compile` ) ; return gen . scopeValue ( "keyword" , typeof result == "function" ? { ref : result } : { ref : result , code : stringify ( result ) } ) ; } export function validSchemaType ( schema , schemaType , allowUndefined = false ) { return ( ! schemaType . length || schemaType . some ( ( st ) => st === "array" ? Array . isArray ( schema ) : st === "object" ? schema && typeof schema == "object" && ! Array . isArray ( schema ) : typeof schema == st || ( allowUndefined && typeof schema == "undefined" ) ) ) ; } export function validateKeywordUsage ( { schema , opts , self , errSchemaPath } , def , keyword ) { if ( Array . isArray ( def . keyword ) ? ! def . keyword . includes ( keyword ) : def . keyword !== keyword ) { throw new Error ( "ajv implementation error" ) ; } const deps = def . dependencies ; if ( deps ?. some ( ( kwd ) => ! Object . prototype . hasOwnProperty . call ( schema , kwd ) ) ) { throw new Error ( `parent schema must have dependencies of ${ keyword }: ${ deps . join ( "," ) }` ) ; } if ( def . validateSchema ) { const valid = def . validateSchema ( schema [ keyword ] ) ; if ( ! valid ) { const msg = `keyword "${ keyword }" value is invalid at path "${ errSchemaPath }": ` + self . errorsText ( def . validateSchema . errors ) ; if ( opts . validateSchema === "log" ) self . logger . error ( msg ) ; else throw new Error ( msg ) ; } } }