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
|
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}}`,
|
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)
|
107 | )
|
108 | cxt.ok(valid)
|
109 | }
|
110 | }
|
111 |
|
112 | export default def
|