UNPKG

3.78 kBPlain TextView Raw
1import type {
2 CodeKeywordDefinition,
3 AddedKeywordDefinition,
4 ErrorObject,
5 KeywordErrorDefinition,
6 AnySchema,
7} from "../../types"
8import {allSchemaProperties, usePattern, isOwnProperty} from "../code"
9import {_, nil, or, not, Code, Name} from "../../compile/codegen"
10import N from "../../compile/names"
11import type {SubschemaArgs} from "../../compile/validate/subschema"
12import {alwaysValidSchema, schemaRefOrVal, Type} from "../../compile/util"
13
14export type AdditionalPropertiesError = ErrorObject<
15 "additionalProperties",
16 {additionalProperty: string},
17 AnySchema
18>
19
20const error: KeywordErrorDefinition = {
21 message: "must NOT have additional properties",
22 params: ({params}) => _`{additionalProperty: ${params.additionalProperty}}`,
23}
24
25const def: CodeKeywordDefinition & AddedKeywordDefinition = {
26 keyword: "additionalProperties",
27 type: ["object"],
28 schemaType: ["boolean", "object"],
29 allowUndefined: true,
30 trackErrors: true,
31 error,
32 code(cxt) {
33 const {gen, schema, parentSchema, data, errsCount, it} = cxt
34 /* istanbul ignore if */
35 if (!errsCount) throw new Error("ajv implementation error")
36 const {allErrors, opts} = it
37 it.props = true
38 if (opts.removeAdditional !== "all" && alwaysValidSchema(it, schema)) return
39 const props = allSchemaProperties(parentSchema.properties)
40 const patProps = allSchemaProperties(parentSchema.patternProperties)
41 checkAdditionalProperties()
42 cxt.ok(_`${errsCount} === ${N.errors}`)
43
44 function checkAdditionalProperties(): void {
45 gen.forIn("key", data, (key: Name) => {
46 if (!props.length && !patProps.length) additionalPropertyCode(key)
47 else gen.if(isAdditional(key), () => additionalPropertyCode(key))
48 })
49 }
50
51 function isAdditional(key: Name): Code {
52 let definedProp: Code
53 if (props.length > 8) {
54 // TODO maybe an option instead of hard-coded 8?
55 const propsSchema = schemaRefOrVal(it, parentSchema.properties, "properties")
56 definedProp = isOwnProperty(gen, propsSchema as Code, key)
57 } else if (props.length) {
58 definedProp = or(...props.map((p) => _`${key} === ${p}`))
59 } else {
60 definedProp = nil
61 }
62 if (patProps.length) {
63 definedProp = or(definedProp, ...patProps.map((p) => _`${usePattern(cxt, p)}.test(${key})`))
64 }
65 return not(definedProp)
66 }
67
68 function deleteAdditional(key: Name): void {
69 gen.code(_`delete ${data}[${key}]`)
70 }
71
72 function additionalPropertyCode(key: Name): void {
73 if (opts.removeAdditional === "all" || (opts.removeAdditional && schema === false)) {
74 deleteAdditional(key)
75 return
76 }
77
78 if (schema === false) {
79 cxt.setParams({additionalProperty: key})
80 cxt.error()
81 if (!allErrors) gen.break()
82 return
83 }
84
85 if (typeof schema == "object" && !alwaysValidSchema(it, schema)) {
86 const valid = gen.name("valid")
87 if (opts.removeAdditional === "failing") {
88 applyAdditionalSchema(key, valid, false)
89 gen.if(not(valid), () => {
90 cxt.reset()
91 deleteAdditional(key)
92 })
93 } else {
94 applyAdditionalSchema(key, valid)
95 if (!allErrors) gen.if(not(valid), () => gen.break())
96 }
97 }
98 }
99
100 function applyAdditionalSchema(key: Name, valid: Name, errors?: false): void {
101 const subschema: SubschemaArgs = {
102 keyword: "additionalProperties",
103 dataProp: key,
104 dataPropType: Type.Str,
105 }
106 if (errors === false) {
107 Object.assign(subschema, {
108 compositeRule: true,
109 createErrors: false,
110 allErrors: false,
111 })
112 }
113 cxt.subschema(subschema, valid)
114 }
115 },
116}
117
118export default def