123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- import type {AnySchema} from "../../types"
- import type {SchemaCxt, SchemaObjCxt} from ".."
- import type {InstanceOptions} from "../../core"
- import {boolOrEmptySchema, topBoolOrEmptySchema} from "./boolSchema"
- import {coerceAndCheckDataType, getSchemaTypes} from "./dataType"
- import {schemaKeywords} from "./iterate"
- import {_, nil, str, Block, Code, Name, CodeGen} from "../codegen"
- import N from "../names"
- import {resolveUrl} from "../resolve"
- import {schemaHasRulesButRef, checkUnknownRules} from "../util"
- // schema compilation - generates validation function, subschemaCode (below) is used for subschemas
- export function validateFunctionCode(it: SchemaCxt): void {
- if (isSchemaObj(it)) {
- checkKeywords(it)
- if (schemaCxtHasRules(it)) {
- topSchemaObjCode(it)
- return
- }
- }
- validateFunction(it, () => topBoolOrEmptySchema(it))
- }
- function validateFunction(
- {gen, validateName, schema, schemaEnv, opts}: SchemaCxt,
- body: Block
- ): void {
- if (opts.code.es5) {
- gen.func(validateName, _`${N.data}, ${N.valCxt}`, schemaEnv.$async, () => {
- gen.code(_`"use strict"; ${funcSourceUrl(schema, opts)}`)
- destructureValCxtES5(gen, opts)
- gen.code(body)
- })
- } else {
- gen.func(validateName, _`${N.data}, ${destructureValCxt(opts)}`, schemaEnv.$async, () =>
- gen.code(funcSourceUrl(schema, opts)).code(body)
- )
- }
- }
- function destructureValCxt(opts: InstanceOptions): Code {
- return _`{${N.dataPath}="", ${N.parentData}, ${N.parentDataProperty}, ${N.rootData}=${N.data}${
- opts.dynamicRef ? _`, ${N.dynamicAnchors}={}` : nil
- }}={}`
- }
- function destructureValCxtES5(gen: CodeGen, opts: InstanceOptions): void {
- gen.if(
- N.valCxt,
- () => {
- gen.var(N.dataPath, _`${N.valCxt}.${N.dataPath}`)
- gen.var(N.parentData, _`${N.valCxt}.${N.parentData}`)
- gen.var(N.parentDataProperty, _`${N.valCxt}.${N.parentDataProperty}`)
- gen.var(N.rootData, _`${N.valCxt}.${N.rootData}`)
- if (opts.dynamicRef) gen.var(N.dynamicAnchors, _`${N.valCxt}.${N.dynamicAnchors}`)
- },
- () => {
- gen.var(N.dataPath, _`""`)
- gen.var(N.parentData, _`undefined`)
- gen.var(N.parentDataProperty, _`undefined`)
- gen.var(N.rootData, N.data)
- if (opts.dynamicRef) gen.var(N.dynamicAnchors, _`{}`)
- }
- )
- }
- function topSchemaObjCode(it: SchemaObjCxt): void {
- const {schema, opts, gen} = it
- validateFunction(it, () => {
- if (opts.$comment && schema.$comment) commentKeyword(it)
- checkNoDefault(it)
- gen.let(N.vErrors, null)
- gen.let(N.errors, 0)
- if (opts.unevaluated) resetEvaluated(it)
- typeAndKeywords(it)
- returnResults(it)
- })
- return
- }
- function resetEvaluated(it: SchemaObjCxt): void {
- // TODO maybe some hook to execute it in the end to check whether props/items are Name, as in assignEvaluated
- const {gen, validateName} = it
- it.evaluated = gen.const("evaluated", _`${validateName}.evaluated`)
- gen.if(_`${it.evaluated}.dynamicProps`, () => gen.assign(_`${it.evaluated}.props`, _`undefined`))
- gen.if(_`${it.evaluated}.dynamicItems`, () => gen.assign(_`${it.evaluated}.items`, _`undefined`))
- }
- function funcSourceUrl(schema: AnySchema, opts: InstanceOptions): Code {
- return typeof schema == "object" && schema.$id && (opts.code.source || opts.code.process)
- ? _`/*# sourceURL=${schema.$id} */`
- : nil
- }
- // schema compilation - this function is used recursively to generate code for sub-schemas
- export function subschemaCode(it: SchemaCxt, valid: Name): void {
- if (isSchemaObj(it)) {
- checkKeywords(it)
- if (schemaCxtHasRules(it)) {
- subSchemaObjCode(it, valid)
- return
- }
- }
- boolOrEmptySchema(it, valid)
- }
- export function schemaCxtHasRules({schema, self}: SchemaCxt): boolean {
- if (typeof schema == "boolean") return !schema
- for (const key in schema) if (self.RULES.all[key]) return true
- return false
- }
- function isSchemaObj(it: SchemaCxt): it is SchemaObjCxt {
- return typeof it.schema != "boolean"
- }
- function subSchemaObjCode(it: SchemaObjCxt, valid: Name): void {
- const {schema, gen, opts} = it
- if (opts.$comment && schema.$comment) commentKeyword(it)
- updateContext(it)
- checkAsync(it)
- const errsCount = gen.const("_errs", N.errors)
- typeAndKeywords(it, errsCount)
- // TODO var
- gen.var(valid, _`${errsCount} === ${N.errors}`)
- }
- function checkKeywords(it: SchemaObjCxt): void {
- checkUnknownRules(it)
- checkRefsAndKeywords(it)
- }
- function typeAndKeywords(it: SchemaObjCxt, errsCount?: Name): void {
- const types = getSchemaTypes(it.schema)
- const checkedTypes = coerceAndCheckDataType(it, types)
- schemaKeywords(it, types, !checkedTypes, errsCount)
- }
- function checkRefsAndKeywords(it: SchemaObjCxt): void {
- const {schema, errSchemaPath, opts, self} = it
- if (schema.$ref && opts.ignoreKeywordsWithRef && schemaHasRulesButRef(schema, self.RULES)) {
- self.logger.warn(`$ref: keywords ignored in schema at path "${errSchemaPath}"`)
- }
- }
- function checkNoDefault(it: SchemaObjCxt): void {
- const {schema, opts} = it
- if (schema.default !== undefined && opts.useDefaults && opts.strict) {
- checkStrictMode(it, "default is ignored in the schema root")
- }
- }
- function updateContext(it: SchemaObjCxt): void {
- if (it.schema.$id) it.baseId = resolveUrl(it.baseId, it.schema.$id)
- }
- function checkAsync(it: SchemaObjCxt): void {
- if (it.schema.$async && !it.schemaEnv.$async) throw new Error("async schema in sync schema")
- }
- function commentKeyword({gen, schemaEnv, schema, errSchemaPath, opts}: SchemaObjCxt): void {
- const msg = schema.$comment
- if (opts.$comment === true) {
- gen.code(_`${N.self}.logger.log(${msg})`)
- } else if (typeof opts.$comment == "function") {
- const schemaPath = str`${errSchemaPath}/$comment`
- const rootName = gen.scopeValue("root", {ref: schemaEnv.root})
- gen.code(_`${N.self}.opts.$comment(${msg}, ${schemaPath}, ${rootName}.schema)`)
- }
- }
- function returnResults(it: SchemaCxt): void {
- const {gen, schemaEnv, validateName, ValidationError, opts} = it
- if (schemaEnv.$async) {
- // TODO assign unevaluated
- gen.if(
- _`${N.errors} === 0`,
- () => gen.return(N.data),
- () => gen.throw(_`new ${ValidationError as Name}(${N.vErrors})`)
- )
- } else {
- gen.assign(_`${validateName}.errors`, N.vErrors)
- if (opts.unevaluated) assignEvaluated(it)
- gen.return(_`${N.errors} === 0`)
- }
- }
- function assignEvaluated({gen, evaluated, props, items}: SchemaCxt): void {
- if (props instanceof Name) gen.assign(_`${evaluated}.props`, props)
- if (items instanceof Name) gen.assign(_`${evaluated}.items`, items)
- }
- export function checkStrictMode(it: SchemaCxt, msg: string, mode = it.opts.strict): void {
- if (!mode) return
- msg = `strict mode: ${msg}`
- if (mode === true) throw new Error(msg)
- it.self.logger.warn(msg)
- }
|