additionalProperties.ts 3.7 KB

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