Skip to content

Commit 192e090

Browse files
authored
fix: ajv validation oneOf anyOf (#326)
* fix: ajv validation oneOf * feat: apply to anyof
1 parent ab383c9 commit 192e090

File tree

3 files changed

+76
-13
lines changed

3 files changed

+76
-13
lines changed

index.js

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,10 @@ function getStringSerializer (format) {
228228
'$asString'
229229
}
230230

231+
function getTestSerializer (format) {
232+
return stringSerializerMap[format]
233+
}
234+
231235
function $pad2Zeros (num) {
232236
const s = '00' + num
233237
return s[s.length - 2] + s[s.length - 1]
@@ -280,37 +284,40 @@ function $asBooleanNullable (bool) {
280284
return bool === null ? null : $asBoolean(bool)
281285
}
282286

283-
function $asDatetime (date) {
287+
function $asDatetime (date, skipQuotes) {
288+
const quotes = skipQuotes === true ? '' : '"'
284289
if (date instanceof Date) {
285-
return '"' + date.toISOString() + '"'
290+
return quotes + date.toISOString() + quotes
286291
} else if (date && typeof date.toISOString === 'function') {
287-
return '"' + date.toISOString() + '"'
292+
return quotes + date.toISOString() + quotes
288293
} else {
289294
return $asString(date)
290295
}
291296
}
292297

293-
function $asDate (date) {
298+
function $asDate (date, skipQuotes) {
299+
const quotes = skipQuotes === true ? '' : '"'
294300
if (date instanceof Date) {
295301
const year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date)
296302
const month = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(date)
297303
const day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date)
298-
return '"' + year + '-' + month + '-' + day + '"'
304+
return quotes + year + '-' + month + '-' + day + quotes
299305
} else if (date && typeof date.format === 'function') {
300-
return '"' + date.format('YYYY-MM-DD') + '"'
306+
return quotes + date.format('YYYY-MM-DD') + quotes
301307
} else {
302308
return $asString(date)
303309
}
304310
}
305311

306-
function $asTime (date) {
312+
function $asTime (date, skipQuotes) {
313+
const quotes = skipQuotes === true ? '' : '"'
307314
if (date instanceof Date) {
308315
const hour = new Intl.DateTimeFormat('en', { hour: 'numeric', hour12: false }).format(date)
309316
const minute = new Intl.DateTimeFormat('en', { minute: 'numeric' }).format(date)
310317
const second = new Intl.DateTimeFormat('en', { second: 'numeric' }).format(date)
311-
return '"' + $pad2Zeros(hour) + ':' + $pad2Zeros(minute) + ':' + $pad2Zeros(second) + '"'
318+
return quotes + $pad2Zeros(hour) + ':' + $pad2Zeros(minute) + ':' + $pad2Zeros(second) + quotes
312319
} else if (date && typeof date.format === 'function') {
313-
return '"' + date.format('HH:mm:ss') + '"'
320+
return quotes + date.format('HH:mm:ss') + quotes
314321
} else {
315322
return $asString(date)
316323
}
@@ -1177,6 +1184,11 @@ function nested (laterCode, name, key, location, subKey, isArray) {
11771184
const anyOfLocations = dereferenceOfRefs(location, 'anyOf')
11781185
anyOfLocations.forEach((location, index) => {
11791186
const nestedResult = nested(laterCode, name, key, location, subKey !== '' ? subKey : 'i' + index, isArray)
1187+
// We need a test serializer as the String serializer will not work with
1188+
// date/time ajv validations
1189+
// see: https://github.com/fastify/fast-json-stringify/issues/325
1190+
const testSerializer = getTestSerializer(location.schema.format)
1191+
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
11801192

11811193
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced
11821194
// otherwise any $ref pointing to an external schema would result in an error.
@@ -1186,7 +1198,7 @@ function nested (laterCode, name, key, location, subKey, isArray) {
11861198
// 2. `nested`, through `buildCode`, replaces any reference in object properties with the actual schema
11871199
// (see https://github.com/fastify/fast-json-stringify/blob/6da3b3e8ac24b1ca5578223adedb4083b7adf8db/index.js#L631)
11881200
code += `
1189-
${index === 0 ? 'if' : 'else if'}(ajv.validate(${JSON.stringify(location.schema)}, obj${accessor}))
1201+
${index === 0 ? 'if' : 'else if'}(ajv.validate(${JSON.stringify(location.schema)}, ${testValue}))
11901202
${nestedResult.code}
11911203
`
11921204
laterCode = nestedResult.laterCode
@@ -1199,10 +1211,11 @@ function nested (laterCode, name, key, location, subKey, isArray) {
11991211
const oneOfLocations = dereferenceOfRefs(location, 'oneOf')
12001212
oneOfLocations.forEach((location, index) => {
12011213
const nestedResult = nested(laterCode, name, key, location, subKey !== '' ? subKey : 'i' + index, isArray)
1202-
1203-
// see comment on anyOf about derefencing the schema before calling ajv.validate
1214+
const testSerializer = getTestSerializer(location.schema.format)
1215+
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
1216+
// see comment on anyOf about dereferencing the schema before calling ajv.validate
12041217
code += `
1205-
${index === 0 ? 'if' : 'else if'}(ajv.validate(${JSON.stringify(location.schema)}, obj${accessor}))
1218+
${index === 0 ? 'if' : 'else if'}(ajv.validate(${JSON.stringify(location.schema)}, ${testValue}))
12061219
${nestedResult.code}
12071220
`
12081221
laterCode = nestedResult.laterCode

test/anyof.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,3 +447,27 @@ test('anyOf with enum with more than 100 entries', (t) => {
447447
const value = stringify(['EUR', 'USD', null])
448448
t.equal(value, '["EUR","USD",null]')
449449
})
450+
451+
test('anyOf object with field of type string with format or null', (t) => {
452+
t.plan(1)
453+
const toStringify = new Date()
454+
const withOneOfSchema = {
455+
type: 'object',
456+
properties: {
457+
prop: {
458+
anyOf: [{
459+
type: 'string',
460+
format: 'date-time'
461+
}, {
462+
type: 'null'
463+
}]
464+
}
465+
}
466+
}
467+
468+
const withOneOfStringify = build(withOneOfSchema)
469+
470+
t.equal(withOneOfStringify({
471+
prop: toStringify
472+
}), `{"prop":"${toStringify.toISOString()}"}`)
473+
})

test/oneof.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,3 +375,29 @@ test('oneOf with enum with more than 100 entries', (t) => {
375375
const value = stringify(['EUR', 'USD', null])
376376
t.equal(value, '["EUR","USD",null]')
377377
})
378+
379+
test('oneOf object with field of type string with format or null', (t) => {
380+
t.plan(1)
381+
382+
const toStringify = new Date()
383+
384+
const withOneOfSchema = {
385+
type: 'object',
386+
properties: {
387+
prop: {
388+
oneOf: [{
389+
type: 'string',
390+
format: 'date-time'
391+
}, {
392+
type: 'null'
393+
}]
394+
}
395+
}
396+
}
397+
398+
const withOneOfStringify = build(withOneOfSchema)
399+
400+
t.equal(withOneOfStringify({
401+
prop: toStringify
402+
}), `{"prop":"${toStringify.toISOString()}"}`)
403+
})

0 commit comments

Comments
 (0)