Skip to content

feat(core): create no default Ajv instance when a Form Validator is set - #2648

Open
gigibiffi84 wants to merge 18 commits into
eclipsesource:masterfrom
gigibiffi84:feat/no-default-ajv-with-validator
Open

gigibiffi84 wants to merge 18 commits into
eclipsesource:masterfrom
gigibiffi84:feat/no-default-ajv-with-validator

Conversation

@gigibiffi84

Copy link
Copy Markdown

Part of #1498. Stacked on #2644 (Form Validator bridge) and the PRs after it; the diff shrinks to the last commit once those are merged.

What changed

When a validator option is configured and no ajv is passed, core no longer creates a default Ajv instance. state.jsonforms.core.ajv stays undefined, so nothing in the form (core, renderers, adopters' code) can compile schemas with an Ajv instance nobody configured.

  • getOrCreateAjv(state, action?, createDefault = true): new optional third parameter; INIT and UPDATE_CORE pass false while a custom validator is in effect. The return type is now Ajv | undefined.
  • An explicitly passed ajv is still stored. Switching back to Ajv (validator: undefined) creates the default instance as before.
  • vue-vuetify AdditionalProperties reads Ajv with useAjv(true), since it may legitimately be absent now.

Why not also import Ajv lazily

Measured on the built core: importing @jsonforms/core (with ajv and ajv-formats) and calling createAjv() perform zero new Function calls; only compile() does (4 on the first compile). So the instance itself never violated a CSP, and with a Form Validator configured core never compiles. A lazy import() would make the synchronous reducer API asynchronous, and the default path still needs Ajv, so bundlers would keep it either way. Removing Ajv from the bundle is the @jsonforms/validator-ajv split of 4.x.

For adopters

No change without the validator option. With it, code that reads core.ajv / getAjv(state) gets undefined unless an ajv is passed explicitly; rule helpers such as isVisible(..., getAjv(state), ...) keep working (they fall back to core's schema matcher), and getRuleValidator(state) is the intended replacement.

Tests

New core.test.ts cases: no instance across init, updateCore, setSchema and a validation-mode round trip; explicit ajv kept; switching back to Ajv creates the default; getOrCreateAjv with createDefault = false. Adapter end-to-end test asserts core.ajv is undefined. 574 core tests, 13 adapter tests, React (29) and Vue (4) binding tests and vue-vuetify additional/complex tests (17) green; core, the three bindings, the renderer sets, vue-vanilla, vue-vuetify, examples and the adapter build.

🤖 Generated with Claude Code

gigibiffi84 and others added 18 commits June 5, 2024 09:21
The SET_AJV reducer compiled the schema with the new Ajv instance but
never wrote it to state, so every later setSchema or re-enabled
validation silently went back to the previous instance.

Part of eclipsesource#1498

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ing them with Ajv

Combinator tab selection compiled every oneOf/anyOf/allOf branch with
Ajv and then ignored all errors except the structural keywords
required, additionalProperties, type, enum and const. The new
isStructuralMatch evaluates exactly those keywords itself, recursively
through properties, patternProperties, items, $ref, nested combinators
and if/then/else.

Tab selection therefore no longer depends on an Ajv instance or on
code generation, so it also works under a Content Security Policy
without unsafe-eval. Equivalence with the previous filtered-Ajv
behaviour is covered by tests comparing both approaches keyword by
keyword.

Part of eclipsesource#1498

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds the validation seam from issue eclipsesource#1498 as a non-breaking addition:
the `validator` init/updateCore option takes a FormValidatorFactory
`(schema) => FormValidator` or a FormValidator already bound to the
schema. The types mirror the FormValidator / ValidationIssue of the 4.x
presentation-model branch, restricted to synchronous results.

- The core reducer creates the Form Validator through the factory
  whenever the schema changes and caches it in `state.formValidator`;
  `state.validator` keeps holding the compiled Ajv function when Ajv
  validates, so existing consumers are unaffected.
- Issues from custom validators are converted to the Ajv error shape
  the rest of core and the renderers read (`issuesToErrors`): missing
  `key` becomes `custom`, `required` issues addressed to the missing
  property are split into parent path plus `params.missingProperty`,
  non-error severities are dropped and a missing `parentSchema` is
  resolved from the form schema.
- `createAjvValidator(ajv?)` is the built-in adapter;
  `compiledAjvValidator(validateFn)` wraps Ajv standalone code for CSP
  setups without unsafe-eval.
- `getValidator(state)` exposes the bound Form Validator.
- Switching to NoValidation via setValidationMode now also drops the
  cached validator, as init and updateCore already did.

Precedence: `validator` wins, otherwise `ajv`, otherwise the default
Ajv instance. Existing users notice nothing.

Part of eclipsesource#1498

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Resolved with the bridge's reducer (which already stores the Ajv
instance on setAjv), both util exports, and both groups of reducer
tests.
Schema based rule conditions were always evaluated with
ajv.validate(condition.schema, value), the last place where a custom
Form Validator did not apply.

- Widen the `ajv` parameter of isVisible, isEnabled, isReadonly, the
  eval* functions and Runtime.isVisible/isEnabled to RuleValidator: an
  Ajv instance (unchanged behaviour), a FormValidator (its `matches`,
  or the Structural Matcher when it has none), a FormValidatorFactory
  (one Form Validator per condition schema, cached) or undefined (the
  Structural Matcher alone).
- Add matchesConditionSchema and the getRuleValidator selector; core's
  own mappers pass getRuleValidator(state) instead of getAjv(state).

Part of eclipsesource#1498

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- React, Vue and Angular JsonForms gain a `validator` prop/input next to
  `ajv`, forwarded to init/updateCore; Vue adds useValidator();
  JsonFormsAngularService.updateCoreState takes an optional validator.
- Material and vanilla withAjvProps additionally inject `ruleValidator`
  (getRuleValidator), used by the categorization layouts; Angular
  Material evaluates category visibility with getRuleValidator too.
- ExampleDescription gains an optional `validator`, passed through by
  all five example apps; new "Custom validator" example with a
  dependency-free handwritten Form Validator and a schema-based rule.

Part of eclipsesource#1498

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Rule conditions evaluated without a validator (none configured, or a
custom Form Validator without `matches`) only knew the structural
keywords. The new isSchemaMatch extends the Structural Matcher with
minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf,
minLength, maxLength, pattern, minItems, maxItems, uniqueItems,
minProperties, maxProperties and not, mirroring Ajv's semantics, and
matchesConditionSchema uses it for both validator-free paths.

isStructuralMatch is unchanged, so combinator tab selection keeps
ignoring value keywords as before.

Part of eclipsesource#1498

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
New package to use any Standard Schema library (Valibot, Zod, ArkType)
as the JSON Forms validator: fromStandardSchema derives the JSON Schema
through Standard JSON Schema and returns a Form Validator validating
with the library itself, so no schema is compiled at runtime and forms
work under a CSP without unsafe-eval.

- Issue paths become JSON Pointers; Valibot, Zod and ArkType issue
  kinds map to JSON Schema keywords, others become `custom`.
- Asynchronous schemas are rejected with guidance (3.x is synchronous).
- New "Standard Schema (Valibot)" example in all five example apps and
  a serve-csp script to demonstrate the CSP case.
- examples rollup config: load .d.mts/.d.cts as empty modules, which
  rollup-plugin-typescript2 0.34 would otherwise parse as JavaScript.

Part of eclipsesource#1498

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a custom `validator` option is in effect and no `ajv` is passed,
INIT and UPDATE_CORE no longer create a default Ajv instance, so
`core.ajv` stays undefined and nothing can compile schemas with an
instance nobody configured. getOrCreateAjv gains an optional
`createDefault` parameter; an explicit `ajv` is still stored and
switching back to Ajv creates the default as before. vue-vuetify's
AdditionalProperties reads Ajv optionally.

Ajv is not imported lazily: measured, importing core and createAjv()
perform no `new Function` calls (only compile() does), and a lazy
import would make the synchronous reducer API asynchronous.

Part of eclipsesource#1498

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@netlify

netlify Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for jsonforms-examples ready!

Name Link
🔨 Latest commit c5139b4
🔍 Latest deploy log https://app.netlify.com/projects/jsonforms-examples/deploys/6ac723752676ad000803ea07
😎 Deploy Preview https://deploy-preview-2648--jsonforms-examples.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant