| import type { |
| CodeKeywordDefinition, |
| AddedKeywordDefinition, |
| ErrorObject, |
| KeywordErrorDefinition, |
| AnySchema, |
| } from "../../types" |
| import {allSchemaProperties, usePattern, isOwnProperty} from "../code" |
| import {_, nil, or, not, Code, Name} from "../../compile/codegen" |
| import N from "../../compile/names" |
| import type {SubschemaArgs} from "../../compile/validate/subschema" |
| import {alwaysValidSchema, schemaRefOrVal, Type} from "../../compile/util" |
| |
| export type AdditionalPropertiesError = ErrorObject< |
| "additionalProperties", |
| {additionalProperty: string}, |
| AnySchema |
| > |
| |
| const error: KeywordErrorDefinition = { |
| message: "must NOT have additional properties", |
| params: ({params}) => _`{additionalProperty: ${params.additionalProperty}}`, |
| } |
| |
| const def: CodeKeywordDefinition & AddedKeywordDefinition = { |
| keyword: "additionalProperties", |
| type: ["object"], |
| schemaType: ["boolean", "object"], |
| allowUndefined: true, |
| trackErrors: true, |
| error, |
| code(cxt) { |
| const {gen, schema, parentSchema, data, errsCount, it} = cxt |
| /* istanbul ignore if */ |
| if (!errsCount) throw new Error("ajv implementation error") |
| const {allErrors, opts} = it |
| it.props = true |
| if (opts.removeAdditional !== "all" && alwaysValidSchema(it, schema)) return |
| const props = allSchemaProperties(parentSchema.properties) |
| const patProps = allSchemaProperties(parentSchema.patternProperties) |
| checkAdditionalProperties() |
| cxt.ok(_`${errsCount} === ${N.errors}`) |
| |
| function checkAdditionalProperties(): void { |
| gen.forIn("key", data, (key: Name) => { |
| if (!props.length && !patProps.length) additionalPropertyCode(key) |
| else gen.if(isAdditional(key), () => additionalPropertyCode(key)) |
| }) |
| } |
| |
| function isAdditional(key: Name): Code { |
| let definedProp: Code |
| if (props.length > 8) { |
| // TODO maybe an option instead of hard-coded 8? |
| const propsSchema = schemaRefOrVal(it, parentSchema.properties, "properties") |
| definedProp = isOwnProperty(gen, propsSchema as Code, key) |
| } else if (props.length) { |
| definedProp = or(...props.map((p) => _`${key} === ${p}`)) |
| } else { |
| definedProp = nil |
| } |
| if (patProps.length) { |
| definedProp = or(definedProp, ...patProps.map((p) => _`${usePattern(cxt, p)}.test(${key})`)) |
| } |
| return not(definedProp) |
| } |
| |
| function deleteAdditional(key: Name): void { |
| gen.code(_`delete ${data}[${key}]`) |
| } |
| |
| function additionalPropertyCode(key: Name): void { |
| if (opts.removeAdditional === "all" || (opts.removeAdditional && schema === false)) { |
| deleteAdditional(key) |
| return |
| } |
| |
| if (schema === false) { |
| cxt.setParams({additionalProperty: key}) |
| cxt.error() |
| if (!allErrors) gen.break() |
| return |
| } |
| |
| if (typeof schema == "object" && !alwaysValidSchema(it, schema)) { |
| const valid = gen.name("valid") |
| if (opts.removeAdditional === "failing") { |
| applyAdditionalSchema(key, valid, false) |
| gen.if(not(valid), () => { |
| cxt.reset() |
| deleteAdditional(key) |
| }) |
| } else { |
| applyAdditionalSchema(key, valid) |
| if (!allErrors) gen.if(not(valid), () => gen.break()) |
| } |
| } |
| } |
| |
| function applyAdditionalSchema(key: Name, valid: Name, errors?: false): void { |
| const subschema: SubschemaArgs = { |
| keyword: "additionalProperties", |
| dataProp: key, |
| dataPropType: Type.Str, |
| } |
| if (errors === false) { |
| Object.assign(subschema, { |
| compositeRule: true, |
| createErrors: false, |
| allErrors: false, |
| }) |
| } |
| cxt.subschema(subschema, valid) |
| } |
| }, |
| } |
| |
| export default def |