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.
		
		
		
		
		
			
		
			
				
					169 lines
				
				5.1 KiB
			
		
		
			
		
	
	
					169 lines
				
				5.1 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								import type {AnySchema, SchemaMap} from "../types"
							 | 
						||
| 
								 | 
							
								import type {SchemaCxt} from "../compile"
							 | 
						||
| 
								 | 
							
								import type {KeywordCxt} from "../compile/validate"
							 | 
						||
| 
								 | 
							
								import {CodeGen, _, and, or, not, nil, strConcat, getProperty, Code, Name} from "../compile/codegen"
							 | 
						||
| 
								 | 
							
								import {alwaysValidSchema, Type} from "../compile/util"
							 | 
						||
| 
								 | 
							
								import N from "../compile/names"
							 | 
						||
| 
								 | 
							
								import {useFunc} from "../compile/util"
							 | 
						||
| 
								 | 
							
								export function checkReportMissingProp(cxt: KeywordCxt, prop: string): void {
							 | 
						||
| 
								 | 
							
								  const {gen, data, it} = cxt
							 | 
						||
| 
								 | 
							
								  gen.if(noPropertyInData(gen, data, prop, it.opts.ownProperties), () => {
							 | 
						||
| 
								 | 
							
								    cxt.setParams({missingProperty: _`${prop}`}, true)
							 | 
						||
| 
								 | 
							
								    cxt.error()
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function checkMissingProp(
							 | 
						||
| 
								 | 
							
								  {gen, data, it: {opts}}: KeywordCxt,
							 | 
						||
| 
								 | 
							
								  properties: string[],
							 | 
						||
| 
								 | 
							
								  missing: Name
							 | 
						||
| 
								 | 
							
								): Code {
							 | 
						||
| 
								 | 
							
								  return or(
							 | 
						||
| 
								 | 
							
								    ...properties.map((prop) =>
							 | 
						||
| 
								 | 
							
								      and(noPropertyInData(gen, data, prop, opts.ownProperties), _`${missing} = ${prop}`)
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function reportMissingProp(cxt: KeywordCxt, missing: Name): void {
							 | 
						||
| 
								 | 
							
								  cxt.setParams({missingProperty: missing}, true)
							 | 
						||
| 
								 | 
							
								  cxt.error()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function hasPropFunc(gen: CodeGen): Name {
							 | 
						||
| 
								 | 
							
								  return gen.scopeValue("func", {
							 | 
						||
| 
								 | 
							
								    // eslint-disable-next-line @typescript-eslint/unbound-method
							 | 
						||
| 
								 | 
							
								    ref: Object.prototype.hasOwnProperty,
							 | 
						||
| 
								 | 
							
								    code: _`Object.prototype.hasOwnProperty`,
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function isOwnProperty(gen: CodeGen, data: Name, property: Name | string): Code {
							 | 
						||
| 
								 | 
							
								  return _`${hasPropFunc(gen)}.call(${data}, ${property})`
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function propertyInData(
							 | 
						||
| 
								 | 
							
								  gen: CodeGen,
							 | 
						||
| 
								 | 
							
								  data: Name,
							 | 
						||
| 
								 | 
							
								  property: Name | string,
							 | 
						||
| 
								 | 
							
								  ownProperties?: boolean
							 | 
						||
| 
								 | 
							
								): Code {
							 | 
						||
| 
								 | 
							
								  const cond = _`${data}${getProperty(property)} !== undefined`
							 | 
						||
| 
								 | 
							
								  return ownProperties ? _`${cond} && ${isOwnProperty(gen, data, property)}` : cond
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function noPropertyInData(
							 | 
						||
| 
								 | 
							
								  gen: CodeGen,
							 | 
						||
| 
								 | 
							
								  data: Name,
							 | 
						||
| 
								 | 
							
								  property: Name | string,
							 | 
						||
| 
								 | 
							
								  ownProperties?: boolean
							 | 
						||
| 
								 | 
							
								): Code {
							 | 
						||
| 
								 | 
							
								  const cond = _`${data}${getProperty(property)} === undefined`
							 | 
						||
| 
								 | 
							
								  return ownProperties ? or(cond, not(isOwnProperty(gen, data, property))) : cond
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function allSchemaProperties(schemaMap?: SchemaMap): string[] {
							 | 
						||
| 
								 | 
							
								  return schemaMap ? Object.keys(schemaMap).filter((p) => p !== "__proto__") : []
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function schemaProperties(it: SchemaCxt, schemaMap: SchemaMap): string[] {
							 | 
						||
| 
								 | 
							
								  return allSchemaProperties(schemaMap).filter(
							 | 
						||
| 
								 | 
							
								    (p) => !alwaysValidSchema(it, schemaMap[p] as AnySchema)
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function callValidateCode(
							 | 
						||
| 
								 | 
							
								  {schemaCode, data, it: {gen, topSchemaRef, schemaPath, errorPath}, it}: KeywordCxt,
							 | 
						||
| 
								 | 
							
								  func: Code,
							 | 
						||
| 
								 | 
							
								  context: Code,
							 | 
						||
| 
								 | 
							
								  passSchema?: boolean
							 | 
						||
| 
								 | 
							
								): Code {
							 | 
						||
| 
								 | 
							
								  const dataAndSchema = passSchema ? _`${schemaCode}, ${data}, ${topSchemaRef}${schemaPath}` : data
							 | 
						||
| 
								 | 
							
								  const valCxt: [Name, Code | number][] = [
							 | 
						||
| 
								 | 
							
								    [N.instancePath, strConcat(N.instancePath, errorPath)],
							 | 
						||
| 
								 | 
							
								    [N.parentData, it.parentData],
							 | 
						||
| 
								 | 
							
								    [N.parentDataProperty, it.parentDataProperty],
							 | 
						||
| 
								 | 
							
								    [N.rootData, N.rootData],
							 | 
						||
| 
								 | 
							
								  ]
							 | 
						||
| 
								 | 
							
								  if (it.opts.dynamicRef) valCxt.push([N.dynamicAnchors, N.dynamicAnchors])
							 | 
						||
| 
								 | 
							
								  const args = _`${dataAndSchema}, ${gen.object(...valCxt)}`
							 | 
						||
| 
								 | 
							
								  return context !== nil ? _`${func}.call(${context}, ${args})` : _`${func}(${args})`
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const newRegExp = _`new RegExp`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function usePattern({gen, it: {opts}}: KeywordCxt, pattern: string): Name {
							 | 
						||
| 
								 | 
							
								  const u = opts.unicodeRegExp ? "u" : ""
							 | 
						||
| 
								 | 
							
								  const {regExp} = opts.code
							 | 
						||
| 
								 | 
							
								  const rx = regExp(pattern, u)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return gen.scopeValue("pattern", {
							 | 
						||
| 
								 | 
							
								    key: rx.toString(),
							 | 
						||
| 
								 | 
							
								    ref: rx,
							 | 
						||
| 
								 | 
							
								    code: _`${regExp.code === "new RegExp" ? newRegExp : useFunc(gen, regExp)}(${pattern}, ${u})`,
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function validateArray(cxt: KeywordCxt): Name {
							 | 
						||
| 
								 | 
							
								  const {gen, data, keyword, it} = cxt
							 | 
						||
| 
								 | 
							
								  const valid = gen.name("valid")
							 | 
						||
| 
								 | 
							
								  if (it.allErrors) {
							 | 
						||
| 
								 | 
							
								    const validArr = gen.let("valid", true)
							 | 
						||
| 
								 | 
							
								    validateItems(() => gen.assign(validArr, false))
							 | 
						||
| 
								 | 
							
								    return validArr
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  gen.var(valid, true)
							 | 
						||
| 
								 | 
							
								  validateItems(() => gen.break())
							 | 
						||
| 
								 | 
							
								  return valid
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function validateItems(notValid: () => void): void {
							 | 
						||
| 
								 | 
							
								    const len = gen.const("len", _`${data}.length`)
							 | 
						||
| 
								 | 
							
								    gen.forRange("i", 0, len, (i) => {
							 | 
						||
| 
								 | 
							
								      cxt.subschema(
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          keyword,
							 | 
						||
| 
								 | 
							
								          dataProp: i,
							 | 
						||
| 
								 | 
							
								          dataPropType: Type.Num,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        valid
							 | 
						||
| 
								 | 
							
								      )
							 | 
						||
| 
								 | 
							
								      gen.if(not(valid), notValid)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export function validateUnion(cxt: KeywordCxt): void {
							 | 
						||
| 
								 | 
							
								  const {gen, schema, keyword, it} = cxt
							 | 
						||
| 
								 | 
							
								  /* istanbul ignore if */
							 | 
						||
| 
								 | 
							
								  if (!Array.isArray(schema)) throw new Error("ajv implementation error")
							 | 
						||
| 
								 | 
							
								  const alwaysValid = schema.some((sch: AnySchema) => alwaysValidSchema(it, sch))
							 | 
						||
| 
								 | 
							
								  if (alwaysValid && !it.opts.unevaluated) return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const valid = gen.let("valid", false)
							 | 
						||
| 
								 | 
							
								  const schValid = gen.name("_valid")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  gen.block(() =>
							 | 
						||
| 
								 | 
							
								    schema.forEach((_sch: AnySchema, i: number) => {
							 | 
						||
| 
								 | 
							
								      const schCxt = cxt.subschema(
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          keyword,
							 | 
						||
| 
								 | 
							
								          schemaProp: i,
							 | 
						||
| 
								 | 
							
								          compositeRule: true,
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        schValid
							 | 
						||
| 
								 | 
							
								      )
							 | 
						||
| 
								 | 
							
								      gen.assign(valid, _`${valid} || ${schValid}`)
							 | 
						||
| 
								 | 
							
								      const merged = cxt.mergeValidEvaluated(schCxt, schValid)
							 | 
						||
| 
								 | 
							
								      // can short-circuit if `unevaluatedProperties/Items` not supported (opts.unevaluated !== true)
							 | 
						||
| 
								 | 
							
								      // or if all properties and items were evaluated (it.props === true && it.items === true)
							 | 
						||
| 
								 | 
							
								      if (!merged) gen.if(not(valid))
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  cxt.result(
							 | 
						||
| 
								 | 
							
								    valid,
							 | 
						||
| 
								 | 
							
								    () => cxt.reset(),
							 | 
						||
| 
								 | 
							
								    () => cxt.error(true)
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								}
							 |