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 |