Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
32dfbbf
Merge pull request #1 from eclipsesource/master
gigibiffi84 Jun 5, 2024
66cc1a4
Merge branch 'eclipsesource:master' into master
gigibiffi84 Oct 20, 2025
af98e7d
Merge branch 'eclipsesource:master' into master
gigibiffi84 Oct 7, 2026
1339578
fix(core): store the new Ajv instance when handling setAjv
gigibiffi84 Oct 7, 2026
ea98a3c
feat(core): select combinator branches structurally instead of compil…
gigibiffi84 Oct 7, 2026
bb5d28c
feat(core): add a pluggable Form Validator next to Ajv
gigibiffi84 Oct 7, 2026
9af3568
Merge branch 'fix/set-ajv-stores-instance'
gigibiffi84 Oct 7, 2026
b57145a
Merge branch 'feat/structural-combinator-selection'
gigibiffi84 Oct 7, 2026
c222d9d
Merge branch 'feat/form-validator-bridge'
gigibiffi84 Oct 7, 2026
fb54f12
feat(core): evaluate rule conditions through the Form Validator
gigibiffi84 Oct 7, 2026
818a672
Merge branch 'feat/rules-via-form-validator'
gigibiffi84 Oct 7, 2026
f2acbc0
feat: expose the Form Validator in the React, Angular and Vue bindings
gigibiffi84 Oct 7, 2026
55c82ba
Merge branch 'feat/validator-prop-bindings'
gigibiffi84 Oct 7, 2026
b8f0dbc
feat(core): evaluate value constraints in validator-free rule conditions
gigibiffi84 Oct 7, 2026
a6eea18
Merge branch 'feat/schema-match-for-rules'
gigibiffi84 Oct 7, 2026
0552e3d
feat: add @jsonforms/standard-schema adapter (prototype)
gigibiffi84 Oct 7, 2026
6569b3f
Merge branch 'feat/standard-schema-adapter'
gigibiffi84 Oct 7, 2026
c5139b4
feat(core): create no default Ajv instance when a Form Validator is set
gigibiffi84 Oct 8, 2026
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