[6a3a178] | 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
|
---|