You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					119 lines
				
				3.7 KiB
			
		
		
			
		
	
	
					119 lines
				
				3.7 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								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
							 |