forked from ajv-validator/ajv
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ref.ts
129 lines (118 loc) · 4.28 KB
/
ref.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import type {CodeKeywordDefinition, AnySchema} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import MissingRefError from "../../compile/ref_error"
import {callValidateCode} from "../code"
import {_, nil, stringify, Code, Name} from "../../compile/codegen"
import N from "../../compile/names"
import {SchemaEnv, resolveRef} from "../../compile"
import {mergeEvaluated} from "../../compile/util"
const def: CodeKeywordDefinition = {
keyword: "$ref",
schemaType: "string",
code(cxt: KeywordCxt): void {
const {gen, schema: $ref, it} = cxt
const {baseId, schemaEnv: env, validateName, opts, self} = it
const {root} = env
if (($ref === "#" || $ref === "#/") && baseId === root.baseId) return callRootRef()
const schOrEnv = resolveRef.call(self, root, baseId, $ref)
if (schOrEnv === undefined) throw new MissingRefError(it.opts.uriResolver, baseId, $ref)
if (schOrEnv instanceof SchemaEnv) return callValidate(schOrEnv)
return inlineRefSchema(schOrEnv)
function callRootRef(): void {
if (env === root) return callRef(cxt, validateName, env, env.$async)
const rootName = gen.scopeValue("root", {ref: root})
return callRef(cxt, _`${rootName}.validate`, root, root.$async)
}
function callValidate(sch: SchemaEnv): void {
const v = getValidate(cxt, sch)
callRef(cxt, v, sch, sch.$async)
}
function inlineRefSchema(sch: AnySchema): void {
const schName = gen.scopeValue(
"schema",
opts.code.source === true ? {ref: sch, code: stringify(sch)} : {ref: sch}
)
const valid = gen.name("valid")
const schCxt = cxt.subschema(
{
schema: sch,
dataTypes: [],
schemaPath: nil,
topSchemaRef: schName,
errSchemaPath: $ref,
},
valid
)
cxt.mergeEvaluated(schCxt)
cxt.ok(valid)
}
},
}
export function getValidate(cxt: KeywordCxt, sch: SchemaEnv): Code {
const {gen} = cxt
return sch.validate
? gen.scopeValue("validate", {ref: sch.validate})
: _`${gen.scopeValue("wrapper", {ref: sch})}.validate`
}
export function callRef(cxt: KeywordCxt, v: Code, sch?: SchemaEnv, $async?: boolean): void {
const {gen, it} = cxt
const {allErrors, schemaEnv: env, opts} = it
const passCxt = opts.passContext ? N.this : nil
if ($async) callAsyncRef()
else callSyncRef()
function callAsyncRef(): void {
if (!env.$async) throw new Error("async schema referenced by sync schema")
const valid = gen.let("valid")
gen.try(
() => {
gen.code(_`await ${callValidateCode(cxt, v, passCxt)}`)
addEvaluatedFrom(v) // TODO will not work with async, it has to be returned with the result
if (!allErrors) gen.assign(valid, true)
},
(e) => {
gen.if(_`!(${e} instanceof ${it.ValidationError as Name})`, () => gen.throw(e))
addErrorsFrom(e)
if (!allErrors) gen.assign(valid, false)
}
)
cxt.ok(valid)
}
function callSyncRef(): void {
cxt.result(
callValidateCode(cxt, v, passCxt),
() => addEvaluatedFrom(v),
() => addErrorsFrom(v)
)
}
function addErrorsFrom(source: Code): void {
const errs = _`${source}.errors`
gen.assign(N.vErrors, _`${N.vErrors} === null ? ${errs} : ${N.vErrors}.concat(${errs})`) // TODO tagged
gen.assign(N.errors, _`${N.vErrors}.length`)
}
function addEvaluatedFrom(source: Code): void {
if (!it.opts.unevaluated) return
const schEvaluated = sch?.validate?.evaluated
// TODO refactor
if (it.props !== true) {
if (schEvaluated && !schEvaluated.dynamicProps) {
if (schEvaluated.props !== undefined) {
it.props = mergeEvaluated.props(gen, schEvaluated.props, it.props)
}
} else {
const props = gen.var("props", _`${source}.evaluated.props`)
it.props = mergeEvaluated.props(gen, props, it.props, Name)
}
}
if (it.items !== true) {
if (schEvaluated && !schEvaluated.dynamicItems) {
if (schEvaluated.items !== undefined) {
it.items = mergeEvaluated.items(gen, schEvaluated.items, it.items)
}
} else {
const items = gen.var("items", _`${source}.evaluated.items`)
it.items = mergeEvaluated.items(gen, items, it.items, Name)
}
}
}
}
export default def