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