Skip to content

feat: server-side JS field validators for extended inputs (#158) - #163

Closed
romain-pm wants to merge 2 commits into
feat/extended-inputs-modulefrom
feat/js-field-validators
Closed

feat: server-side JS field validators for extended inputs (#158)#163
romain-pm wants to merge 2 commits into
feat/extended-inputs-modulefrom
feat/js-field-validators

Conversation

@romain-pm

@romain-pm romain-pm commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

What

Completes the server-side validation gap left open in #158: the extended field types now re-enforce their constraints on submission, in JavaScript, so a client that bypasses the browser cannot store out-of-range values.

Stacked on #162 (base branch feat/extended-inputs-module) — review/merge that first. The diff here is only the validation layer.

Cross-repo dependency ⚠️

Depends on a new javascript-modules SDK: Jahia/javascript-modules#686 (branch feature/js-server-extensions-sdk, stacked on feature/js-server-extensions). That branch exports a small, polyglot-free SDK (JSServerExtensionInvoker) so a third-party bundle can consume JS-declared server extensions and invoke their callbacks. That must be released before this can build/run against a stock engine.

How it works

  1. js-modules SDKJSServerExtensionInvoker.forEach(type, handler) runs within one pooled GraalVM context; Invoker.call(callable, args…) executes a JS function and converts the result to plain Java. No GraalVM/engine-internal types leak, so formidable-engine needs no polyglot dependency.
  2. formidable-engine — a new validateFieldsWithJs pipeline step (after validateRequired) loads each submitted field's node with a system session and runs the JS validators registered for its node type; a violation rejects the submission with FMDB-010. Fails closed on validator error; skipped entirely (graceful) if the SDK service is absent. FormFieldMetadataCollector now carries field node identifiers; JsFieldValidator is the bridge.
  3. formidable-extended-inputsregisterFormFieldValidator({ nodeType }, (node, value, locale) => …) (thin wrapper over server.registry.add), plus validators for rating (1..max integer), scale (min..max by step), switch (true/false), consent (true, required-aware).
registerFormFieldValidator({ nodeType: "fmdbext:rating" }, (node, value) => {
  const max = clamp(intProp(node, "maxValue", 5), 2, 10);
  const n = Number(value);
  if (!Number.isInteger(n) || n < 1 || n > max) return { message: `Rating must be 1..${max}.` };
});

Verification

Built, deployed (SDK engine + formidable) and driven end-to-end on a local Jahia 8.2.x by POSTing directly to /modules/formidable-engine/form-submit, bypassing the browser:

Case Result
Valid (incl. edges: rating 5, scale min, nps 0, switch false) ✅ accepted
rating 999, abc, 0, 3.5 ⛔ FMDB-010
scale (effort) 99, abc ⛔ FMDB-010
switch maybe ⛔ FMDB-010
consent yes ⛔ FMDB-010
nps 11, -1 ⛔ FMDB-010

149 formidable-engine unit tests pass, and 19 new Cypress e2e specs (see below) pass against a local instance.

Notes / follow-ups

  • This is the recommended "option 1" from the Request: additional field types (rating, consent, switch, scale/NPS) #158 discussion (a Formidable-owned submission-pipeline JS hook) — it keeps formidable-extended-inputs pure-JS/CND with no per-field Java.
  • The scale validator reads the node's own min/max/step; a scale rendered via the nps view but configured with custom bounds would validate against those bounds (documented edge case).
  • Cypress server-rejection specs included (security/42-extended-inputs-server-validation.cy.ts, 19 tests: valid + boundary accepted, forged rating/scale/switch/consent — incl. an off-step scale value — rejected with FMDB-010); formidable-extended-inputs added to the CI provisioning manifest. Messages are literal English (surfaced only as the opaque FMDB-010 code to visitors); {bundle.key} i18n could be wired if we want localized validator messages in logs.
  • SDK import is declared version-less (SNAPSHOT); pin once the js-modules SDK is released.

Re-enforces field constraints on form submission, in JavaScript, so a client
bypassing browser validation cannot store out-of-range values.

formidable-engine:
- depends on the javascript-modules SDK (JSServerExtensionInvoker), imported
  from the javascript-modules-engine bundle (feature/js-server-extensions-sdk)
- new validateFieldsWithJs pipeline step (after validateRequired): loads each
  submitted field's node (system session) and runs JS validators registered for
  its type; a violation rejects the submission with FMDB-010. Fails closed on
  validator error; skipped entirely when the SDK service is absent
- FormFieldMetadataCollector now carries field node identifiers
- JsFieldValidator bridges the pipeline to the SDK invoker

formidable-extended-inputs:
- registerFormFieldValidator(props, fn) JS API (server.registry.add)
- validators for rating (1..max int), scale (min..max by step), switch
  (true/false), consent (true, required-aware)

Verified end-to-end on Jahia 8.2.x: valid values (incl. edges) accepted; forged
rating/scale/switch/consent values rejected with FMDB-010; 149 engine tests green.
@github-actions

Copy link
Copy Markdown
Contributor

📝 Documentation Guidelines

Thank you for contributing to our documentation! To ensure your contributions meet our standards, please review these resources:

This comment is posted automatically when changes are detected in the docs/ folder.

…t values

New security spec (42-extended-inputs-server-validation.cy.ts) POSTs directly to
the submit servlet, bypassing the browser, and asserts:
- valid submissions (incl. boundary values) are accepted
- forged rating/scale/switch/consent values are rejected with FMDB-010,
  including an in-bounds but off-step scale value

Adds fmdbext node factories (extendedInputs fixtures) and installs
formidable-extended-inputs in the CI provisioning manifest.

19/19 passing against a local Jahia 8.2.x.
@hduchesne

Copy link
Copy Markdown
Member

Heads-up: the base branch of this PR (feat/extended-inputs-module, #162) was absorbed into #172 and closed as superseded, so this PR will need a retarget/rebase onto main once #172 merges.

A few things to know for that rebase — #172 also addressed the server-side validation gap, but at a different level:

Also note #172 shipped a few fixes on the fields your validators target (switch buttons mode no longer prechecks "No", rating DOM order) — none of them change the submitted value contract, so your validator logic should rebase cleanly.

@hduchesne
hduchesne deleted the branch feat/extended-inputs-module September 8, 2026 15:25
@hduchesne hduchesne closed this Sep 8, 2026
@hduchesne

Copy link
Copy Markdown
Member

Branch housekeeping: this draft is more than a hundred commits behind main since July. Do you plan to pick it up (a rebase would be the first step), or can we close it and keep the work reachable from the closed PR? No action from me until you say.

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.

2 participants