Skip to content

Commit 668bd21

Browse files
florianreinhartmcollina
authored andcommitted
Add support for array of types in schema definition (#78)
1 parent 682cf04 commit 668bd21

File tree

2 files changed

+270
-28
lines changed

2 files changed

+270
-28
lines changed

index.js

Lines changed: 86 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ function build (schema, options) {
9090

9191
var dependencies = []
9292
var dependenciesName = []
93-
if (hasAnyOf(schema)) {
93+
if (hasAnyOf(schema) || hasArrayOfTypes(schema)) {
9494
dependencies.push(new Ajv())
9595
dependenciesName.push('ajv')
9696
}
@@ -113,6 +113,40 @@ function hasAnyOf (schema) {
113113
return false
114114
}
115115

116+
function hasArrayOfTypes (schema) {
117+
if (Array.isArray(schema.type)) { return true }
118+
var i
119+
120+
if (schema.type === 'object') {
121+
if (schema.properties) {
122+
var propertyKeys = Object.keys(schema.properties)
123+
for (i = 0; i < propertyKeys.length; i++) {
124+
if (hasArrayOfTypes(schema.properties[propertyKeys[i]])) {
125+
return true
126+
}
127+
}
128+
}
129+
} else if (schema.type === 'array') {
130+
if (Array.isArray(schema.items)) {
131+
for (i = 0; i < schema.items.length; i++) {
132+
if (hasArrayOfTypes(schema.items[i])) {
133+
return true
134+
}
135+
}
136+
} else if (schema.items) {
137+
return hasArrayOfTypes(schema.items)
138+
}
139+
} else if (Array.isArray(schema.anyOf)) {
140+
for (i = 0; i < schema.anyOf.length; i++) {
141+
if (hasArrayOfTypes(schema.anyOf[i])) {
142+
return true
143+
}
144+
}
145+
}
146+
147+
return false
148+
}
149+
116150
function $asNull () {
117151
return 'null'
118152
}
@@ -493,32 +527,7 @@ function buildArray (schema, code, name, externalSchema, fullSchema) {
493527
result = schema.items.reduce((res, item, i) => {
494528
var accessor = '[i]'
495529
const tmpRes = nested(laterCode, name, accessor, item, externalSchema, fullSchema, i)
496-
var condition = `i === ${i} && `
497-
switch (item.type) {
498-
case 'null':
499-
condition += `obj${accessor} === null`
500-
break
501-
case 'string':
502-
condition += `typeof obj${accessor} === 'string'`
503-
break
504-
case 'integer':
505-
condition += `Number.isInteger(obj${accessor})`
506-
break
507-
case 'number':
508-
condition += `Number.isFinite(obj${accessor})`
509-
break
510-
case 'boolean':
511-
condition += `typeof obj${accessor} === 'boolean'`
512-
break
513-
case 'object':
514-
condition += `obj${accessor} && typeof obj${accessor} === 'object' && obj${accessor}.constructor === Object`
515-
break
516-
case 'array':
517-
condition += `Array.isArray(obj${accessor})`
518-
break
519-
default:
520-
throw new Error(`${item.type} unsupported`)
521-
}
530+
var condition = `i === ${i} && ${buildArrayTypeCondition(item.type, accessor)}`
522531
return {
523532
code: `${res.code}
524533
${i > 0 ? 'else' : ''} if (${condition}) {
@@ -561,6 +570,43 @@ function buildArray (schema, code, name, externalSchema, fullSchema) {
561570
return code
562571
}
563572

573+
function buildArrayTypeCondition (type, accessor) {
574+
var condition
575+
switch (type) {
576+
case 'null':
577+
condition = `obj${accessor} === null`
578+
break
579+
case 'string':
580+
condition = `typeof obj${accessor} === 'string'`
581+
break
582+
case 'integer':
583+
condition = `Number.isInteger(obj${accessor})`
584+
break
585+
case 'number':
586+
condition = `Number.isFinite(obj${accessor})`
587+
break
588+
case 'boolean':
589+
condition = `typeof obj${accessor} === 'boolean'`
590+
break
591+
case 'object':
592+
condition = `obj${accessor} && typeof obj${accessor} === 'object' && obj${accessor}.constructor === Object`
593+
break
594+
case 'array':
595+
condition = `Array.isArray(obj${accessor})`
596+
break
597+
default:
598+
if (Array.isArray(type)) {
599+
var conditions = type.map((subType) => {
600+
return buildArrayTypeCondition(subType, accessor)
601+
})
602+
condition = `(${conditions.join(' || ')})`
603+
} else {
604+
throw new Error(`${type} unsupported`)
605+
}
606+
}
607+
return condition
608+
}
609+
564610
function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKey) {
565611
var code = ''
566612
var funcName
@@ -622,7 +668,19 @@ function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKe
622668
} else throw new Error(`${schema} unsupported`)
623669
break
624670
default:
625-
throw new Error(`${type} unsupported`)
671+
if (Array.isArray(type)) {
672+
type.forEach((type, index) => {
673+
var tempSchema = {type: type}
674+
var nestedResult = nested(laterCode, name, key, tempSchema, externalSchema, fullSchema, subKey)
675+
code += `
676+
${index === 0 ? 'if' : 'else if'}(ajv.validate(${require('util').inspect(tempSchema, {depth: null})}, obj${accessor}))
677+
${nestedResult.code}
678+
`
679+
laterCode = nestedResult.laterCode
680+
})
681+
} else {
682+
throw new Error(`${type} unsupported`)
683+
}
626684
}
627685

628686
return {

test/typesArray.test.js

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
'use strict'
2+
3+
const test = require('tap').test
4+
const build = require('..')
5+
6+
test('simple object with multi-type property', (t) => {
7+
t.plan(2)
8+
9+
const schema = {
10+
title: 'simple object with multi-type property',
11+
type: 'object',
12+
properties: {
13+
stringOrNumber: {
14+
type: ['string', 'number']
15+
}
16+
}
17+
}
18+
const stringify = build(schema)
19+
20+
try {
21+
const value = stringify({
22+
stringOrNumber: 'string'
23+
})
24+
t.is(value, '{"stringOrNumber":"string"}')
25+
} catch (e) {
26+
t.fail()
27+
}
28+
29+
try {
30+
const value = stringify({
31+
stringOrNumber: 42
32+
})
33+
t.is(value, '{"stringOrNumber":42}')
34+
} catch (e) {
35+
t.fail()
36+
}
37+
})
38+
39+
test('object with array of multiple types', (t) => {
40+
t.plan(3)
41+
42+
const schema = {
43+
title: 'object with array of multiple types',
44+
type: 'object',
45+
properties: {
46+
arrayOfStringsAndNumbers: {
47+
type: 'array',
48+
items: {
49+
type: ['string', 'number']
50+
}
51+
}
52+
}
53+
}
54+
const stringify = build(schema)
55+
56+
try {
57+
const value = stringify({
58+
arrayOfStringsAndNumbers: ['string1', 'string2']
59+
})
60+
t.is(value, '{"arrayOfStringsAndNumbers":["string1","string2"]}')
61+
} catch (e) {
62+
console.log(e)
63+
t.fail()
64+
}
65+
66+
try {
67+
const value = stringify({
68+
arrayOfStringsAndNumbers: [42, 7]
69+
})
70+
t.is(value, '{"arrayOfStringsAndNumbers":[42,7]}')
71+
} catch (e) {
72+
t.fail()
73+
}
74+
75+
try {
76+
const value = stringify({
77+
arrayOfStringsAndNumbers: ['string1', 42, 7, 'string2']
78+
})
79+
t.is(value, '{"arrayOfStringsAndNumbers":["string1",42,7,"string2"]}')
80+
} catch (e) {
81+
t.fail()
82+
}
83+
})
84+
85+
test('object with tuple of multiple types', (t) => {
86+
t.plan(2)
87+
88+
const schema = {
89+
title: 'object with array of multiple types',
90+
type: 'object',
91+
properties: {
92+
fixedTupleOfStringsAndNumbers: {
93+
type: 'array',
94+
items: [
95+
{
96+
type: 'string'
97+
},
98+
{
99+
type: 'number'
100+
},
101+
{
102+
type: ['string', 'number']
103+
}
104+
]
105+
}
106+
}
107+
}
108+
const stringify = build(schema)
109+
110+
try {
111+
const value = stringify({
112+
fixedTupleOfStringsAndNumbers: ['string1', 42, 7]
113+
})
114+
t.is(value, '{"fixedTupleOfStringsAndNumbers":["string1",42,7]}')
115+
} catch (e) {
116+
console.log(e)
117+
t.fail()
118+
}
119+
120+
try {
121+
const value = stringify({
122+
fixedTupleOfStringsAndNumbers: ['string1', 42, 'string2']
123+
})
124+
t.is(value, '{"fixedTupleOfStringsAndNumbers":["string1",42,"string2"]}')
125+
} catch (e) {
126+
console.log(e)
127+
t.fail()
128+
}
129+
})
130+
131+
test('object with anyOf and multiple types', (t) => {
132+
t.plan(3)
133+
134+
const schema = {
135+
title: 'object with anyOf and multiple types',
136+
type: 'object',
137+
properties: {
138+
objectOrBoolean: {
139+
anyOf: [
140+
{
141+
type: 'object',
142+
properties: {
143+
stringOrNumber: {
144+
type: ['string', 'number']
145+
}
146+
}
147+
},
148+
{
149+
type: 'boolean'
150+
}
151+
]
152+
}
153+
}
154+
}
155+
const stringify = build(schema)
156+
157+
try {
158+
const value = stringify({
159+
objectOrBoolean: { stringOrNumber: 'string' }
160+
})
161+
t.is(value, '{"objectOrBoolean":{"stringOrNumber":"string"}}')
162+
} catch (e) {
163+
console.log(e)
164+
t.fail()
165+
}
166+
167+
try {
168+
const value = stringify({
169+
objectOrBoolean: { stringOrNumber: 42 }
170+
})
171+
t.is(value, '{"objectOrBoolean":{"stringOrNumber":42}}')
172+
} catch (e) {
173+
t.fail()
174+
}
175+
176+
try {
177+
const value = stringify({
178+
objectOrBoolean: true
179+
})
180+
t.is(value, '{"objectOrBoolean":true}')
181+
} catch (e) {
182+
t.fail()
183+
}
184+
})

0 commit comments

Comments
 (0)