Skip to content

Commit

Permalink
Update ajv to version 7
Browse files Browse the repository at this point in the history
- mitigate removal of option 'errorDataPath: property' by appending the
property path to the error dataPath in the sanitizeErrors call. The
error message for missing required properties was adapted to refer to
the property and not its parent
- remove custom time validation and use format validations from
ajv-formats
- replace 'jsonPointers: true' with 'jsPropertySyntax: true' ajv option
as the former was removed
- remove json schema draft 04 from default ajv instance, as v7 no longer
supports it
- update imports and tests
  • Loading branch information
AlexandraBuzila committed Apr 22, 2021
1 parent 9b95669 commit 2af1bd1
Show file tree
Hide file tree
Showing 23 changed files with 1,072 additions and 896 deletions.
1,782 changes: 974 additions & 808 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
"@istanbuljs/nyc-config-typescript": "0.1.3",
"@types/jest": "^24.0.23",
"@types/lodash": "^4.14.149",
"ajv": "^6.10.2",
"ajv": "^7.0.0",
"ajv-formats": "^1.5.1",
"ava": "~2.4.0",
"babel-loader": "^8.0.6",
"coveralls": "^3.0.9",
"core-js": "^3.9.1",
"coveralls": "^3.0.9",
"cross-env": "^7.0.2",
"css-loader": "^3.2.0",
"lcov-result-merger": "^3.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ describe('AutoComplete control Error Tests', () => {
{
dataPath: 'foo',
message: 'Hi, this is me, test error!',
params: '',
params: {},
keyword: '',
schemaPath: ''
}
Expand Down
2 changes: 1 addition & 1 deletion packages/angular-material/test/date-control.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ describe('Date control Error Tests', () => {
{
dataPath: 'foo',
message: 'Hi, this is me, test error!',
params: '',
params: {},
keyword: '',
schemaPath: ''
}
Expand Down
2 changes: 1 addition & 1 deletion packages/angular-test/src/boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export const booleanErrorTest = <C extends JsonFormsControl, I>(
message: 'Hi, this is me, test error!',
keyword: '',
schemaPath: '',
params: ''
params: {}
}
]));
formsService.refresh();
Expand Down
2 changes: 1 addition & 1 deletion packages/angular-test/src/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ export const numberErrorTest = <C extends JsonFormsControl>(
message: 'Hi, this is me, test error!',
keyword: '',
schemaPath: '',
params: ''
params: {}
}
]));
formsService.refresh();
Expand Down
2 changes: 1 addition & 1 deletion packages/angular-test/src/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ export const rangeErrorTest = <C extends JsonFormsControl, I>(
message: 'Hi, this is me, test error!',
keyword: '',
schemaPath: '',
params: ''
params: {}
}
]));
formsService.refresh();
Expand Down
2 changes: 1 addition & 1 deletion packages/angular-test/src/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export const textErrorTest = <C extends JsonFormsControl>(
message: 'Hi, this is me, test error!',
keyword: '',
schemaPath: '',
params: ''
params: {}
}
]));
formsService.refresh();
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/src/jsonforms-root.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges
} from '@angular/core';
import { Actions, JsonFormsRendererRegistryEntry, JsonSchema, UISchemaElement, UISchemaTester, ValidationMode } from '@jsonforms/core';
import { Ajv, ErrorObject } from 'ajv';
import Ajv, { ErrorObject } from 'ajv';
import { JsonFormsAngularService, USE_STATE_VALUE } from './jsonforms.service';
@Component({
selector: 'jsonforms',
Expand Down
2 changes: 1 addition & 1 deletion packages/angular/src/jsonforms.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { BehaviorSubject, Observable } from 'rxjs';
import { JsonFormsBaseRenderer } from './base.renderer';

import { cloneDeep } from 'lodash';
import { Ajv } from 'ajv';
import Ajv from 'ajv';

export const USE_STATE_VALUE = Symbol('Marker to use state value');
export class JsonFormsAngularService {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
},
"dependencies": {
"@types/json-schema": "^7.0.3",
"ajv": "^6.10.2",
"ajv": "^7.0.0",
"ajv-formats": "^1.5.1",
"json-schema-ref-parser": "7.1.3",
"lodash": "^4.17.15",
"uri-js": "^4.2.2",
Expand Down
16 changes: 8 additions & 8 deletions packages/core/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import AJV, { ErrorObject } from 'ajv';
import Ajv, { ErrorObject } from 'ajv';
import { JsonSchema, UISchemaElement } from '../';
import { generateDefaultUISchema, generateJsonSchema } from '../generators';

Expand Down Expand Up @@ -86,19 +86,19 @@ export interface InitAction {
data: any;
schema: JsonSchema;
uischema: UISchemaElement;
options?: InitActionOptions | AJV.Ajv;
options?: InitActionOptions | Ajv;
}

export interface UpdateCoreAction {
type: 'jsonforms/UPDATE_CORE';
data?: any;
schema?: JsonSchema;
uischema?: UISchemaElement;
options?: InitActionOptions | AJV.Ajv;
options?: InitActionOptions | Ajv;
}

export interface InitActionOptions {
ajv?: AJV.Ajv;
ajv?: Ajv;
refParserOptions?: RefParser.Options;
validationMode?: ValidationMode;
}
Expand All @@ -112,7 +112,7 @@ export const init = (
data: any,
schema: JsonSchema = generateJsonSchema(data),
uischema?: UISchemaElement,
options?: InitActionOptions | AJV.Ajv
options?: InitActionOptions | Ajv
) => ({
type: INIT,
data,
Expand All @@ -126,7 +126,7 @@ export const updateCore = (
data: any,
schema: JsonSchema,
uischema?: UISchemaElement,
options?: AJV.Ajv | InitActionOptions
options?: Ajv | InitActionOptions
): UpdateCoreAction => ({
type: UPDATE_CORE,
data,
Expand Down Expand Up @@ -159,10 +159,10 @@ export const unregisterDefaultData = (schemaPath: string) => ({

export interface SetAjvAction {
type: 'jsonforms/SET_AJV';
ajv: AJV.Ajv;
ajv: Ajv;
}

export const setAjv = (ajv: AJV.Ajv) => ({
export const setAjv = (ajv: Ajv) => ({
type: SET_AJV,
ajv
});
Expand Down
57 changes: 46 additions & 11 deletions packages/core/src/reducers/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import get from 'lodash/get';
import filter from 'lodash/filter';
import isEqual from 'lodash/isEqual';
import isFunction from 'lodash/isFunction';
import { Ajv, ErrorObject, ValidateFunction } from 'ajv';
import Ajv, { ErrorObject, ValidateFunction } from 'ajv';
import RefParser from 'json-schema-ref-parser';
import {
CoreActions,
Expand Down Expand Up @@ -57,19 +57,54 @@ const validate = (validator: ValidateFunction, data: any): ErrorObject[] => {
return validator.errors;
};

export const sanitizeErrors = (validator: ValidateFunction, data: any) => {
if (validator === alwaysValid) {
const getInvalidProperty = (error: ErrorObject): string | undefined => {
switch (error.keyword) {
case 'required':
case 'dependencies':
return error.params.missingProperty;
case 'additionalProperties':
return error.params.additionalProperty;
default:
return undefined;
}
};

export const sanitizeErrors = (
validator: ValidateFunction | undefined,
data: any
) => {
if (!validator) {
return [];
}
// check if we have an ajv v7 validator (schemaEnv was introduced in v7)
// FIXME is there a better way?
if (validator.schemaEnv) {
return validate(validator, data).map(error => {
const invalidProperty = getInvalidProperty(error);
if (invalidProperty) {
error.dataPath = `${error.dataPath}.${invalidProperty}`;
if (error.keyword === 'required') {
// change validation message to refer to the property
error.message = 'is a required property';
}
}
// remove '.' chars at the beginning of paths
error.dataPath = error.dataPath.replace(/^./, '');
// change array paths to dot notation (e.g. 'segment[0]' becomes 'segment.0')
error.dataPath = error.dataPath.replace(
/\[\d+\]/g,
m => '.' + m.match(/\d+/g)[0]
);
return error;
});
}
return validate(validator, data).map(error => {
error.dataPath = error.dataPath.replace(/\//g, '.').substr(1);

return error;
});
};

const alwaysValid: ValidateFunction = () => true;

export type ValidationMode = 'ValidateAndShow' | 'ValidateAndHide' | 'NoValidation';

export interface JsonFormsCore {
Expand All @@ -88,7 +123,7 @@ const initState: JsonFormsCore = {
schema: {},
uischema: undefined,
errors: [],
validator: alwaysValid,
validator: undefined,
ajv: undefined,
refParserOptions: undefined,
validationMode: 'ValidateAndShow'
Expand Down Expand Up @@ -176,7 +211,7 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
const o = getRefParserOptions(state, action);

const validationMode = getValidationMode(state, action);
const v = validationMode === 'NoValidation' ? alwaysValid : thisAjv.compile(action.schema);
const v = validationMode === 'NoValidation' ? undefined : thisAjv.compile(action.schema);
const e = sanitizeErrors(v, action.data);

return {
Expand Down Expand Up @@ -205,7 +240,7 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
// revalidate only if necessary
validator =
validationMode === 'NoValidation'
? alwaysValid
? undefined
: thisAjv.compile(action.schema);
errors = sanitizeErrors(validator, action.data);
} else if (state.data !== action.data) {
Expand Down Expand Up @@ -237,7 +272,7 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
}
case SET_AJV: {
const currentAjv = action.ajv;
const validator = state.validationMode === 'NoValidation' ? alwaysValid : currentAjv.compile(state.schema);
const validator = state.validationMode === 'NoValidation' ? undefined : currentAjv.compile(state.schema);
const errors = sanitizeErrors(validator, state.data);
return {
...state,
Expand Down Expand Up @@ -303,10 +338,10 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
return state;
}
if (action.validationMode === 'NoValidation') {
const errors = sanitizeErrors(alwaysValid, state.data);
const errors = sanitizeErrors(undefined, state.data);
return {
...state,
validator: alwaysValid,
validator: undefined,
errors,
validationMode: action.validationMode
};
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import RefParser from 'json-schema-ref-parser';
import { cellReducer } from './cells';
import { configReducer } from './config';
import get from 'lodash/get';
import { Ajv } from 'ajv';
import Ajv from 'ajv';

export {
rendererReducer,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
toDataPathSegments
} from './path';
import { isEnabled, isVisible } from './runtime';
import { Ajv } from 'ajv';
import Ajv from 'ajv';

export { createCleanLabel, createLabelDescriptionFrom } from './label';

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/util/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
} from '../models/uischema';
import { resolveData } from './resolvers';
import { composeWithUi } from './path';
import { Ajv } from 'ajv';
import Ajv from 'ajv';

const isOrCondition = (condition: Condition): condition is OrCondition =>
condition.type === 'OR';
Expand Down
13 changes: 5 additions & 8 deletions packages/core/src/util/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,17 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import AJV from 'ajv';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { Options } from 'ajv';
import { Draft4 } from '../models/draft4';

export const createAjv = (options?: Options) => {
const ajv = new AJV({
schemaId: 'auto',
const ajv = new Ajv({
allErrors: true,
jsonPointers: true,
errorDataPath: 'property',
jsPropertySyntax: true,
verbose: true,
...options
});
ajv.addFormat('time', '^([0-1][0-9]|2[0-3]):[0-5][0-9]$');
ajv.addMetaSchema(Draft4);
addFormats(ajv);
return ajv;
};
Loading

0 comments on commit 2af1bd1

Please sign in to comment.