lib/vocabularies/applicator/dependencies.ts
import type {
CodeKeywordDefinition,
ErrorObject,
KeywordErrorDefinition,
SchemaMap,
AnySchema,
} from "../../types"
import type {KeywordCxt} from "../../compile/validate"
import {_, str} from "../../compile/codegen"
import {alwaysValidSchema} from "../../compile/util"
import {checkReportMissingProp, checkMissingProp, reportMissingProp, propertyInData} from "../code"
export type PropertyDependencies = {[K in string]?: string[]}
export interface DependenciesErrorParams {
property: string
missingProperty: string
depsCount: number
deps: string // TODO change to string[]
}
type SchemaDependencies = SchemaMap
export type DependenciesError = ErrorObject<
"dependencies",
DependenciesErrorParams,
{[K in string]?: string[] | AnySchema}
>
export const error: KeywordErrorDefinition = {
message: ({params: {property, depsCount, deps}}) => {
const property_ies = depsCount === 1 ? "property" : "properties"
return str`must have ${property_ies} ${deps} when property ${property} is present`
},
params: ({params: {property, depsCount, deps, missingProperty}}) =>
_`{property: ${property},
missingProperty: ${missingProperty},
depsCount: ${depsCount},
deps: ${deps}}`, // TODO change to reference
}
const def: CodeKeywordDefinition = {
keyword: "dependencies",
type: "object",
schemaType: "object",
error,
code(cxt: KeywordCxt) {
const [propDeps, schDeps] = splitDependencies(cxt)
validatePropertyDeps(cxt, propDeps)
validateSchemaDeps(cxt, schDeps)
},
}
function splitDependencies({schema}: KeywordCxt): [PropertyDependencies, SchemaDependencies] {
const propertyDeps: PropertyDependencies = {}
const schemaDeps: SchemaDependencies = {}
for (const key in schema) {
if (key === "__proto__") continue
const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps
deps[key] = schema[key]
}
return [propertyDeps, schemaDeps]
}
export function validatePropertyDeps(
cxt: KeywordCxt,
propertyDeps: {[K in string]?: string[]} = cxt.schema
): void {
const {gen, data, it} = cxt
if (Object.keys(propertyDeps).length === 0) return
const missing = gen.let("missing")
for (const prop in propertyDeps) {
const deps = propertyDeps[prop] as string[]
if (deps.length === 0) continue
const hasProperty = propertyInData(gen, data, prop, it.opts.ownProperties)
cxt.setParams({
property: prop,
depsCount: deps.length,
deps: deps.join(", "),
})
if (it.allErrors) {
gen.if(hasProperty, () => {
for (const depProp of deps) {
checkReportMissingProp(cxt, depProp)
}
})
} else {
gen.if(_`${hasProperty} && (${checkMissingProp(cxt, deps, missing)})`)
reportMissingProp(cxt, missing)
gen.else()
}
}
}
export function validateSchemaDeps(cxt: KeywordCxt, schemaDeps: SchemaMap = cxt.schema): void {
const {gen, data, keyword, it} = cxt
const valid = gen.name("valid")
for (const prop in schemaDeps) {
if (alwaysValidSchema(it, schemaDeps[prop] as AnySchema)) continue
gen.if(
propertyInData(gen, data, prop, it.opts.ownProperties),
() => {
const schCxt = cxt.subschema({keyword, schemaProp: prop}, valid)
cxt.mergeValidEvaluated(schCxt, valid)
},
() => gen.var(valid, true) // TODO var
)
cxt.ok(valid)
}
}
export default def