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