Skip to content

Commit 9584e7e

Browse files
Integrate external errors in form
Add support for specifying external errors via `additionalErrors: ajv.ErrorObject[]` prop. The additional errors are merged with the validation errors and are supplied to the affected controls. The additional errors are not affected by the validationMode like the validation errors are and it is the responsability of the framework user to update the prop when applicable (e.g. on ValidationMode changes). Fixes #1926
1 parent 8e9c14d commit 9584e7e

File tree

9 files changed

+354
-20
lines changed

9 files changed

+354
-20
lines changed

packages/angular/src/jsonforms-root.component.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export class JsonForms implements OnChanges, OnInit {
4646
@Input() ajv: Ajv;
4747
@Input() config: any;
4848
@Input() i18n: JsonFormsI18nState;
49+
@Input() additionalErrors: ErrorObject[];
4950
@Output() errors = new EventEmitter<ErrorObject[]>();
5051

5152
private previousData:any;
@@ -64,7 +65,8 @@ export class JsonForms implements OnChanges, OnInit {
6465
uischema: this.uischema,
6566
schema: this.schema,
6667
ajv: this.ajv,
67-
validationMode: this.validationMode
68+
validationMode: this.validationMode,
69+
additionalErrors: this.additionalErrors
6870
},
6971
uischemas: this.uischemas,
7072
i18n: this.i18n,
@@ -128,14 +130,16 @@ export class JsonForms implements OnChanges, OnInit {
128130
const newValidationMode = changes.validationMode;
129131
const newAjv = changes.ajv;
130132
const newConfig = changes.config;
133+
const newAdditionalErrors = changes.additionalErrors;
131134

132-
if (newData || newSchema || newUiSchema || newValidationMode || newAjv) {
135+
if (newData || newSchema || newUiSchema || newValidationMode || newAjv || newAdditionalErrors) {
133136
this.jsonformsService.updateCoreState(
134137
newData ? newData.currentValue : USE_STATE_VALUE,
135138
newSchema ? newSchema.currentValue : USE_STATE_VALUE,
136139
newUiSchema ? newUiSchema.currentValue : USE_STATE_VALUE,
137140
newAjv ? newAjv.currentValue : USE_STATE_VALUE,
138-
newValidationMode ? newValidationMode.currentValue : USE_STATE_VALUE
141+
newValidationMode ? newValidationMode.currentValue : USE_STATE_VALUE,
142+
newAdditionalErrors ? newAdditionalErrors.currentValue : USE_STATE_VALUE
139143
);
140144
}
141145

packages/angular/src/jsonforms.service.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ import { BehaviorSubject, Observable } from 'rxjs';
4949
import { JsonFormsBaseRenderer } from './base.renderer';
5050

5151
import { cloneDeep } from 'lodash';
52-
import Ajv from 'ajv';
52+
import Ajv, { ErrorObject } from 'ajv';
5353

5454
export const USE_STATE_VALUE = Symbol('Marker to use state value');
5555
export class JsonFormsAngularService {
5656

5757
private _state: JsonFormsSubStates;
5858
private state: BehaviorSubject<JsonFormsState>;
5959

60-
init(initialState: JsonFormsSubStates = { core: { data: undefined, schema: undefined, uischema: undefined, validationMode: 'ValidateAndShow' } }) {
60+
init(initialState: JsonFormsSubStates = { core: { data: undefined, schema: undefined, uischema: undefined, validationMode: 'ValidateAndShow', additionalErrors: undefined } }) {
6161
this._state = initialState;
6262
this._state.config = configReducer(undefined, setConfig(this._state.config));
6363
this._state.i18n = i18nReducer(this._state.i18n, updateI18n(this._state.i18n?.locale, this._state.i18n?.translate, this._state.i18n?.translateError));
@@ -209,15 +209,17 @@ export class JsonFormsAngularService {
209209
schema: JsonSchema | typeof USE_STATE_VALUE,
210210
uischema: UISchemaElement | typeof USE_STATE_VALUE,
211211
ajv: Ajv | typeof USE_STATE_VALUE,
212-
validationMode: ValidationMode | typeof USE_STATE_VALUE
212+
validationMode: ValidationMode | typeof USE_STATE_VALUE,
213+
additionalErrors: ErrorObject[] | typeof USE_STATE_VALUE,
213214
): void {
214215
const newData = data === USE_STATE_VALUE ? this._state.core.data : data;
215216
const newSchema = schema === USE_STATE_VALUE ? this._state.core.schema : schema ?? generateJsonSchema(newData);
216217
const newUischema = uischema === USE_STATE_VALUE ? this._state.core.uischema : uischema ?? generateDefaultUISchema(newSchema);
217218
const newAjv = ajv === USE_STATE_VALUE ? this._state.core.ajv : ajv;
218219
const newValidationMode = validationMode === USE_STATE_VALUE ? this._state.core.validationMode : validationMode;
220+
const newAdditionalErrors = additionalErrors === USE_STATE_VALUE ? this._state.core.additionalErrors : additionalErrors;
219221
this.updateCore(
220-
Actions.updateCore(newData, newSchema, newUischema, {ajv: newAjv, validationMode: newValidationMode})
222+
Actions.updateCore(newData, newSchema, newUischema, {ajv: newAjv, validationMode: newValidationMode, additionalErrors: newAdditionalErrors})
221223
);
222224
}
223225

packages/core/src/actions/actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export interface UpdateCoreAction {
100100
export interface InitActionOptions {
101101
ajv?: AJV;
102102
validationMode?: ValidationMode;
103+
additionalErrors?: ErrorObject[];
103104
}
104105

105106
export interface SetValidationModeAction {

packages/core/src/reducers/core.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export interface JsonFormsCore {
6565
schema: JsonSchema;
6666
uischema: UISchemaElement;
6767
errors?: ErrorObject[];
68+
additionalErrors?: ErrorObject[];
6869
validator?: ValidateFunction;
6970
ajv?: Ajv;
7071
validationMode?: ValidationMode;
@@ -78,6 +79,7 @@ const initState: JsonFormsCore = {
7879
validator: undefined,
7980
ajv: undefined,
8081
validationMode: 'ValidateAndShow',
82+
additionalErrors: []
8183
};
8284

8385
const reuseAjvForSchema = (ajv: Ajv, schema: JsonSchema): Ajv => {
@@ -133,6 +135,23 @@ const hasValidationModeOption = (option: any): option is InitActionOptions => {
133135
return false;
134136
};
135137

138+
const hasAdditionalErrorsOption = (option: any): option is InitActionOptions => {
139+
if (option) {
140+
return option.additionalErrors !== undefined;
141+
}
142+
return false;
143+
};
144+
145+
const getAdditionalErrors = (
146+
state: JsonFormsCore,
147+
action?: InitAction | UpdateCoreAction
148+
): ErrorObject[] => {
149+
if (action && hasAdditionalErrorsOption(action.options)) {
150+
return action.options.additionalErrors;
151+
}
152+
return state.additionalErrors;
153+
};
154+
136155
// tslint:disable-next-line: cyclomatic-complexity
137156
export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
138157
state = initState,
@@ -145,12 +164,14 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
145164
const validationMode = getValidationMode(state, action);
146165
const v = validationMode === 'NoValidation' ? undefined : thisAjv.compile(action.schema);
147166
const e = validate(v, action.data);
167+
const additionalErrors = getAdditionalErrors(state, action);
148168

149169
return {
150170
...state,
151171
data: action.data,
152172
schema: action.schema,
153173
uischema: action.uischema,
174+
additionalErrors,
154175
errors: e,
155176
validator: v,
156177
ajv: thisAjv,
@@ -176,6 +197,7 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
176197
} else if (state.data !== action.data) {
177198
errors = validate(validator, action.data);
178199
}
200+
const additionalErrors = getAdditionalErrors(state, action);
179201

180202
const stateChanged =
181203
state.data !== action.data ||
@@ -184,7 +206,8 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
184206
state.ajv !== thisAjv ||
185207
state.errors !== errors ||
186208
state.validator !== validator ||
187-
state.validationMode !== validationMode
209+
state.validationMode !== validationMode ||
210+
state.additionalErrors !== additionalErrors
188211
return stateChanged
189212
? {
190213
...state,
@@ -195,6 +218,7 @@ export const coreReducer: Reducer<JsonFormsCore, CoreActions> = (
195218
errors: isEqual(errors, state.errors) ? state.errors : errors,
196219
validator: validator,
197220
validationMode: validationMode,
221+
additionalErrors
198222
}
199223
: state;
200224
}
@@ -391,8 +415,11 @@ const getErrorsAt = (
391415
instancePath: string,
392416
schema: JsonSchema,
393417
matchPath: (path: string) => boolean
394-
) => (state: JsonFormsCore): ErrorObject[] =>
395-
errorsAt(instancePath, schema, matchPath)(state.validationMode === 'ValidateAndHide' ? [] : state.errors);
418+
) => (state: JsonFormsCore): ErrorObject[] => {
419+
const errors = state.errors ?? [];
420+
const additionalErrors = state.additionalErrors ?? [];
421+
return errorsAt(instancePath, schema, matchPath)(state.validationMode === 'ValidateAndHide' ? [...additionalErrors] : [...errors, ...additionalErrors]);
422+
}
396423

397424
export const errorAt = (instancePath: string, schema: JsonSchema) =>
398425
getErrorsAt(instancePath, schema, path => path === instancePath);

0 commit comments

Comments
 (0)