Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/angular-material/example/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const itemTester: UISchemaTester = (_schema, schemaPath, _path) => {
[(data)]="selectedExample.data"
[schema]="selectedExample.schema"
[uischema]="selectedExample.uischema"
[validator]="selectedExample.validator"
[renderers]="renderers"
[i18n]="i18n"
[readonly]="readonly"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
Category,
defaultJsonFormsI18nState,
deriveLabelForUISchemaElement,
getAjv,
getRuleValidator,
getConfig,
isVisible,
JsonFormsState,
Expand Down Expand Up @@ -93,7 +93,7 @@ export class CategorizationTabLayoutRenderer
category,
props.data,
undefined,
getAjv(state),
getRuleValidator(state),
getConfig(state)
)
);
Expand Down
4 changes: 2 additions & 2 deletions packages/angular-material/src/library/other/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/
import {
ControlElement,
getAjv,
getRuleValidator,
getConfig,
getData,
isVisible,
Expand All @@ -49,7 +49,7 @@ export const mapStateToVisible = (
ownProps.uischema,
getData(state),
undefined,
getAjv(state),
getRuleValidator(state),
getConfig(state)
);

Expand Down
10 changes: 9 additions & 1 deletion packages/angular/src/library/jsonforms-root.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
UISchemaElement,
UISchemaTester,
ValidationMode,
ValidatorOption,
} from '@jsonforms/core';
import type Ajv from 'ajv';
import type { ErrorObject } from 'ajv';
Expand Down Expand Up @@ -71,6 +72,7 @@ export class JsonForms implements DoCheck, OnChanges, OnInit, OnDestroy {
@Input() readonly: boolean;
@Input() validationMode: ValidationMode;
@Input() ajv: Ajv;
@Input() validator: ValidatorOption;
@Input() config: JsonFormsAngularConfig;
@Input() i18n: JsonFormsI18nState;
@Input() additionalErrors: ErrorObject[];
Expand All @@ -95,6 +97,7 @@ export class JsonForms implements DoCheck, OnChanges, OnInit, OnDestroy {
uischema: this.uischema,
schema: this.schema,
ajv: this.ajv,
validatorOption: this.validator,
validationMode: this.validationMode,
additionalErrors: this.additionalErrors,
},
Expand Down Expand Up @@ -160,6 +163,7 @@ export class JsonForms implements DoCheck, OnChanges, OnInit, OnDestroy {
const newReadonly = changes.readonly;
const newValidationMode = changes.validationMode;
const newAjv = changes.ajv;
const newValidator = changes.validator;
const newConfig = changes.config;
const newAdditionalErrors = changes.additionalErrors;

Expand All @@ -169,6 +173,7 @@ export class JsonForms implements DoCheck, OnChanges, OnInit, OnDestroy {
newUiSchema ||
newValidationMode ||
newAjv ||
newValidator ||
newAdditionalErrors
) {
this.jsonformsService.updateCoreState(
Expand All @@ -177,7 +182,10 @@ export class JsonForms implements DoCheck, OnChanges, OnInit, OnDestroy {
newUiSchema ? newUiSchema.currentValue : USE_STATE_VALUE,
newAjv ? newAjv.currentValue : USE_STATE_VALUE,
newValidationMode ? newValidationMode.currentValue : USE_STATE_VALUE,
newAdditionalErrors ? newAdditionalErrors.currentValue : USE_STATE_VALUE
newAdditionalErrors
? newAdditionalErrors.currentValue
: USE_STATE_VALUE,
newValidator ? newValidator.currentValue : USE_STATE_VALUE
);
}

Expand Down
9 changes: 8 additions & 1 deletion packages/angular/src/library/jsonforms.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
updateI18n,
Middleware,
defaultMiddleware,
ValidatorOption,
} from '@jsonforms/core';
import { BehaviorSubject, Observable } from 'rxjs';
import type { JsonFormsBaseRenderer } from './base.renderer';
Expand Down Expand Up @@ -293,7 +294,8 @@ export class JsonFormsAngularService {
uischema: UISchemaElement | typeof USE_STATE_VALUE,
ajv: Ajv | typeof USE_STATE_VALUE,
validationMode: ValidationMode | typeof USE_STATE_VALUE,
additionalErrors: ErrorObject[] | typeof USE_STATE_VALUE
additionalErrors: ErrorObject[] | typeof USE_STATE_VALUE,
validator: ValidatorOption | typeof USE_STATE_VALUE = USE_STATE_VALUE
): void {
const newData = data === USE_STATE_VALUE ? this._state.core.data : data;
const newSchema =
Expand All @@ -313,9 +315,14 @@ export class JsonFormsAngularService {
additionalErrors === USE_STATE_VALUE
? this._state.core.additionalErrors
: additionalErrors;
const newValidator =
validator === USE_STATE_VALUE
? this._state.core.validatorOption
: validator;
this.updateCore(
Actions.updateCore(newData, newSchema, newUischema, {
ajv: newAjv,
validator: newValidator,
validationMode: newValidationMode,
additionalErrors: newAdditionalErrors,
})
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import type AJV from 'ajv';
import type { ErrorObject } from 'ajv';
import type { ValidatorOption } from '../util/formValidator';
import { JsonSchema, UISchemaElement } from '../models';
import { generateDefaultUISchema, generateJsonSchema } from '../generators';

Expand Down Expand Up @@ -145,7 +146,15 @@ export interface UpdateCoreAction {
}

export interface InitActionOptions {
/** The AJV instance to validate with. Ignored for form validation when `validator` is set. */
ajv?: AJV;
/**
* A custom Form Validator: a factory producing one per schema, or a
* validator already bound to the schema. Takes precedence over `ajv`.
* Passing `undefined` explicitly switches back to AJV; leaving the key out
* keeps the validator currently in use.
*/
validator?: ValidatorOption;
validationMode?: ValidationMode;
additionalErrors?: ErrorObject[];
}
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/mappers/cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import type { JsonSchema } from '../models';
import {
AnyAction,
Dispatch,
getAjv,
getRuleValidator,
getConfig,
getData,
getErrorAt,
Expand Down Expand Up @@ -116,7 +116,13 @@ export const mapStateToCellProps = (
const visible =
ownProps.visible !== undefined
? ownProps.visible
: isVisible(uischema, rootData, undefined, getAjv(state), config);
: isVisible(
uischema,
rootData,
undefined,
getRuleValidator(state),
config
);

const rootSchema = getSchema(state);

Expand Down
54 changes: 17 additions & 37 deletions packages/core/src/mappers/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ import {
} from '../util';
import {
Translator,
getAjv,
getRuleValidator,
getCells,
getConfig,
getData,
Expand All @@ -84,6 +84,7 @@ import {
getUiSchema,
} from '../store';
import { isInherentlyEnabled, isInherentlyReadonly } from './util';
import { isStructuralMatch } from '../util/structural';
import { CombinatorKeyword } from './combinators';
import isEqual from 'lodash/isEqual';

Expand Down Expand Up @@ -596,7 +597,13 @@ export const mapStateToControlProps = (

const visible: boolean =
ownProps.visible === undefined || hasShowRule(uischema)
? isVisible(uischema, rootData, ownProps.path, getAjv(state), config)
? isVisible(
uischema,
rootData,
ownProps.path,
getRuleValidator(state),
config
)
: ownProps.visible;
const controlElement = uischema as ControlElement;
const id = ownProps.id;
Expand Down Expand Up @@ -1066,7 +1073,7 @@ export const mapStateToLayoutProps = (
ownProps.uischema,
rootData,
ownProps.path,
getAjv(state),
getRuleValidator(state),
getConfig(state)
)
: ownProps.visible;
Expand Down Expand Up @@ -1163,41 +1170,14 @@ export const mapStateToCombinatorRendererProps = (
const { data, schema, rootSchema, i18nKeyPrefix, label, ...props } =
mapStateToControlProps(state, ownProps);

const ajv = state.jsonforms.core.ajv;
const structuralKeywords = [
'required',
'additionalProperties',
'type',
'enum',
'const',
];
const dataIsValid = (errors: ErrorObject[]): boolean => {
return (
!errors ||
errors.length === 0 ||
!errors.find((e) => structuralKeywords.indexOf(e.keyword) !== -1)
);
};
// Pick the first branch whose structural keywords fit the data. This is a
// pure structural check (see isStructuralMatch): no validator instance and
// no code generation are involved, so it also works under a strict CSP.
let indexOfFittingSchema: number;
// TODO instead of compiling the combinator subschemas we can compile the original schema
// without the combinator alternatives and then revalidate and check the errors for the
// element
for (let i = 0; i < schema[keyword]?.length; i++) {
try {
let _schema = schema[keyword][i];
if (_schema.$ref) {
_schema = Resolve.schema(rootSchema, _schema.$ref, rootSchema);
}
const valFn = ajv.compile(_schema);
valFn(data);
if (dataIsValid(valFn.errors)) {
indexOfFittingSchema = i;
break;
}
} catch (error) {
console.debug(
"Combinator subschema is not self contained, can't hand it over to AJV"
);
if (isStructuralMatch(schema[keyword][i], data, rootSchema)) {
indexOfFittingSchema = i;
break;
}
}

Expand Down Expand Up @@ -1319,7 +1299,7 @@ export const mapStateToLabelProps = (
props.uischema,
getData(state),
props.path,
getAjv(state),
getRuleValidator(state),
getConfig(state)
)
: props.visible;
Expand Down
12 changes: 9 additions & 3 deletions packages/core/src/mappers/util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { JsonSchema, UISchemaElement } from '../models';
import { JsonFormsState, getAjv } from '../store';
import { JsonFormsState, getRuleValidator } from '../store';
import { hasEnableRule, hasReadonlyRule, isEnabled, isReadonly } from '../util';

/**
Expand All @@ -19,7 +19,13 @@ export const isInherentlyEnabled = (
return false;
}
if (uischema && hasEnableRule(uischema)) {
return isEnabled(uischema, rootData, ownProps?.path, getAjv(state), config);
return isEnabled(
uischema,
rootData,
ownProps?.path,
getRuleValidator(state),
config
);
}
if (!config?.separateReadonlyFromDisabled) {
if (typeof uischema?.options?.readonly === 'boolean') {
Expand Down Expand Up @@ -66,7 +72,7 @@ export const isInherentlyReadonly = (
uischema,
rootData,
ownProps?.path,
getAjv(state),
getRuleValidator(state),
config
);
}
Expand Down
Loading