1 import type {CodeKeywordDefinition, AnySchema} from "../../types"
2 import type {KeywordCxt} from "../../compile/validate"
3 import MissingRefError from "../../compile/ref_error"
4 import {callValidateCode} from "../code"
5 import {_, nil, stringify, Code, Name} from "../../compile/codegen"
6 import N from "../../compile/names"
7 import {SchemaEnv, resolveRef} from "../../compile"
8 import {mergeEvaluated} from "../../compile/util"
10 const def: CodeKeywordDefinition = {
13 code(cxt: KeywordCxt): void {
14 const {gen, schema: $ref, it} = cxt
15 const {baseId, schemaEnv: env, validateName, opts, self} = it
17 if (($ref === "#" || $ref === "#/") && baseId === root.baseId) return callRootRef()
18 const schOrEnv = resolveRef.call(self, root, baseId, $ref)
19 if (schOrEnv === undefined) throw new MissingRefError(baseId, $ref)
20 if (schOrEnv instanceof SchemaEnv) return callValidate(schOrEnv)
21 return inlineRefSchema(schOrEnv)
23 function callRootRef(): void {
24 if (env === root) return callRef(cxt, validateName, env, env.$async)
25 const rootName = gen.scopeValue("root", {ref: root})
26 return callRef(cxt, _`${rootName}.validate`, root, root.$async)
29 function callValidate(sch: SchemaEnv): void {
30 const v = getValidate(cxt, sch)
31 callRef(cxt, v, sch, sch.$async)
34 function inlineRefSchema(sch: AnySchema): void {
35 const schName = gen.scopeValue(
37 opts.code.source === true ? {ref: sch, code: stringify(sch)} : {ref: sch}
39 const valid = gen.name("valid")
40 const schCxt = cxt.subschema(
45 topSchemaRef: schName,
50 cxt.mergeEvaluated(schCxt)
56 export function getValidate(cxt: KeywordCxt, sch: SchemaEnv): Code {
59 ? gen.scopeValue("validate", {ref: sch.validate})
60 : _`${gen.scopeValue("wrapper", {ref: sch})}.validate`
63 export function callRef(cxt: KeywordCxt, v: Code, sch?: SchemaEnv, $async?: boolean): void {
65 const {allErrors, schemaEnv: env, opts} = it
66 const passCxt = opts.passContext ? N.this : nil
67 if ($async) callAsyncRef()
70 function callAsyncRef(): void {
71 if (!env.$async) throw new Error("async schema referenced by sync schema")
72 const valid = gen.let("valid")
75 gen.code(_`await ${callValidateCode(cxt, v, passCxt)}`)
76 addEvaluatedFrom(v) // TODO will not work with async, it has to be returned with the result
77 if (!allErrors) gen.assign(valid, true)
80 gen.if(_`!(${e} instanceof ${it.ValidationError as Name})`, () => gen.throw(e))
82 if (!allErrors) gen.assign(valid, false)
88 function callSyncRef(): void {
90 callValidateCode(cxt, v, passCxt),
91 () => addEvaluatedFrom(v),
92 () => addErrorsFrom(v)
96 function addErrorsFrom(source: Code): void {
97 const errs = _`${source}.errors`
98 gen.assign(N.vErrors, _`${N.vErrors} === null ? ${errs} : ${N.vErrors}.concat(${errs})`) // TODO tagged
99 gen.assign(N.errors, _`${N.vErrors}.length`)
102 function addEvaluatedFrom(source: Code): void {
103 if (!it.opts.unevaluated) return
104 const schEvaluated = sch?.validate?.evaluated
106 if (it.props !== true) {
107 if (schEvaluated && !schEvaluated.dynamicProps) {
108 if (schEvaluated.props !== undefined) {
109 it.props = mergeEvaluated.props(gen, schEvaluated.props, it.props)
112 const props = gen.var("props", _`${source}.evaluated.props`)
113 it.props = mergeEvaluated.props(gen, props, it.props, Name)
116 if (it.items !== true) {
117 if (schEvaluated && !schEvaluated.dynamicItems) {
118 if (schEvaluated.items !== undefined) {
119 it.items = mergeEvaluated.items(gen, schEvaluated.items, it.items)
122 const items = gen.var("items", _`${source}.evaluated.items`)
123 it.items = mergeEvaluated.items(gen, items, it.items, Name)