Skip to content

Commit 676eb01

Browse files
authored
Improve the debugging experience in case of a bad reference (#202)
* Improve the debugging experience in case of a bad reference * Updated test
1 parent 24d58f7 commit 676eb01

File tree

3 files changed

+229
-2
lines changed

3 files changed

+229
-2
lines changed

index.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var Ajv = require('ajv')
66
var merge = require('deepmerge')
77
var util = require('util')
88
var validate = require('./schema-validator')
9+
var stringSimilarity = null
910

1011
var uglify = null
1112
var isLong
@@ -490,6 +491,10 @@ function refFinder (ref, schema, externalSchema) {
490491
if (ref[0]) {
491492
schema = externalSchema[ref[0]]
492493

494+
if (schema === undefined) {
495+
findBadKey(externalSchema, [ref[0]])
496+
}
497+
493498
if (schema.$ref) {
494499
return refFinder(schema.$ref, schema, externalSchema)
495500
}
@@ -517,8 +522,30 @@ function refFinder (ref, schema, externalSchema) {
517522
}
518523
}
519524
}
520-
const result = (new Function('schema', code))(schema)
525+
var result
526+
try {
527+
result = (new Function('schema', code))(schema)
528+
} catch (err) {}
529+
530+
if (result === undefined) {
531+
findBadKey(schema, walk.slice(1))
532+
}
521533
return result.$ref ? refFinder(result.$ref, schema, externalSchema) : result
534+
535+
function findBadKey (obj, keys) {
536+
if (keys.length === 0) return null
537+
const key = sanitizeKey(keys.shift())
538+
if (obj[key] === undefined) {
539+
stringSimilarity = stringSimilarity || require('string-similarity')
540+
const { bestMatch } = stringSimilarity.findBestMatch(key, Object.keys(obj))
541+
if (bestMatch.rating >= 0.5) {
542+
throw new Error(`Cannot find reference '${key}', did you mean '${bestMatch.target}'?`)
543+
} else {
544+
throw new Error(`Cannot find reference '${key}'`)
545+
}
546+
}
547+
return findBadKey(obj[key], keys)
548+
}
522549
}
523550

524551
function sanitizeKey (key) {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
},
4949
"dependencies": {
5050
"ajv": "^6.10.2",
51-
"deepmerge": "^4.0.0"
51+
"deepmerge": "^4.0.0",
52+
"string-similarity": "^4.0.1"
5253
},
5354
"standard": {
5455
"ignore": [

test/ref.test.js

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,3 +876,202 @@ test('ref in definition with exact match', (t) => {
876876

877877
t.equal(output, '{"foo":"foo"}')
878878
})
879+
880+
test('Bad key', t => {
881+
t.test('Find match', t => {
882+
t.plan(1)
883+
try {
884+
build({
885+
definitions: {
886+
projectId: {
887+
type: 'object',
888+
properties: {
889+
id: { type: 'integer' }
890+
}
891+
}
892+
},
893+
type: 'object',
894+
properties: {
895+
data: {
896+
$ref: '#/definitions/porjectId'
897+
}
898+
}
899+
})
900+
t.fail('Should throw')
901+
} catch (err) {
902+
t.is(err.message, "Cannot find reference 'porjectId', did you mean 'projectId'?")
903+
}
904+
})
905+
906+
t.test('No match', t => {
907+
t.plan(1)
908+
try {
909+
build({
910+
definitions: {
911+
projectId: {
912+
type: 'object',
913+
properties: {
914+
id: { type: 'integer' }
915+
}
916+
}
917+
},
918+
type: 'object',
919+
properties: {
920+
data: {
921+
$ref: '#/definitions/foobar'
922+
}
923+
}
924+
})
925+
t.fail('Should throw')
926+
} catch (err) {
927+
t.is(err.message, "Cannot find reference 'foobar'")
928+
}
929+
})
930+
931+
t.test('Find match (external schema)', t => {
932+
t.plan(1)
933+
try {
934+
build({
935+
type: 'object',
936+
properties: {
937+
data: {
938+
$ref: 'external#/definitions/porjectId'
939+
}
940+
}
941+
}, {
942+
schema: {
943+
external: {
944+
definitions: {
945+
projectId: {
946+
type: 'object',
947+
properties: {
948+
id: { type: 'integer' }
949+
}
950+
}
951+
}
952+
}
953+
}
954+
})
955+
t.fail('Should throw')
956+
} catch (err) {
957+
t.is(err.message, "Cannot find reference 'porjectId', did you mean 'projectId'?")
958+
}
959+
})
960+
961+
t.test('No match (external schema)', t => {
962+
t.plan(1)
963+
try {
964+
build({
965+
type: 'object',
966+
properties: {
967+
data: {
968+
$ref: 'external#/definitions/foobar'
969+
}
970+
}
971+
}, {
972+
schema: {
973+
external: {
974+
definitions: {
975+
projectId: {
976+
type: 'object',
977+
properties: {
978+
id: { type: 'integer' }
979+
}
980+
}
981+
}
982+
}
983+
}
984+
})
985+
t.fail('Should throw')
986+
} catch (err) {
987+
t.is(err.message, "Cannot find reference 'foobar'")
988+
}
989+
})
990+
991+
t.test('Find match (external definitions typo)', t => {
992+
t.plan(1)
993+
try {
994+
build({
995+
type: 'object',
996+
properties: {
997+
data: {
998+
$ref: 'external#/deifnitions/projectId'
999+
}
1000+
}
1001+
}, {
1002+
schema: {
1003+
external: {
1004+
definitions: {
1005+
projectId: {
1006+
type: 'object',
1007+
properties: {
1008+
id: { type: 'integer' }
1009+
}
1010+
}
1011+
}
1012+
}
1013+
}
1014+
})
1015+
t.fail('Should throw')
1016+
} catch (err) {
1017+
t.is(err.message, "Cannot find reference 'deifnitions', did you mean 'definitions'?")
1018+
}
1019+
})
1020+
1021+
t.test('Find match (definitions typo)', t => {
1022+
t.plan(1)
1023+
try {
1024+
build({
1025+
definitions: {
1026+
projectId: {
1027+
type: 'object',
1028+
properties: {
1029+
id: { type: 'integer' }
1030+
}
1031+
}
1032+
},
1033+
type: 'object',
1034+
properties: {
1035+
data: {
1036+
$ref: '#/deifnitions/projectId'
1037+
}
1038+
}
1039+
})
1040+
t.fail('Should throw')
1041+
} catch (err) {
1042+
t.is(err.message, "Cannot find reference 'deifnitions', did you mean 'definitions'?")
1043+
}
1044+
})
1045+
1046+
t.test('Find match (external schema typo)', t => {
1047+
t.plan(1)
1048+
try {
1049+
build({
1050+
type: 'object',
1051+
properties: {
1052+
data: {
1053+
$ref: 'extrenal#/definitions/projectId'
1054+
}
1055+
}
1056+
}, {
1057+
schema: {
1058+
external: {
1059+
definitions: {
1060+
projectId: {
1061+
type: 'object',
1062+
properties: {
1063+
id: { type: 'integer' }
1064+
}
1065+
}
1066+
}
1067+
}
1068+
}
1069+
})
1070+
t.fail('Should throw')
1071+
} catch (err) {
1072+
t.is(err.message, "Cannot find reference 'extrenal', did you mean 'external'?")
1073+
}
1074+
})
1075+
1076+
t.end()
1077+
})

0 commit comments

Comments
 (0)