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"
|
---|
9 |
|
---|
10 | const def: CodeKeywordDefinition = {
|
---|
11 | keyword: "$ref",
|
---|
12 | schemaType: "string",
|
---|
13 | code(cxt: KeywordCxt): void {
|
---|
14 | const {gen, schema: $ref, it} = cxt
|
---|
15 | const {baseId, schemaEnv: env, validateName, opts, self} = it
|
---|
16 | const {root} = env
|
---|
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)
|
---|
22 |
|
---|
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)
|
---|
27 | }
|
---|
28 |
|
---|
29 | function callValidate(sch: SchemaEnv): void {
|
---|
30 | const v = getValidate(cxt, sch)
|
---|
31 | callRef(cxt, v, sch, sch.$async)
|
---|
32 | }
|
---|
33 |
|
---|
34 | function inlineRefSchema(sch: AnySchema): void {
|
---|
35 | const schName = gen.scopeValue(
|
---|
36 | "schema",
|
---|
37 | opts.code.source === true ? {ref: sch, code: stringify(sch)} : {ref: sch}
|
---|
38 | )
|
---|
39 | const valid = gen.name("valid")
|
---|
40 | const schCxt = cxt.subschema(
|
---|
41 | {
|
---|
42 | schema: sch,
|
---|
43 | dataTypes: [],
|
---|
44 | schemaPath: nil,
|
---|
45 | topSchemaRef: schName,
|
---|
46 | errSchemaPath: $ref,
|
---|
47 | },
|
---|
48 | valid
|
---|
49 | )
|
---|
50 | cxt.mergeEvaluated(schCxt)
|
---|
51 | cxt.ok(valid)
|
---|
52 | }
|
---|
53 | },
|
---|
54 | }
|
---|
55 |
|
---|
56 | export function getValidate(cxt: KeywordCxt, sch: SchemaEnv): Code {
|
---|
57 | const {gen} = cxt
|
---|
58 | return sch.validate
|
---|
59 | ? gen.scopeValue("validate", {ref: sch.validate})
|
---|
60 | : _`${gen.scopeValue("wrapper", {ref: sch})}.validate`
|
---|
61 | }
|
---|
62 |
|
---|
63 | export function callRef(cxt: KeywordCxt, v: Code, sch?: SchemaEnv, $async?: boolean): void {
|
---|
64 | const {gen, it} = cxt
|
---|
65 | const {allErrors, schemaEnv: env, opts} = it
|
---|
66 | const passCxt = opts.passContext ? N.this : nil
|
---|
67 | if ($async) callAsyncRef()
|
---|
68 | else callSyncRef()
|
---|
69 |
|
---|
70 | function callAsyncRef(): void {
|
---|
71 | if (!env.$async) throw new Error("async schema referenced by sync schema")
|
---|
72 | const valid = gen.let("valid")
|
---|
73 | gen.try(
|
---|
74 | () => {
|
---|
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)
|
---|
78 | },
|
---|
79 | (e) => {
|
---|
80 | gen.if(_`!(${e} instanceof ${it.ValidationError as Name})`, () => gen.throw(e))
|
---|
81 | addErrorsFrom(e)
|
---|
82 | if (!allErrors) gen.assign(valid, false)
|
---|
83 | }
|
---|
84 | )
|
---|
85 | cxt.ok(valid)
|
---|
86 | }
|
---|
87 |
|
---|
88 | function callSyncRef(): void {
|
---|
89 | cxt.result(
|
---|
90 | callValidateCode(cxt, v, passCxt),
|
---|
91 | () => addEvaluatedFrom(v),
|
---|
92 | () => addErrorsFrom(v)
|
---|
93 | )
|
---|
94 | }
|
---|
95 |
|
---|
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`)
|
---|
100 | }
|
---|
101 |
|
---|
102 | function addEvaluatedFrom(source: Code): void {
|
---|
103 | if (!it.opts.unevaluated) return
|
---|
104 | const schEvaluated = sch?.validate?.evaluated
|
---|
105 | // TODO refactor
|
---|
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)
|
---|
110 | }
|
---|
111 | } else {
|
---|
112 | const props = gen.var("props", _`${source}.evaluated.props`)
|
---|
113 | it.props = mergeEvaluated.props(gen, props, it.props, Name)
|
---|
114 | }
|
---|
115 | }
|
---|
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)
|
---|
120 | }
|
---|
121 | } else {
|
---|
122 | const items = gen.var("items", _`${source}.evaluated.items`)
|
---|
123 | it.items = mergeEvaluated.items(gen, items, it.items, Name)
|
---|
124 | }
|
---|
125 | }
|
---|
126 | }
|
---|
127 | }
|
---|
128 |
|
---|
129 | export default def
|
---|