From a9cd8b063e73af6f9f220eb9e2bccb6d73caec8c Mon Sep 17 00:00:00 2001 From: Tong Li Date: Mon, 17 Sep 2018 14:34:45 +1000 Subject: [PATCH] adds decorated data path (#28) * adds decorated data path * keep standard json pointer path * migrate tests * remove integration test --- .gitignore | 1 + package.json | 2 +- src/json/get-decorated-data-pah.js | 36 +++++++++++++++++++ src/json/index.js | 1 + .../__tests__/__snapshots__/main.js.snap | 6 +++- src/validation-errors/additional-prop.js | 5 ++- src/validation-errors/base.js | 7 +++- src/validation-errors/default.js | 3 +- src/validation-errors/enum.js | 15 ++++++-- src/validation-errors/required.js | 5 +-- 10 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 src/json/get-decorated-data-pah.js diff --git a/.gitignore b/.gitignore index dc9a215f..3744e9fb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /node_modules/ /coverage/ *.log +.vscode diff --git a/package.json b/package.json index 6526a1ed..230a2ac2 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ }, "jest": { "testMatch": [ - "/src/**/__tests__/*.js" + "/src/**/__tests__/**/*.js" ] }, "prettier": { diff --git a/src/json/get-decorated-data-pah.js b/src/json/get-decorated-data-pah.js new file mode 100644 index 00000000..8a0424a4 --- /dev/null +++ b/src/json/get-decorated-data-pah.js @@ -0,0 +1,36 @@ +export default function getDecoratedDataPath(jsonAst, dataPath) { + // TODO: Handle json pointer escape notation and better error handling + const pointers = dataPath.split('/').slice(1); + let decoratedPath = ''; + pointers.reduce((obj, pointer, idx) => { + switch (obj.type) { + case 'Object': { + decoratedPath += `/${pointer}`; + const filtered = obj.children.filter( + child => child.key.value === pointer + ); + if (filtered.length !== 1) { + throw new Error(`Couldn't find property ${pointer} of ${dataPath}`); + } + return filtered[0].value; + } + case 'Array': + decoratedPath += `/${pointer}${getTypeName(obj.children[pointer])}`; + return obj.children[pointer]; + default: + // eslint-disable-next-line no-console + console.log(obj); + } + }, jsonAst); + return decoratedPath; +} + +function getTypeName(obj) { + const type = obj.children.filter(child => child.key.value === 'type'); + + if (!type.length) { + return ''; + } + + return (type[0].value && `:${type[0].value.value}`) || ''; +} diff --git a/src/json/index.js b/src/json/index.js index 595c6b20..e79417e6 100644 --- a/src/json/index.js +++ b/src/json/index.js @@ -1 +1,2 @@ export { default as getMetaFromPath } from './get-meta-from-path'; +export { default as getDecoratedDataPath } from './get-decorated-data-pah'; diff --git a/src/validation-errors/__tests__/__snapshots__/main.js.snap b/src/validation-errors/__tests__/__snapshots__/main.js.snap index 7838b3ab..e897c3f6 100644 --- a/src/validation-errors/__tests__/__snapshots__/main.js.snap +++ b/src/validation-errors/__tests__/__snapshots__/main.js.snap @@ -8,7 +8,8 @@ Array [ "line": 1, "offset": 26, }, - "error": "Property baz is not expected to be here", + "error": " Property baz is not expected to be here", + "path": "", "start": Object { "column": 22, "line": 1, @@ -27,6 +28,7 @@ Array [ "offset": 24, }, "error": "/id: type should be number", + "path": "/id", "start": Object { "column": 7, "line": 1, @@ -45,6 +47,7 @@ Array [ "offset": 11, }, "error": "/id should be equal to one of the allowed values: foo, bar", + "path": "/id", "start": Object { "column": 7, "line": 1, @@ -59,6 +62,7 @@ exports[`Main should support js output format for required errors 1`] = ` Array [ Object { "error": "/nested should have required property 'id'", + "path": "/nested", "start": Object { "column": 11, "line": 1, diff --git a/src/validation-errors/additional-prop.js b/src/validation-errors/additional-prop.js index 08c0b881..9dc5e6f8 100644 --- a/src/validation-errors/additional-prop.js +++ b/src/validation-errors/additional-prop.js @@ -26,7 +26,10 @@ export default class AdditionalPropValidationError extends BaseValidationError { return { ...this.getLocation(`${dataPath}/${params.additionalProperty}`), - error: `Property ${params.additionalProperty} is not expected to be here`, + error: `${this.getDecoratedPath(dataPath)} Property ${ + params.additionalProperty + } is not expected to be here`, + path: dataPath, }; } } diff --git a/src/validation-errors/base.js b/src/validation-errors/base.js index c1a9d5c1..4a890d62 100644 --- a/src/validation-errors/base.js +++ b/src/validation-errors/base.js @@ -1,5 +1,5 @@ import { codeFrameColumns } from '@babel/code-frame'; -import { getMetaFromPath } from '../json'; +import { getMetaFromPath, getDecoratedDataPath } from '../json'; export default class BaseValidationError { constructor( @@ -26,6 +26,11 @@ export default class BaseValidationError { }; } + getDecoratedPath(dataPath = this.options.dataPath) { + const decoratedPath = getDecoratedDataPath(this.jsonAst, dataPath); + return decoratedPath; + } + getCodeFrame(message, dataPath = this.options.dataPath) { return codeFrameColumns(this.jsonRaw, this.getLocation(dataPath), { highlightCode: true, diff --git a/src/validation-errors/default.js b/src/validation-errors/default.js index 2b4e4a8b..7239945e 100644 --- a/src/validation-errors/default.js +++ b/src/validation-errors/default.js @@ -16,7 +16,8 @@ export default class DefaultValidationError extends BaseValidationError { return { ...this.getLocation(), - error: `${dataPath}: ${keyword} ${message}`, + error: `${this.getDecoratedPath(dataPath)}: ${keyword} ${message}`, + path: dataPath, }; } } diff --git a/src/validation-errors/enum.js b/src/validation-errors/enum.js index 147700c0..2d9e9cbf 100644 --- a/src/validation-errors/enum.js +++ b/src/validation-errors/enum.js @@ -5,7 +5,10 @@ import BaseValidationError from './base'; export default class EnumValidationError extends BaseValidationError { print() { - const { message, params: { allowedValues } } = this.options; + const { + message, + params: { allowedValues }, + } = this.options; const bestMatch = this.findBestMatch(); const output = [ @@ -28,7 +31,10 @@ export default class EnumValidationError extends BaseValidationError { const output = { ...this.getLocation(), - error: `${dataPath} ${message}: ${params.allowedValues.join(', ')}`, + error: `${this.getDecoratedPath( + dataPath + )} ${message}: ${params.allowedValues.join(', ')}`, + path: dataPath, }; if (bestMatch !== null) { @@ -39,7 +45,10 @@ export default class EnumValidationError extends BaseValidationError { } findBestMatch() { - const { dataPath, params: { allowedValues } } = this.options; + const { + dataPath, + params: { allowedValues }, + } = this.options; const currentValue = pointer.get(this.data, dataPath); if (!currentValue) { diff --git a/src/validation-errors/required.js b/src/validation-errors/required.js index 899b0b24..711814cb 100644 --- a/src/validation-errors/required.js +++ b/src/validation-errors/required.js @@ -19,11 +19,12 @@ export default class RequiredValidationError extends BaseValidationError { } getError() { - const { message, dataPath, params } = this.options; + const { message, dataPath } = this.options; return { ...this.getLocation(), - error: `${dataPath} ${message}`, + error: `${this.getDecoratedPath(dataPath)} ${message}`, + path: dataPath, }; } }