1 | import type {
|
---|
2 | CodeKeywordDefinition,
|
---|
3 | ErrorObject,
|
---|
4 | KeywordErrorDefinition,
|
---|
5 | SchemaMap,
|
---|
6 | AnySchema,
|
---|
7 | } from "../../types"
|
---|
8 | import type {KeywordCxt} from "../../compile/validate"
|
---|
9 | import {_, str} from "../../compile/codegen"
|
---|
10 | import {alwaysValidSchema} from "../../compile/util"
|
---|
11 | import {checkReportMissingProp, checkMissingProp, reportMissingProp, propertyInData} from "../code"
|
---|
12 |
|
---|
13 | export type PropertyDependencies = {[K in string]?: string[]}
|
---|
14 |
|
---|
15 | export interface DependenciesErrorParams {
|
---|
16 | property: string
|
---|
17 | missingProperty: string
|
---|
18 | depsCount: number
|
---|
19 | deps: string // TODO change to string[]
|
---|
20 | }
|
---|
21 |
|
---|
22 | type SchemaDependencies = SchemaMap
|
---|
23 |
|
---|
24 | export type DependenciesError = ErrorObject<
|
---|
25 | "dependencies",
|
---|
26 | DependenciesErrorParams,
|
---|
27 | {[K in string]?: string[] | AnySchema}
|
---|
28 | >
|
---|
29 |
|
---|
30 | export const error: KeywordErrorDefinition = {
|
---|
31 | message: ({params: {property, depsCount, deps}}) => {
|
---|
32 | const property_ies = depsCount === 1 ? "property" : "properties"
|
---|
33 | return str`must have ${property_ies} ${deps} when property ${property} is present`
|
---|
34 | },
|
---|
35 | params: ({params: {property, depsCount, deps, missingProperty}}) =>
|
---|
36 | _`{property: ${property},
|
---|
37 | missingProperty: ${missingProperty},
|
---|
38 | depsCount: ${depsCount},
|
---|
39 | deps: ${deps}}`, // TODO change to reference
|
---|
40 | }
|
---|
41 |
|
---|
42 | const def: CodeKeywordDefinition = {
|
---|
43 | keyword: "dependencies",
|
---|
44 | type: "object",
|
---|
45 | schemaType: "object",
|
---|
46 | error,
|
---|
47 | code(cxt: KeywordCxt) {
|
---|
48 | const [propDeps, schDeps] = splitDependencies(cxt)
|
---|
49 | validatePropertyDeps(cxt, propDeps)
|
---|
50 | validateSchemaDeps(cxt, schDeps)
|
---|
51 | },
|
---|
52 | }
|
---|
53 |
|
---|
54 | function splitDependencies({schema}: KeywordCxt): [PropertyDependencies, SchemaDependencies] {
|
---|
55 | const propertyDeps: PropertyDependencies = {}
|
---|
56 | const schemaDeps: SchemaDependencies = {}
|
---|
57 | for (const key in schema) {
|
---|
58 | if (key === "__proto__") continue
|
---|
59 | const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps
|
---|
60 | deps[key] = schema[key]
|
---|
61 | }
|
---|
62 | return [propertyDeps, schemaDeps]
|
---|
63 | }
|
---|
64 |
|
---|
65 | export function validatePropertyDeps(
|
---|
66 | cxt: KeywordCxt,
|
---|
67 | propertyDeps: {[K in string]?: string[]} = cxt.schema
|
---|
68 | ): void {
|
---|
69 | const {gen, data, it} = cxt
|
---|
70 | if (Object.keys(propertyDeps).length === 0) return
|
---|
71 | const missing = gen.let("missing")
|
---|
72 | for (const prop in propertyDeps) {
|
---|
73 | const deps = propertyDeps[prop] as string[]
|
---|
74 | if (deps.length === 0) continue
|
---|
75 | const hasProperty = propertyInData(gen, data, prop, it.opts.ownProperties)
|
---|
76 | cxt.setParams({
|
---|
77 | property: prop,
|
---|
78 | depsCount: deps.length,
|
---|
79 | deps: deps.join(", "),
|
---|
80 | })
|
---|
81 | if (it.allErrors) {
|
---|
82 | gen.if(hasProperty, () => {
|
---|
83 | for (const depProp of deps) {
|
---|
84 | checkReportMissingProp(cxt, depProp)
|
---|
85 | }
|
---|
86 | })
|
---|
87 | } else {
|
---|
88 | gen.if(_`${hasProperty} && (${checkMissingProp(cxt, deps, missing)})`)
|
---|
89 | reportMissingProp(cxt, missing)
|
---|
90 | gen.else()
|
---|
91 | }
|
---|
92 | }
|
---|
93 | }
|
---|
94 |
|
---|
95 | export function validateSchemaDeps(cxt: KeywordCxt, schemaDeps: SchemaMap = cxt.schema): void {
|
---|
96 | const {gen, data, keyword, it} = cxt
|
---|
97 | const valid = gen.name("valid")
|
---|
98 | for (const prop in schemaDeps) {
|
---|
99 | if (alwaysValidSchema(it, schemaDeps[prop] as AnySchema)) continue
|
---|
100 | gen.if(
|
---|
101 | propertyInData(gen, data, prop, it.opts.ownProperties),
|
---|
102 | () => {
|
---|
103 | const schCxt = cxt.subschema({keyword, schemaProp: prop}, valid)
|
---|
104 | cxt.mergeValidEvaluated(schCxt, valid)
|
---|
105 | },
|
---|
106 | () => gen.var(valid, true) // TODO var
|
---|
107 | )
|
---|
108 | cxt.ok(valid)
|
---|
109 | }
|
---|
110 | }
|
---|
111 |
|
---|
112 | export default def
|
---|