Skip to content

Commit

Permalink
refactor: "additionalProperties" to typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed Aug 23, 2020
1 parent cf35765 commit 839955c
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 193 deletions.
5 changes: 1 addition & 4 deletions lib/compile/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ export default function rules(): ValidationRules {
{type: "number", rules: ["format"]},
{type: "string", rules: ["format"]},
{type: "array", rules: []},
{
type: "object",
rules: ["additionalProperties"],
},
{type: "object", rules: []},
{rules: ["$ref"]},
],
all: toHash(ALL),
Expand Down
2 changes: 1 addition & 1 deletion lib/compile/subschema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export enum Expr {
Str,
}

interface SubschemaApplication {
export interface SubschemaApplication {
keyword: string
schemaProp?: string | number
data?: string
Expand Down
3 changes: 0 additions & 3 deletions lib/dot/errors.def
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@

{{## def._errorMessages = {
$ref: "'can\\\'t resolve reference {{=it.util.escapeQuotes($schema)}}'",
additionalProperties: "'should NOT have additional properties'",
format: "'should match format \"{{#def.concatSchemaEQ}}\"'",
custom: "'should pass \"{{=$rule.keyword}}\" keyword validation'",
patternRequired: "'should have property matching pattern \\'{{=$missingPattern}}\\''",
Expand All @@ -104,7 +103,6 @@

{{## def._errorSchemas = {
$ref: "{{=it.util.toQuotedString($schema)}}",
additionalProperties: "false",
format: "{{#def.schemaRefOrQS}}",
custom: "validate.schema{{=$schemaPath}}",
patternRequired: "validate.schema{{=$schemaPath}}",
Expand All @@ -118,7 +116,6 @@

{{## def._errorParams = {
$ref: "{ ref: '{{=it.util.escapeQuotes($schema)}}' }",
additionalProperties: "{ additionalProperty: '{{=$additionalProperty}}' }",
format: "{ format: {{#def.schemaValueQS}} }",
custom: "{ keyword: '{{=$rule.keyword}}' }",
patternRequired: "{ missingPattern: '{{=$missingPattern}}' }",
Expand Down
31 changes: 0 additions & 31 deletions lib/dot/missing.def

This file was deleted.

133 changes: 0 additions & 133 deletions lib/dot/properties.jst

This file was deleted.

1 change: 0 additions & 1 deletion lib/dotjs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@
module.exports = {
$ref: require("./ref"),
format: require("./format"),
additionalProperties: require("./properties"),
}
116 changes: 116 additions & 0 deletions lib/vocabularies/applicator/additionalProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {KeywordDefinition, KeywordErrorDefinition} from "../../types"
import {
allSchemaProperties,
schemaRefOrVal,
quotedString,
alwaysValidSchema,
loopPropertiesCode,
orExpr,
} from "../util"
import {applySubschema, SubschemaApplication, Expr} from "../../compile/subschema"
import {reportError, resetErrorsCount} from "../../compile/errors"

const error: KeywordErrorDefinition = {
message: "should NOT have additional properties",
params: ({params}) => `{additionalProperty: ${params.additionalProperty}}`,
}

const def: KeywordDefinition = {
keyword: "additionalProperties",
type: "object",
schemaType: ["object", "boolean"],
error,
code(cxt) {
const {gen, ok, errorParams, schema, parentSchema, data, it} = cxt
const {
allErrors,
usePattern,
opts: {removeAdditional},
} = it

if ((schema === undefined || alwaysValidSchema(it, schema)) && removeAdditional !== "all") {
return ok()
}

const props = allSchemaProperties(parentSchema.properties)
const patProps = allSchemaProperties(parentSchema.patternProperties)

const errsCount = gen.name("_errs")
gen.code(`const ${errsCount} = errors;`)
checkAdditionalProperties()
if (!allErrors) gen.code(`if (${errsCount} === errors) {`)

function checkAdditionalProperties(): void {
loopPropertiesCode(cxt, (key: string) => {
if (!props.length && !patProps.length) additionalPropertyCode(key)
else gen.if(isAdditional(key), () => additionalPropertyCode(key))
})
}

function isAdditional(key: string): string {
let definedProp = ""
if (props.length > 8) {
// TODO maybe an option instead of hard-coded 8?
const propsSchema = schemaRefOrVal(parentSchema.properties, it.schemaPath, "properties")
definedProp = `${propsSchema}.hasOwnProperty(${key})`
} else if (props.length) {
definedProp = orExpr(props, (p) => `${key} === ${quotedString(p)}`)
}
if (patProps.length) {
definedProp +=
(definedProp ? " || " : "") + orExpr(patProps, (p) => `${usePattern(p)}.test(${key})`)
}
return `!(${definedProp})`
}

function deleteAdditional(key: string): void {
gen.code(`delete ${data}[${key}];`)
}

function additionalPropertyCode(key: string): void {
if (removeAdditional === "all" || (removeAdditional && schema === false)) {
deleteAdditional(key)
return
}

if (schema === false) {
errorParams({additionalProperty: key})
reportError(cxt, error)
if (!allErrors) gen.code("break;")
return
}

if (typeof schema == "object" && !alwaysValidSchema(it, schema)) {
const valid = gen.name("valid")
if (removeAdditional === "failing") {
applyAdditionalSchema(key, valid, false)
gen.if(`!${valid}`, () => {
resetErrorsCount(gen, errsCount)
deleteAdditional(key)
})
} else {
applyAdditionalSchema(key, valid)
if (!allErrors) gen.if(`!${valid}`, "break")
}
}
}

function applyAdditionalSchema(key: string, valid: string, errors?: false): void {
const subschema: SubschemaApplication = {
keyword: "additionalProperties",
dataProp: key,
expr: Expr.Str,
}
if (errors === false) {
Object.assign(subschema, {
compositeRule: true,
createErrors: false,
allErrors: false,
})
}
applySubschema(it, subschema, valid)
}
},
}

module.exports = def
1 change: 1 addition & 0 deletions lib/vocabularies/applicator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const applicator: Vocabulary = [
// object
require("./dependencies"),
require("./propertyNames"),
require("./additionalProperties"),
require("./properties"),
require("./patternProperties"),
// any
Expand Down
12 changes: 5 additions & 7 deletions lib/vocabularies/missing.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {KeywordContext, KeywordErrorDefinition} from "../types"
import {noPropertyInData, quotedString} from "./util"
import {noPropertyInData, quotedString, orExpr} from "./util"
import {reportError} from "../compile/errors"
import {Expr} from "../compile/subschema"

Expand All @@ -25,12 +25,10 @@ export function checkMissingProp(
properties: string[],
missing: string
): string {
return properties
.map((prop) => {
const hasNoProp = noPropertyInData(data, prop, Expr.Const, opts.ownProperties)
return `(${hasNoProp} && (${missing} = ${quotedString(prop)}))`
})
.reduce((cond, part) => `${cond} || ${part}`)
return orExpr(properties, (prop) => {
const hasNoProp = noPropertyInData(data, prop, Expr.Const, opts.ownProperties)
return `(${hasNoProp} && (${missing} = ${quotedString(prop)}))`
})
}

export function reportMissingProp(
Expand Down
10 changes: 9 additions & 1 deletion lib/vocabularies/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,12 @@ export function alwaysValidSchema(
: !schemaHasRules(schema, RULES.all)
}

export function allSchemaProperties(schema?: object): string[] {
return schema ? Object.keys(schema).filter((p) => p !== "__proto__") : []
}

export function schemaProperties(it: CompilationContext, schema: object): string[] {
return Object.keys(schema).filter((p) => p !== "__proto__" && !alwaysValidSchema(it, schema[p]))
return allSchemaProperties(schema).filter((p) => !alwaysValidSchema(it, schema[p]))
}

export function isOwnProperty(data: string, property: string, expr: Expr): string {
Expand Down Expand Up @@ -98,3 +102,7 @@ export function loopPropertiesCode(
const iteration = it.opts.ownProperties ? `of Object.keys(${data})` : `in ${data}`
gen.for(`const ${key} ${iteration}`, () => loopBody(key))
}

export function orExpr(items: string[], mapCondition: (s: string, i: number) => string): string {
return items.map(mapCondition).reduce((expr, cond) => `${expr} || ${cond}`)
}
Loading

0 comments on commit 839955c

Please sign in to comment.