Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance for const schemas #511

Merged
merged 14 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 54 additions & 0 deletions benchmark/bench.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,60 @@ const benchmarks = [
n5: 42,
b5: true
}
},
{
name: 'object with const string property',
schema: {
type: 'object',
properties: {
a: { const: 'const string' }
}
},
input: { a: 'const string' }
},
{
name: 'object with const number property',
schema: {
type: 'object',
properties: {
a: { const: 1 }
}
},
input: { a: 1 }
},
{
name: 'object with const bool property',
schema: {
type: 'object',
properties: {
a: { const: true }
}
},
input: { a: true }
},
{
name: 'object with const object property',
schema: {
type: 'object',
properties: {
foo: { const: { bar: 'baz' } }
}
},
input: {
foo: { bar: 'baz' }
}
},
{
name: 'object with const null property',
schema: {
type: 'object',
properties: {
foo: { const: null }
}
},
input: {
foo: null
}
}
]

Expand Down
21 changes: 13 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ function buildCode (location) {
code += buildValue(propertyLocation, `obj[${JSON.stringify(key)}]`)

const defaultValue = schema.properties[key].default

if (defaultValue !== undefined) {
code += `
} else {
Expand Down Expand Up @@ -796,7 +797,7 @@ function buildValue (location, input) {
}

let type = schema.type
const nullable = schema.nullable === true
const nullable = schema.nullable === true || (Array.isArray(type) && type.includes('null'))

let code = ''
let funcName
Expand All @@ -805,6 +806,17 @@ function buildValue (location, input) {
type = 'string'
}

if ('const' in schema) {
if (nullable) {
code += `
json += ${input} === null ? 'null' : '${JSON.stringify(schema.const)}'
`
return code
}
code += `json += '${JSON.stringify(schema.const)}'`
return code
}

switch (type) {
case 'null':
code += 'json += serializer.asNull()'
Expand Down Expand Up @@ -865,13 +877,6 @@ function buildValue (location, input) {
code += `
json += JSON.stringify(${input})
`
} else if ('const' in schema) {
code += `
if(ajv.validate(${JSON.stringify(schema)}, ${input}))
json += '${JSON.stringify(schema.const)}'
else
throw new Error(\`Item $\{JSON.stringify(${input})} does not match schema definition.\`)
`
} else if (schema.type === undefined) {
code += `
json += JSON.stringify(${input})
Expand Down
243 changes: 235 additions & 8 deletions test/const.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,184 @@ test('schema with const string', (t) => {
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const string and different input', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 'bar' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 'baz'
})

t.equal(output, '{"foo":"bar"}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const string and different type input', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 'bar' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 1
})

t.equal(output, '{"foo":"bar"}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const string and no input', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 'bar' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({})

t.equal(output, '{}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const number', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 1 }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 1
})

t.equal(output, '{"foo":1}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const number and different input', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 1 }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 2
})

t.equal(output, '{"foo":1}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const bool', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: true }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: true
})

t.equal(output, '{"foo":true}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const number', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: 1 }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: 1
})

t.equal(output, '{"foo":1}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const null', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: null }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: null
})

t.equal(output, '{"foo":null}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const array', (t) => {
t.plan(2)

const schema = {
type: 'object',
properties: {
foo: { const: [1, 2, 3] }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: [1, 2, 3]
})

t.equal(output, '{"foo":[1,2,3]}')
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const object', (t) => {
t.plan(2)

Expand All @@ -44,6 +222,56 @@ test('schema with const object', (t) => {
t.ok(validate(JSON.parse(output)), 'valid schema')
})

test('schema with const and null as type', (t) => {
t.plan(4)

const schema = {
type: 'object',
properties: {
foo: { type: ['string', 'null'], const: 'baz' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: null
})

t.equal(output, '{"foo":null}')
t.ok(validate(JSON.parse(output)), 'valid schema')

const output2 = stringify({ foo: 'baz' })
t.equal(output2, '{"foo":"baz"}')
t.ok(validate(JSON.parse(output2)), 'valid schema')
})

test('schema with const as nullable', (t) => {
t.plan(4)

const schema = {
type: 'object',
properties: {
foo: { nullable: true, const: 'baz' }
}
}

const validate = validator(schema)
const stringify = build(schema)
const output = stringify({
foo: null
})

t.equal(output, '{"foo":null}')
t.ok(validate(JSON.parse(output)), 'valid schema')

const output2 = stringify({
foo: 'baz'
})
t.equal(output2, '{"foo":"baz"}')
t.ok(validate(JSON.parse(output2)), 'valid schema')
})

test('schema with const and invalid object', (t) => {
t.plan(2)

Expand All @@ -55,13 +283,12 @@ test('schema with const and invalid object', (t) => {
required: ['foo']
}

const validate = validator(schema)
const stringify = build(schema)
try {
stringify({
foo: { foo: 'baz' }
})
} catch (err) {
t.match(err.message, /^Item .* does not match schema definition/, 'Given object has invalid const value')
t.ok(err)
}
const result = stringify({
foo: { foo: 'baz' }
})

t.equal(result, '{"foo":{"foo":"bar"}}')
t.ok(validate(JSON.parse(result)), 'valid schema')
})