| 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 |