diff --git a/package-lock.json b/package-lock.json index 8b891f9..8a933f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -366,6 +366,11 @@ "uri-js": "3.0.2" } }, + "ajv-errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz", + "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=" + }, "ajv-keywords": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.1.0.tgz", diff --git a/package.json b/package.json index e0d83ef..3ccb442 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "json-refs": "^3.0.4", "material-ui-pickers": "1.0.0-rc.9", "react": "16.3.2", + "ajv-errors": "^1.0.0", "react-dom": "^16.3.1", "react-redux": "^5.0.7", "recompose": "^0.27.1", diff --git a/src/models/ui-metaschema.ts b/src/models/ui-metaschema.ts index b2edfa6..1d9883c 100644 --- a/src/models/ui-metaschema.ts +++ b/src/models/ui-metaschema.ts @@ -1,5 +1,7 @@ import { JsonSchema7 } from '@jsonforms/core'; + export const uiMetaSchema: JsonSchema7 = { + '$schema': 'http://json-schema.org/draft-07/schema', 'type': 'object', '$id': '#root', 'properties': { @@ -71,10 +73,23 @@ export const uiMetaSchema: JsonSchema7 = { '$ref': '#/definitions/rule' } }, - 'required': [ - 'type', - 'scope' - ] + 'required': ['type', 'scope'], + 'additionalProperties': false, + 'errorMessage': { + 'properties': { + 'type': 'type should be equal to one of the allowed values', + 'scope': 'Control scope should match pattern "^#\\/properties\\/{1}"', + 'suggestion': 'Control suggestion should be array', + 'options': 'Control options should be object', + 'label': 'Control label should be string, boolean or label object' + }, + 'required': { + 'scope': 'Control should have an object property "scope"', + 'type': 'Control should have a string property "type"' + }, + 'additionalProperties': 'Control should not have properties ' + + 'other than type, label, scope, options, suggestion and rule' + } }, 'horizontallayout': { 'type': 'object', @@ -92,10 +107,18 @@ export const uiMetaSchema: JsonSchema7 = { '$ref': '#/definitions/rule' } }, - 'required': [ - 'type', - 'elements' - ] + 'required': ['type', 'elements'], + 'additionalProperties': false, + 'errorMessage': { + 'properties': { + 'type': 'type should be equal to one of the allowed values' + }, + 'required': { + 'elements': 'Layout should have an array property "elements"', + 'type': 'Layout should have a string property "type"' + }, + 'additionalProperties': 'Layout should not have properties other than type and elements' + } }, 'verticallayout': { 'type': 'object', @@ -137,7 +160,19 @@ export const uiMetaSchema: JsonSchema7 = { 'required': [ 'type', 'elements' - ] + ], + 'additionalProperties': false, + 'errorMessage': { + 'properties': { + 'type': 'type should be equal to one of the allowed values' + }, + 'required': { + 'elements': 'Categorization should have an array property "elements"', + 'type': 'Categorization should have a string property "type"' + }, + 'additionalProperties': 'Categorization should not have properties ' + + 'other than type and elements' + } }, 'category': { 'type': 'object', @@ -158,10 +193,19 @@ export const uiMetaSchema: JsonSchema7 = { '$ref': '#/definitions/rule' } }, - 'required': [ - 'type', - 'elements' - ] + 'additionalProperties': false, + 'errorMessage': { + 'properties': { + 'type': 'type should be equal to one of the allowed values', + 'label': 'Category label should be string' + }, + 'required': { + 'type': 'Category layout should have a string property "type"', + 'elements': 'Category layout should have an array property "elements"' + }, + 'additionalProperties': 'Category layout should not have properties ' + + 'other than type,elements and label' + } }, 'group': { 'type': 'object', @@ -179,11 +223,21 @@ export const uiMetaSchema: JsonSchema7 = { 'type': 'string' } }, - 'required': [ - 'type', - 'elements', - 'label' - ] + 'required': ['type', 'elements', 'label'], + 'additionalProperties': false, + 'errorMessage': { + 'properties': { + 'type': 'type should be equal to one of the allowed values', + 'label': 'Group label should be string' + }, + 'required': { + 'type': 'Group layout should have a string property "type"', + 'elements': 'Group layout should have an array property "elements"', + 'label': 'Group layout should have a string property "label"' + }, + 'additionalProperties': 'Group layout should not have properties ' + + 'other than type,elements and label' + } }, 'rule': { 'type': 'object', @@ -228,6 +282,12 @@ export const uiMetaSchema: JsonSchema7 = { 'dependencies': { 'effect': ['condition'], 'condition': ['effect'] + }, + 'errorMessage': { + 'dependencies': { + 'effect': 'Effect has to de defined', + 'condition': 'Condition has to be defined', + } } }, 'scope': { @@ -244,5 +304,18 @@ export const uiMetaSchema: JsonSchema7 = { 'required': [ 'elements', 'type' - ] + ], + 'additionalProperties': false, + 'errorMessage': { + 'properties': { + 'type': 'Root type should be equal to one of the allowed values', + 'label': 'Root label should be string' + }, + 'required': { + 'elements': 'Root should have an array property "elements"', + 'type': 'Root should have a string property "type"' + }, + 'additionalProperties': 'Root should not have properties ' + + 'other than type, elements, label and rule' + } }; diff --git a/src/validation.ts b/src/validation.ts index 6b8f062..200784b 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -1,19 +1,20 @@ import * as AJV from 'ajv'; import { ErrorObject } from 'ajv'; import { JsonSchema7 } from '@jsonforms/core'; +import * as AjvErrors from 'ajv-errors'; // tslint:disable-line -const ajv = new AJV({allErrors: true, verbose: true}); +const ajv = new AJV({allErrors: true, verbose: true, jsonPointers: true}); +AjvErrors(ajv); interface ValidationErrors { message: string; schemaPath?: string; } -const extractErrors = (errors: ErrorObject[]): ValidationErrors[] => +const extractErrors = (errors: ErrorObject[]): ValidationErrors[] => errors.map((error: ErrorObject) => { return { message: error.message, - schemaPath: error.schemaPath }; }); diff --git a/test/uischemaValidation.test.ts b/test/uischemaValidation.test.ts index 73e62b9..a506597 100644 --- a/test/uischemaValidation.test.ts +++ b/test/uischemaValidation.test.ts @@ -175,9 +175,7 @@ test('invalid control element, layout label must be string', () => { elements: [ { type: 'Control', - label: { - label: 'Occupation' - }, + label: ['Occupation'], scope: '#/properties/occupation' } ] @@ -226,12 +224,6 @@ test('invalid vertical layout, missing layout elements', () => { expect(errors).not.toEqual([]); }); -test('invalid layout, empty uischema', () => { - const uischema = {}; - const errors = validator(uischema); - expect(errors).not.toEqual([]); -}); - test('invalid layout type', () => { const uischema = { type: 'TestLayout', @@ -318,6 +310,27 @@ test('invalid control element, options of Control must be type of object', () => expect(errors).not.toEqual([]); }); +test('invalid control element, invalid suggestion type', () => { + const uischema = { + type: 'HorizontalLayout', + elements: [ + { + type: 'Control', + label: 'Occupation', + scope: '#/properties/occupation', + suggestion: { + Accountant: 'Accountant', + Engineer: 'Engineer', + Freelancer: 'Freelancer', + Journalism: 'Journalism' + } + } + ] + }; + const errors = validator(uischema); + expect(errors).not.toEqual([]); +}); + test('invalid rule, missing rule effect', () => { const uischema = { type: 'HorizontalLayout', @@ -470,6 +483,29 @@ test('invalid rule, condition type must be LEAF', () => { expect(errors).not.toEqual([]); }); +test('invalid rule, invalid effect value', () => { + const uischema = { + type: 'HorizontalLayout', + elements: [ + { + type: 'Control', + label: 'Occupation', + scope: '#/properties/occupation', + rule: { + effect: 'Test', + condition: { + type: 'LEAF', + scope: '#/properties/alive', + expectedValue: true + } + } + } + ] + }; + const errors = validator(uischema); + expect(errors).not.toEqual([]); +}); + test('invalid rule, expected value of condition must be string,integer,number or boolean', () => { const uischema = { type: 'HorizontalLayout', @@ -559,3 +595,18 @@ test('invalid group layout, Group layout must have label', () => { const errors = validator(uischema); expect(errors).not.toEqual([]); }); + +test('invalid control element, custom error message for missing scope', () => { + const uischema = { + type: 'HorizontalLayout', + elements: [ + { + type: 'Control', + label: 'Occupation', + } + ] + }; + const errors = validator(uischema); + expect(errors).not.toEqual([]); + expect(errors[0].message).toEqual(`Control should have an object property "scope"`); +});