Skip to content

Commit ddb1c48

Browse files
Improve performance for const schemas (fastify#511)
* Improved performance for const schema * Improve const object schema performance * Improve string and object performance * Restored example file * Changed findIndex with indexOf * Removed useless inference when schema is const * Review requests - Removed switchTypeSchema - Use of variable isRequired where defined - Rebase to PR fastify#510 - Remove use of default value for const * Rebase to fastify#510 (use hasOwnProperty from Object.prototype) * Use isRequired when available * Restored FJS format style * Restore FJS format style * Added support for nullable / type: [..., 'null']
1 parent 03660fd commit ddb1c48

File tree

3 files changed

+302
-16
lines changed

3 files changed

+302
-16
lines changed

benchmark/bench.js

+54
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,60 @@ const benchmarks = [
213213
n5: 42,
214214
b5: true
215215
}
216+
},
217+
{
218+
name: 'object with const string property',
219+
schema: {
220+
type: 'object',
221+
properties: {
222+
a: { const: 'const string' }
223+
}
224+
},
225+
input: { a: 'const string' }
226+
},
227+
{
228+
name: 'object with const number property',
229+
schema: {
230+
type: 'object',
231+
properties: {
232+
a: { const: 1 }
233+
}
234+
},
235+
input: { a: 1 }
236+
},
237+
{
238+
name: 'object with const bool property',
239+
schema: {
240+
type: 'object',
241+
properties: {
242+
a: { const: true }
243+
}
244+
},
245+
input: { a: true }
246+
},
247+
{
248+
name: 'object with const object property',
249+
schema: {
250+
type: 'object',
251+
properties: {
252+
foo: { const: { bar: 'baz' } }
253+
}
254+
},
255+
input: {
256+
foo: { bar: 'baz' }
257+
}
258+
},
259+
{
260+
name: 'object with const null property',
261+
schema: {
262+
type: 'object',
263+
properties: {
264+
foo: { const: null }
265+
}
266+
},
267+
input: {
268+
foo: null
269+
}
216270
}
217271
]
218272

index.js

+13-8
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ function buildCode (location) {
361361
code += buildValue(propertyLocation, `obj[${JSON.stringify(key)}]`)
362362

363363
const defaultValue = schema.properties[key].default
364+
364365
if (defaultValue !== undefined) {
365366
code += `
366367
} else {
@@ -796,7 +797,7 @@ function buildValue (location, input) {
796797
}
797798

798799
let type = schema.type
799-
const nullable = schema.nullable === true
800+
const nullable = schema.nullable === true || (Array.isArray(type) && type.includes('null'))
800801

801802
let code = ''
802803
let funcName
@@ -805,6 +806,17 @@ function buildValue (location, input) {
805806
type = 'string'
806807
}
807808

809+
if ('const' in schema) {
810+
if (nullable) {
811+
code += `
812+
json += ${input} === null ? 'null' : '${JSON.stringify(schema.const)}'
813+
`
814+
return code
815+
}
816+
code += `json += '${JSON.stringify(schema.const)}'`
817+
return code
818+
}
819+
808820
switch (type) {
809821
case 'null':
810822
code += 'json += serializer.asNull()'
@@ -865,13 +877,6 @@ function buildValue (location, input) {
865877
code += `
866878
json += JSON.stringify(${input})
867879
`
868-
} else if ('const' in schema) {
869-
code += `
870-
if(ajv.validate(${JSON.stringify(schema)}, ${input}))
871-
json += '${JSON.stringify(schema.const)}'
872-
else
873-
throw new Error(\`Item $\{JSON.stringify(${input})} does not match schema definition.\`)
874-
`
875880
} else if (schema.type === undefined) {
876881
code += `
877882
json += JSON.stringify(${input})

test/const.test.js

+235-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,184 @@ test('schema with const string', (t) => {
2424
t.ok(validate(JSON.parse(output)), 'valid schema')
2525
})
2626

27+
test('schema with const string and different input', (t) => {
28+
t.plan(2)
29+
30+
const schema = {
31+
type: 'object',
32+
properties: {
33+
foo: { const: 'bar' }
34+
}
35+
}
36+
37+
const validate = validator(schema)
38+
const stringify = build(schema)
39+
const output = stringify({
40+
foo: 'baz'
41+
})
42+
43+
t.equal(output, '{"foo":"bar"}')
44+
t.ok(validate(JSON.parse(output)), 'valid schema')
45+
})
46+
47+
test('schema with const string and different type input', (t) => {
48+
t.plan(2)
49+
50+
const schema = {
51+
type: 'object',
52+
properties: {
53+
foo: { const: 'bar' }
54+
}
55+
}
56+
57+
const validate = validator(schema)
58+
const stringify = build(schema)
59+
const output = stringify({
60+
foo: 1
61+
})
62+
63+
t.equal(output, '{"foo":"bar"}')
64+
t.ok(validate(JSON.parse(output)), 'valid schema')
65+
})
66+
67+
test('schema with const string and no input', (t) => {
68+
t.plan(2)
69+
70+
const schema = {
71+
type: 'object',
72+
properties: {
73+
foo: { const: 'bar' }
74+
}
75+
}
76+
77+
const validate = validator(schema)
78+
const stringify = build(schema)
79+
const output = stringify({})
80+
81+
t.equal(output, '{}')
82+
t.ok(validate(JSON.parse(output)), 'valid schema')
83+
})
84+
85+
test('schema with const number', (t) => {
86+
t.plan(2)
87+
88+
const schema = {
89+
type: 'object',
90+
properties: {
91+
foo: { const: 1 }
92+
}
93+
}
94+
95+
const validate = validator(schema)
96+
const stringify = build(schema)
97+
const output = stringify({
98+
foo: 1
99+
})
100+
101+
t.equal(output, '{"foo":1}')
102+
t.ok(validate(JSON.parse(output)), 'valid schema')
103+
})
104+
105+
test('schema with const number and different input', (t) => {
106+
t.plan(2)
107+
108+
const schema = {
109+
type: 'object',
110+
properties: {
111+
foo: { const: 1 }
112+
}
113+
}
114+
115+
const validate = validator(schema)
116+
const stringify = build(schema)
117+
const output = stringify({
118+
foo: 2
119+
})
120+
121+
t.equal(output, '{"foo":1}')
122+
t.ok(validate(JSON.parse(output)), 'valid schema')
123+
})
124+
125+
test('schema with const bool', (t) => {
126+
t.plan(2)
127+
128+
const schema = {
129+
type: 'object',
130+
properties: {
131+
foo: { const: true }
132+
}
133+
}
134+
135+
const validate = validator(schema)
136+
const stringify = build(schema)
137+
const output = stringify({
138+
foo: true
139+
})
140+
141+
t.equal(output, '{"foo":true}')
142+
t.ok(validate(JSON.parse(output)), 'valid schema')
143+
})
144+
145+
test('schema with const number', (t) => {
146+
t.plan(2)
147+
148+
const schema = {
149+
type: 'object',
150+
properties: {
151+
foo: { const: 1 }
152+
}
153+
}
154+
155+
const validate = validator(schema)
156+
const stringify = build(schema)
157+
const output = stringify({
158+
foo: 1
159+
})
160+
161+
t.equal(output, '{"foo":1}')
162+
t.ok(validate(JSON.parse(output)), 'valid schema')
163+
})
164+
165+
test('schema with const null', (t) => {
166+
t.plan(2)
167+
168+
const schema = {
169+
type: 'object',
170+
properties: {
171+
foo: { const: null }
172+
}
173+
}
174+
175+
const validate = validator(schema)
176+
const stringify = build(schema)
177+
const output = stringify({
178+
foo: null
179+
})
180+
181+
t.equal(output, '{"foo":null}')
182+
t.ok(validate(JSON.parse(output)), 'valid schema')
183+
})
184+
185+
test('schema with const array', (t) => {
186+
t.plan(2)
187+
188+
const schema = {
189+
type: 'object',
190+
properties: {
191+
foo: { const: [1, 2, 3] }
192+
}
193+
}
194+
195+
const validate = validator(schema)
196+
const stringify = build(schema)
197+
const output = stringify({
198+
foo: [1, 2, 3]
199+
})
200+
201+
t.equal(output, '{"foo":[1,2,3]}')
202+
t.ok(validate(JSON.parse(output)), 'valid schema')
203+
})
204+
27205
test('schema with const object', (t) => {
28206
t.plan(2)
29207

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

225+
test('schema with const and null as type', (t) => {
226+
t.plan(4)
227+
228+
const schema = {
229+
type: 'object',
230+
properties: {
231+
foo: { type: ['string', 'null'], const: 'baz' }
232+
}
233+
}
234+
235+
const validate = validator(schema)
236+
const stringify = build(schema)
237+
const output = stringify({
238+
foo: null
239+
})
240+
241+
t.equal(output, '{"foo":null}')
242+
t.ok(validate(JSON.parse(output)), 'valid schema')
243+
244+
const output2 = stringify({ foo: 'baz' })
245+
t.equal(output2, '{"foo":"baz"}')
246+
t.ok(validate(JSON.parse(output2)), 'valid schema')
247+
})
248+
249+
test('schema with const as nullable', (t) => {
250+
t.plan(4)
251+
252+
const schema = {
253+
type: 'object',
254+
properties: {
255+
foo: { nullable: true, const: 'baz' }
256+
}
257+
}
258+
259+
const validate = validator(schema)
260+
const stringify = build(schema)
261+
const output = stringify({
262+
foo: null
263+
})
264+
265+
t.equal(output, '{"foo":null}')
266+
t.ok(validate(JSON.parse(output)), 'valid schema')
267+
268+
const output2 = stringify({
269+
foo: 'baz'
270+
})
271+
t.equal(output2, '{"foo":"baz"}')
272+
t.ok(validate(JSON.parse(output2)), 'valid schema')
273+
})
274+
47275
test('schema with const and invalid object', (t) => {
48276
t.plan(2)
49277

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

286+
const validate = validator(schema)
58287
const stringify = build(schema)
59-
try {
60-
stringify({
61-
foo: { foo: 'baz' }
62-
})
63-
} catch (err) {
64-
t.match(err.message, /^Item .* does not match schema definition/, 'Given object has invalid const value')
65-
t.ok(err)
66-
}
288+
const result = stringify({
289+
foo: { foo: 'baz' }
290+
})
291+
292+
t.equal(result, '{"foo":{"foo":"bar"}}')
293+
t.ok(validate(JSON.parse(result)), 'valid schema')
67294
})

0 commit comments

Comments
 (0)