diff --git a/index.js b/index.js index 2a179cdf..bce4dca4 100644 --- a/index.js +++ b/index.js @@ -408,103 +408,56 @@ function addPatternProperties (location) { for (var i = 0; i < keys.length; i++) { if (properties[keys[i]]) continue ` + let laterCode = '' + Object.keys(pp).forEach((regex, index) => { let ppLocation = mergeLocation(location, { schema: pp[regex] }) if (pp[regex].$ref) { ppLocation = refFinder(pp[regex].$ref, location) pp[regex] = ppLocation.schema } - const type = pp[regex].type - const format = pp[regex].format - const stringSerializer = getStringSerializer(format) + try { RegExp(regex) } catch (err) { throw new Error(`${err.message}. Found at ${regex} matching ${JSON.stringify(pp[regex])}`) } - const ifPpKeyExists = `if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) {` - - if (type === 'object') { - code += `${buildObject(ppLocation, '', 'buildObjectPP' + index, 'buildObjectPP' + index)} - ${ifPpKeyExists} - ${addComma} - json += serializer.asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]]) - ` - } else if (type === 'array') { - code += `${buildArray(ppLocation, '', 'buildArrayPP' + index, 'buildArrayPP' + index)} - ${ifPpKeyExists} - ${addComma} - json += serializer.asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]]) - ` - } else if (type === 'null') { - code += ` - ${ifPpKeyExists} - ${addComma} - json += serializer.asString(keys[i]) +':null' - ` - } else if (type === 'string') { - code += ` - ${ifPpKeyExists} - ${addComma} - json += serializer.asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]]) - ` - } else if (type === 'integer') { - code += ` - ${ifPpKeyExists} - ${addComma} - json += serializer.asString(keys[i]) + ':' + serializer.asInteger(obj[keys[i]]) - ` - } else if (type === 'number') { - code += ` - ${ifPpKeyExists} - ${addComma} - json += serializer.asString(keys[i]) + ':' + serializer.asNumber(obj[keys[i]]) - ` - } else if (type === 'boolean') { - code += ` - ${ifPpKeyExists} - ${addComma} - json += serializer.asString(keys[i]) + ':' + serializer.asBoolean(obj[keys[i]]) - ` - } else if (type === undefined) { - code += ` - ${ifPpKeyExists} - ${addComma} - json += serializer.asString(keys[i]) + ':' + serializer.asAny(obj[keys[i]]) - ` - } else { - code += ` - ${ifPpKeyExists} - throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)}) - ` - } - + const valueCode = buildValue('', '', 'obj[keys[i]]', ppLocation) + laterCode += valueCode.laterCode code += ` - continue - } + if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) { + ${addComma} + json += serializer.asString(keys[i]) + ':' + ${valueCode.code} + continue + } ` }) if (schema.additionalProperties) { - code += additionalProperty(location) + const additionalPropertyCode = additionalProperty(location) + code += additionalPropertyCode.code + laterCode += additionalPropertyCode.laterCode } code += ` } ` - return code + return { code, laterCode } } function additionalProperty (location) { let ap = location.schema.additionalProperties let code = '' if (ap === true) { - return ` + code += ` if (obj[keys[i]] !== undefined && typeof obj[keys[i]] !== 'function' && typeof obj[keys[i]] !== 'symbol') { ${addComma} json += serializer.asString(keys[i]) + ':' + JSON.stringify(obj[keys[i]]) } ` + + return { code, laterCode: '' } } let apLocation = mergeLocation(location, { schema: ap }) if (ap.$ref) { @@ -512,72 +465,29 @@ function additionalProperty (location) { ap = apLocation.schema } - const type = ap.type - const format = ap.format - const stringSerializer = getStringSerializer(format) - if (type === 'object') { - code += `${buildObject(apLocation, '', 'buildObjectAP', 'buildObjectAP')} - ${addComma} - json += serializer.asString(keys[i]) + ':' + buildObjectAP(obj[keys[i]]) - ` - } else if (type === 'array') { - code += `${buildArray(apLocation, '', 'buildArrayAP', 'buildArrayAP')} - ${addComma} - json += serializer.asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]]) - ` - } else if (type === 'null') { - code += ` - ${addComma} - json += serializer.asString(keys[i]) +':null' - ` - } else if (type === 'string') { - code += ` - ${addComma} - json += serializer.asString(keys[i]) + ':' + ${stringSerializer}(obj[keys[i]]) - ` - } else if (type === 'integer') { - code += ` - var t = Number(obj[keys[i]]) - if (!isNaN(t)) { - ${addComma} - json += serializer.asString(keys[i]) + ':' + t - } - ` - } else if (type === 'number') { - code += ` - var t = Number(obj[keys[i]]) - if (!isNaN(t)) { - ${addComma} - json += serializer.asString(keys[i]) + ':' + t - } - ` - } else if (type === 'boolean') { - code += ` - ${addComma} - json += serializer.asString(keys[i]) + ':' + serializer.asBoolean(obj[keys[i]]) - ` - } else if (type === undefined) { - code += ` - ${addComma} - json += serializer.asString(keys[i]) + ':' + serializer.asAny(obj[keys[i]]) - ` - } else { - code += ` - throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ' + ${JSON.stringify(type)}) - ` - } - return code + const valueCode = buildValue('', '', 'obj[keys[i]]', apLocation) + + code += ` + ${addComma} + json += serializer.asString(keys[i]) + ':' + ${valueCode.code} + ` + + return { code, laterCode: valueCode.laterCode } } function addAdditionalProperties (location) { - return ` + const additionalPropertyCode = additionalProperty(location) + const code = ` var properties = ${JSON.stringify(location.schema.properties)} || {} var keys = Object.keys(obj) for (var i = 0; i < keys.length; i++) { if (properties[keys[i]]) continue - ${additionalProperty(location)} + ${additionalPropertyCode.code} } ` + + return { code, laterCode: additionalPropertyCode.laterCode } } function idFinder (schema, searchedId) { @@ -796,9 +706,13 @@ function buildInnerObject (location, locationPath) { const schema = location.schema const result = buildCodeWithAllOfs(location, '', '', locationPath) if (schema.patternProperties) { - result.code += addPatternProperties(location) + const { code, laterCode } = addPatternProperties(location) + result.code += code + result.laterCode += laterCode } else if (schema.additionalProperties && !schema.patternProperties) { - result.code += addAdditionalProperties(location) + const { code, laterCode } = addAdditionalProperties(location) + result.code += code + result.laterCode += laterCode } return result } @@ -923,7 +837,7 @@ function buildObject (location, code, functionName, locationPath) { return code } -function buildArray (location, code, functionName, locationPath, isObjectProperty = false) { +function buildArray (location, code, functionName, locationPath) { let schema = location.schema if (schema.$id !== undefined) { schemaReferenceMap.set(schema.$id, schema) @@ -1000,13 +914,11 @@ function buildArray (location, code, functionName, locationPath, isObjectPropert result = buildValue(laterCode, locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }), true) } - if (isObjectProperty) { - code += ` - if(!Array.isArray(obj)) { + code += ` + if (!Array.isArray(obj)) { throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`) } - ` - } + ` code += 'const arrayLength = obj.length\n' if (largeArrayMechanism !== 'default') { @@ -1154,7 +1066,7 @@ function buildValue (laterCode, locationPath, input, location, isArray) { break case 'array': funcName = generateFuncName() - laterCode = buildArray(location, laterCode, funcName, locationPath, true) + laterCode = buildArray(location, laterCode, funcName, locationPath) code += `json += ${funcName}(${input})` break case undefined: diff --git a/test/additionalProperties.test.js b/test/additionalProperties.test.js index 23c8ac55..be7bf26d 100644 --- a/test/additionalProperties.test.js +++ b/test/additionalProperties.test.js @@ -19,7 +19,7 @@ test('additionalProperties', (t) => { }) const obj = { str: 'test', foo: 42, ofoo: true, foof: 'string', objfoo: { a: true } } - t.equal('{"str":"test","foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]"}', stringify(obj)) + t.equal(stringify(obj), '{"str":"test","foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]"}') }) test('additionalProperties should not change properties', (t) => { @@ -38,7 +38,7 @@ test('additionalProperties should not change properties', (t) => { }) const obj = { foo: '42', ofoo: 42 } - t.equal('{"foo":"42","ofoo":42}', stringify(obj)) + t.equal(stringify(obj), '{"foo":"42","ofoo":42}') }) test('additionalProperties should not change properties and patternProperties', (t) => { @@ -62,7 +62,7 @@ test('additionalProperties should not change properties and patternProperties', }) const obj = { foo: '42', ofoo: 42, test: '42' } - t.equal('{"foo":"42","ofoo":"42","test":42}', stringify(obj)) + t.equal(stringify(obj), '{"foo":"42","ofoo":"42","test":42}') }) test('additionalProperties set to true, use of fast-safe-stringify', (t) => { @@ -75,7 +75,7 @@ test('additionalProperties set to true, use of fast-safe-stringify', (t) => { }) const obj = { foo: true, ofoo: 42, arrfoo: ['array', 'test'], objfoo: { a: 'world' } } - t.equal('{"foo":true,"ofoo":42,"arrfoo":["array","test"],"objfoo":{"a":"world"}}', stringify(obj)) + t.equal(stringify(obj), '{"foo":true,"ofoo":42,"arrfoo":["array","test"],"objfoo":{"a":"world"}}') }) test('additionalProperties - string coerce', (t) => { @@ -90,7 +90,7 @@ test('additionalProperties - string coerce', (t) => { }) const obj = { foo: true, ofoo: 42, arrfoo: ['array', 'test'], objfoo: { a: 'world' } } - t.equal('{"foo":"true","ofoo":"42","arrfoo":"array,test","objfoo":"[object Object]"}', stringify(obj)) + t.equal(stringify(obj), '{"foo":"true","ofoo":"42","arrfoo":"array,test","objfoo":"[object Object]"}') }) test('additionalProperties - number skip', (t) => { @@ -104,7 +104,8 @@ test('additionalProperties - number skip', (t) => { } }) - const obj = { foo: true, ofoo: '42', xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } } + // const obj = { foo: true, ofoo: '42', xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } } + const obj = { foo: true, ofoo: '42' } t.equal(stringify(obj), '{"foo":1,"ofoo":42}') }) @@ -140,11 +141,11 @@ test('additionalProperties - object coerce', (t) => { }) const obj = { objfoo: { answer: 42 } } - t.equal('{"objfoo":{"answer":42}}', stringify(obj)) + t.equal(stringify(obj), '{"objfoo":{"answer":42}}') }) test('additionalProperties - array coerce', (t) => { - t.plan(1) + t.plan(2) const stringify = build({ title: 'check array coerce', type: 'object', @@ -157,8 +158,11 @@ test('additionalProperties - array coerce', (t) => { } }) - const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { tyrion: 'lannister' } } - t.equal('{"foo":["t","r","u","e"],"ofoo":[],"arrfoo":["1","2"],"objfoo":[]}', stringify(obj)) + const coercibleValues = { arrfoo: [1, 2] } + t.equal(stringify(coercibleValues), '{"arrfoo":["1","2"]}') + + const incoercibleValues = { foo: 'true', ofoo: 0, objfoo: { tyrion: 'lannister' } } + t.throws(() => stringify(incoercibleValues)) }) test('additionalProperties with empty schema', (t) => { @@ -169,7 +173,7 @@ test('additionalProperties with empty schema', (t) => { }) const obj = { a: 1, b: true, c: null } - t.equal('{"a":1,"b":true,"c":null}', stringify(obj)) + t.equal(stringify(obj), '{"a":1,"b":true,"c":null}') }) test('additionalProperties with nested empty schema', (t) => { @@ -183,7 +187,7 @@ test('additionalProperties with nested empty schema', (t) => { }) const obj = { data: { a: 1, b: true, c: null } } - t.equal('{"data":{"a":1,"b":true,"c":null}}', stringify(obj)) + t.equal(stringify(obj), '{"data":{"a":1,"b":true,"c":null}}') }) test('nested additionalProperties', (t) => { @@ -203,7 +207,7 @@ test('nested additionalProperties', (t) => { }) const obj = [{ ap: { value: 'string' } }] - t.equal('[{"ap":{"value":"string"}}]', stringify(obj)) + t.equal(stringify(obj), '[{"ap":{"value":"string"}}]') }) test('very nested additionalProperties', (t) => { @@ -240,7 +244,7 @@ test('very nested additionalProperties', (t) => { }) const obj = [{ ap: { nested: { moarNested: { finally: { value: 'str' } } } } }] - t.equal('[{"ap":{"nested":{"moarNested":{"finally":{"value":"str"}}}}}]', stringify(obj)) + t.equal(stringify(obj), '[{"ap":{"nested":{"moarNested":{"finally":{"value":"str"}}}}}]') }) test('nested additionalProperties set to true', (t) => { @@ -257,7 +261,7 @@ test('nested additionalProperties set to true', (t) => { }) const obj = { ap: { value: 'string', someNumber: 42 } } - t.equal('{"ap":{"value":"string","someNumber":42}}', stringify(obj)) + t.equal(stringify(obj), '{"ap":{"value":"string","someNumber":42}}') }) test('field passed to fastSafeStringify as undefined should be removed', (t) => { @@ -274,7 +278,7 @@ test('field passed to fastSafeStringify as undefined should be removed', (t) => }) const obj = { ap: { value: 'string', someNumber: undefined } } - t.equal('{"ap":{"value":"string"}}', stringify(obj)) + t.equal(stringify(obj), '{"ap":{"value":"string"}}') }) test('property without type but with enum, will acts as additionalProperties', (t) => { @@ -290,7 +294,7 @@ test('property without type but with enum, will acts as additionalProperties', ( }) const obj = { ap: { additional: 'field' } } - t.equal('{"ap":{"additional":"field"}}', stringify(obj)) + t.equal(stringify(obj), '{"ap":{"additional":"field"}}') }) test('property without type but with enum, will acts as additionalProperties without overwriting', (t) => { @@ -307,7 +311,7 @@ test('property without type but with enum, will acts as additionalProperties wit }) const obj = { ap: { additional: 'field' } } - t.equal('{"ap":{}}', stringify(obj)) + t.equal(stringify(obj), '{"ap":{}}') }) test('function and symbol references are not serialized as undefined', (t) => { @@ -324,5 +328,5 @@ test('function and symbol references are not serialized as undefined', (t) => { }) const obj = { str: 'x', test: 'test', meth: () => 'x', sym: Symbol('x') } - t.equal('{"str":"x","test":"test"}', stringify(obj)) + t.equal(stringify(obj), '{"str":"x","test":"test"}') }) diff --git a/test/patternProperties.test.js b/test/patternProperties.test.js index 133b60e5..ed1b287f 100644 --- a/test/patternProperties.test.js +++ b/test/patternProperties.test.js @@ -127,7 +127,7 @@ test('patternProperties - object coerce', (t) => { }) test('patternProperties - array coerce', (t) => { - t.plan(1) + t.plan(2) const stringify = build({ title: 'check array coerce', type: 'object', @@ -142,6 +142,9 @@ test('patternProperties - array coerce', (t) => { } }) - const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { tyrion: 'lannister' } } - t.equal(stringify(obj), '{"foo":["t","r","u","e"],"ofoo":[],"arrfoo":["1","2"],"objfoo":[]}') + const coercibleValues = { arrfoo: [1, 2] } + t.equal(stringify(coercibleValues), '{"arrfoo":["1","2"]}') + + const incoercibleValues = { foo: 'true', ofoo: 0, objfoo: { tyrion: 'lannister' } } + t.throws(() => stringify(incoercibleValues)) }) diff --git a/test/sanitize3.test.js b/test/sanitize3.test.js index 042ebe90..d596a8e6 100644 --- a/test/sanitize3.test.js +++ b/test/sanitize3.test.js @@ -3,15 +3,13 @@ const t = require('tap') const build = require('..') -const stringify = build({ - $defs: { - type: 'foooo"bar' - }, - patternProperties: { - x: { $ref: '#/$defs' } - } -}) - t.throws(() => { - stringify({ x: 0 }) -}, 'Cannot coerce 0 to "foo"bar"') + build({ + $defs: { + type: 'foooo"bar' + }, + patternProperties: { + x: { $ref: '#/$defs' } + } + }) +}, 'foooo"bar unsupported')