UNPKG

3.39 kBPlain TextView Raw
1import type {
2 CodeKeywordDefinition,
3 ErrorObject,
4 KeywordErrorDefinition,
5 SchemaMap,
6 AnySchema,
7} from "../../types"
8import type {KeywordCxt} from "../../compile/validate"
9import {_, str} from "../../compile/codegen"
10import {alwaysValidSchema} from "../../compile/util"
11import {checkReportMissingProp, checkMissingProp, reportMissingProp, propertyInData} from "../code"
12
13export type PropertyDependencies = {[K in string]?: string[]}
14
15export interface DependenciesErrorParams {
16 property: string
17 missingProperty: string
18 depsCount: number
19 deps: string // TODO change to string[]
20}
21
22type SchemaDependencies = SchemaMap
23
24export type DependenciesError = ErrorObject<
25 "dependencies",
26 DependenciesErrorParams,
27 {[K in string]?: string[] | AnySchema}
28>
29
30export 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
42const 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
54function 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
65export 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
95export 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
112export default def