You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					172 lines
				
				5.2 KiB
			
		
		
			
		
	
	
					172 lines
				
				5.2 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								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: KeywordCxt, def: MacroKeywordDefinition): void {
							 | 
						||
| 
								 | 
							
								  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: KeywordCxt, def: FuncKeywordDefinition): void {
							 | 
						||
| 
								 | 
							
								  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(): void {
							 | 
						||
| 
								 | 
							
								    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(): Name {
							 | 
						||
| 
								 | 
							
								    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(): Code {
							 | 
						||
| 
								 | 
							
								    const validateErrs = _`${validateRef}.errors`
							 | 
						||
| 
								 | 
							
								    gen.assign(validateErrs, null)
							 | 
						||
| 
								 | 
							
								    assignValid(nil)
							 | 
						||
| 
								 | 
							
								    return validateErrs
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function assignValid(_await: Code = def.async ? _`await ` : nil): void {
							 | 
						||
| 
								 | 
							
								    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: () => void): void {
							 | 
						||
| 
								 | 
							
								    gen.if(not(def.valid ?? valid), errors)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function modifyData(cxt: KeywordCxt): void {
							 | 
						||
| 
								 | 
							
								  const {gen, data, it} = cxt
							 | 
						||
| 
								 | 
							
								  gen.if(it.parentData, () => gen.assign(data, _`${it.parentData}[${it.parentDataProperty}]`))
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function addErrs(cxt: KeywordCxt, errs: Code): void {
							 | 
						||
| 
								 | 
							
								  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}: SchemaObjCxt, def: FuncKeywordDefinition): void {
							 | 
						||
| 
								 | 
							
								  if (def.async && !schemaEnv.$async) throw new Error("async keyword in sync schema")
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function useKeyword(gen: CodeGen, keyword: string, result?: KeywordCompilationResult): Name {
							 | 
						||
| 
								 | 
							
								  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: unknown,
							 | 
						||
| 
								 | 
							
								  schemaType: JSONType[],
							 | 
						||
| 
								 | 
							
								  allowUndefined = false
							 | 
						||
| 
								 | 
							
								): boolean {
							 | 
						||
| 
								 | 
							
								  // TODO add tests
							 | 
						||
| 
								 | 
							
								  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}: SchemaObjCxt,
							 | 
						||
| 
								 | 
							
								  def: AddedKeywordDefinition,
							 | 
						||
| 
								 | 
							
								  keyword: string
							 | 
						||
| 
								 | 
							
								): void {
							 | 
						||
| 
								 | 
							
								  /* istanbul ignore if */
							 | 
						||
| 
								 | 
							
								  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)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 |