GSOC 26 Proof of Concept: add SchemaForm component for JSON Schema to Form UI#3274
GSOC 26 Proof of Concept: add SchemaForm component for JSON Schema to Form UI#3274DSingh0304 wants to merge 28 commits intoapache:masterfrom
Conversation
|
@Baoyuantop Is this approach aligned with the project's direction? |
|
This may require more time to discuss. |
Yes sir sure ... |
- Add SchemaForm component that renders forms from JSON Schema - Support for oneOf (conditional field groups) - Support for dependencies (conditional fields based on other values) - Reuses existing FormItem components (TextInput, Select, Switch, NumberInput) - Add demo page at /schema_form_demo for testing
Instead of globally disabling emptyObjectsCleaner which broke timeout validation, this fix uses a marker-based approach that only preserves empty objects in the plugins field. Other empty objects (like timeout) are still cleaned as expected. - Add producePreservePlugins to mark empty plugin configs before cleaning - Add produceRemovePreserveMarkers to restore them after cleaning - This prevents both bugs: plugins being dropped AND invalid timeout errors
- Add ArrayField component using useFieldArray for object arrays - Add string array support using TagsInput - Create validation.ts with AJV integration - Add createSchemaResolver for React Hook Form - Map AJV errors to user-friendly messages - Test with real proxy-rewrite plugin schema - Add tabs to demo page for schema comparison Addresses feedback from Issue apache#2986
e185529 to
fb8581c
Compare
Install the test stack needed for SchemaForm unit/component tests: - vitest ^4.0.18 (test runner, replaces jest) - @vitest/coverage-v8 (coverage via v8) - @testing-library/react, @testing-library/user-event (component testing) - @testing-library/jest-dom (custom matchers e.g. toBeInTheDocument) - jsdom (browser-like DOM for vitest)
- vitest.config.ts: jsdom env, include only src/**/*.test.*, @ alias, ~icons/* alias (stubs unplugin-icons), v8 coverage config - src/test/setup.ts: window.matchMedia + ResizeObserver stubs for Mantine, @testing-library/jest-dom matchers registered globally - src/test/utils.tsx: renderWithForm/FormWrapper helpers wrapping MantineProvider + react-hook-form FormProvider - src/test/__mocks__/Icon.tsx: null stub for ~icons/material-symbols/* used by ArrayField (unplugin-icons not available in jsdom) - .gitignore: exclude vitest coverage output directory
…SchemaField SchemaField now accepts an isEncrypted boolean prop. When true it renders FormItemPasswordInput (masked input with reveal toggle) instead of the default FormItemTextInput, supporting the APISIX-specific encrypt_fields extension. SchemaForm.renderProperties() reads schema.encrypt_fields and passes the flag down per-field automatically — no schema changes required by plugin authors. Also removes unused _required param from ArrayField (pre-existing lint error).
Adds two new sub-components to SchemaForm:
AnyOfFields
Handles JSON Schema anyOf the same way oneOf is handled — detects a
discriminator field via const-keyed branches, watches its value with
useWatch, and renders the matching branch's extra properties. In APISIX
plugin schemas anyOf and oneOf share the same discriminator convention.
IfThenElseFields
Handles JSON Schema Draft 7 if/then/else keywords. The if sub-schema is
compiled and evaluated against live form data using the AJV instance from
validation.ts on every render cycle. When it matches, the then schema's
properties are rendered; otherwise the else schema's properties are shown.
Uses useWatch({ control }) (all values, no name) so defaultValues are
available immediately on the first render before fields register.
SchemaForm.renderProperties() is extended to read the APISIX-specific
encrypt_fields extension and pass isEncrypted to each SchemaField.
validation.ts: fix pre-existing quote-style lint error (singlequote rule).
validation.test.ts — 14 pure unit tests (no DOM)
Tests validateWithSchema for required/type/minLength/maximum/pattern/
enum/minItems/nested paths, and createSchemaResolver happy+error paths.
SchemaField.test.tsx — 11 component tests
Verifies every schema type maps to the correct Mantine widget:
string→TextInput, integer/number→NumberInput (inputmode=decimal),
boolean→Switch, isEncrypted→PasswordInput, enum→Select (listbox),
array-of-strings→TagsInput, array-of-objects→ArrayField (Add Item btn),
nested object→Fieldset, and auto-formatted labels from snake_case names.
SchemaForm.test.tsx — 18 integration tests
Covers all conditional rendering patterns with real form state:
top-level properties, no-properties empty schema, encrypt_fields,
enum/nested objects, oneOf branch selection, anyOf branch selection,
dependencies with oneOf, if/then (condition matches),
if/else (condition fails), no-else schema, basePath prefixing, and
oneOf field isolation (two tests asserting mutual exclusion of branches).
Testing notes:
- Mantine v8 Select renders aria-haspopup=listbox, not role=combobox
- Mantine v8 NumberInput renders inputmode=decimal, not role=spinbutton
- Required field labels include an asterisk span — queries use /^Label/
regex to match despite the trailing asterisk in the accessible name
- Floating-ui dropdown interaction not testable in jsdom — interactive
oneOf switching is covered by the Playwright e2e suite instead
schema_form_demo.tsx
Two new tab panels added to the existing demo route (/schema_form_demo):
anyOf tab — limit-count plugin shape
Demonstrates AnyOfFields: selecting 'redis' shows Redis connection
fields; selecting 'local' shows the local policy rate-limit fields.
Mirrors the real limit-count plugin's Redis vs local backend split.
if/then/else tab — fault-injection plugin shape
Demonstrates IfThenElseFields: toggling 'Enable Abort' (boolean)
shows/hides the abort HTTP code and body fields in real time using
the AJV-evaluated if/const:true condition.
docs/en/schema-form.md
Developer guide covering:
- Supported JSON Schema keywords and their widget mappings
- How to add a new widget (step-by-step)
- SchemaForm component API (schema, basePath props)
- SchemaField prop table (name, schema, control, required, isEncrypted)
- AJV validation integration and createSchemaResolver usage
- Testing patterns with renderWithForm and the jsdom environment
- Architecture notes on conditional rendering strategies
Auto-generated by TanStack Router when schema_form_demo.tsx was added. Registers /schema_form_demo in FileRoutesByFullPath, FileRoutesByTo, and FileRoutesById.
…submission handlers
|
Hi @Baoyuantop @SkyeYoung I've submitted this PoC PR for this issue as part of my GSoC 2026 application journey. The branch adds anyOf, if/then/else, and encrypt_fields support to the existing SchemaForm, with 43 passing tests and an interactive demo at /schema_form_demo. A developer guide is included at schema-form.md. Happy to discuss any implementation decisions or adjust based on feedback. Thank you! |
…clusiveMinimum/Maximum - Replace fault-injection demo schema with limit-conn burst configuration, aligning the if/then/else tab with the plugin listed in the proposal table (APISIX-39 section 6: if/then/else → limit-conn, response-rewrite, ...) - demo now also exercises exclusiveMinimum (default_conn_delay must be > 0) - Wire schema.exclusiveMinimum / schema.exclusiveMaximum to NumberInput min/max in SchemaField.tsx so the widget UI hint reflects exclusive boundaries; AJV still enforces strict exclusion on submit
|
Hi @DSingh0304 I tested the SchemaForm demo locally from this PR branch. Steps to reproduce:
Expected behavior: Actual behavior: Example form data after switching: { It seems the previous Screenshot attached for reference. Let me know if you would like me to explore a potential fix. |
|
I also tested enabling It seems this behavior may be related to React Hook Form keeping unmounted fields in form state by default. |
|
Hi @suryaparua-official I have been testing the form myself and I came across these issue. I will resolve them soon, I have been busy. Thank you for pointing the issues out. They will be resolved soon. |
|
Hi @DSingh0304, I tested the SchemaForm demo and was able to reproduce the issue where switching between schema branches (oneOf / anyOf / if-then-else) retains stale field values in the form state. I worked on a fix for this by implementing branch-aware cleanup inside SchemaForm. Instead of relying only on Solution
Testing
I’d be happy to open a PR or contribute to the existing one if this approach aligns with the project direction. Please let me know your thoughts. |
Hey @Jaswanth-arjun I have fixed that issue in these recent commits, thank you for reporting the issue and i have added the test for it as well. I will update the PR description, proposal and screenshot and video proof as well. |
Thanks for the update! @DSingh0304 I also worked on this issue and implemented a similar branch-cleanup approach with additional testing locally. I’d be happy to help further improve this PR. Possible areas I can contribute:
Please let me know if I can help with anything here. |
Yes sure I will let you know if I need any help. |
|
This PR has been updated and these are the updates: 1. Alignment on Correctness (Form State Cleanup) with video proof attached in description.I have implemented a robust state management system using
2. Verifiable DeliverablesI’ve updated my proposal to bind each phase to these deliverables:
|
|
84aab84 to
56813fd
Compare






JSON Schema Form UI:
anyOf,if/then/else,patternProperties& Form State CleanupCloses #3315
Closes #2986
Summary
This PR is a proof of concept for the GSoC 2026 "Dynamic Plugin Configuration UI" project. It demonstrates a production-ready, recursive form engine that handles complex JSON Schema Draft 7 constructs.
Beyond adding new keywords, this update focuses on Correctness and Data Integrity, ensuring that the form state sent to the APISIX backend is always minimal and strictly adheres to the active schema branch.
What's Changed (Recent Fixes & Polish)
1. Automatic Form State Cleanup (High Priority)
Implemented
unregister()logic inOneOfFields,AnyOfFields, andIfThenElseFields.redisbackend to alocalbackend would leave "ghost" values (likeredis_host) in the final submit payload, potentially causing backend validation failures.2.
patternPropertiesSupport (Dynamic Key-Value Maps)Many APISIX plugins (like
proxy-rewriteorresponse-rewrite) require dynamic HTTP header configuration.PatternPropertiesFieldcomponent to handle schemas where keys are regex-based strings.serializePatternPropertiesutility to transform internal form state into the flat object structure required by the APISIX Admin API.3. Smart Label Formatting
Refined the
formatLabelutility to handle technical acronyms. Fields likeoauth_client_idorapi_key_urinow render professionally as "OAuth Client ID" and "API Key URI" instead of camel-cased defaults.4. Clean Build (Zero Warnings)
Resolved all linting errors and warnings across the codebase.
SchemaForm.tsxto resolve Fast Refresh issues.pnpm lintand--max-warnings=0.Core Engine Features
1.
anyOfconditional field groups (AnyOfFields)Automatically detects discriminator
constvalues and renders the active branch's fields reactively. Essential for thelimit-countplugin's backend selection.2.
if/then/elseconditional rendering (IfThenElseFields)Uses AJV to evaluate nested schemas against live form data. Powers toggles like "Enable Burst Mode" in the
limit-connplugin.3.
encrypt_fieldsmaskingAutomatically transforms
TextInputinto maskedPasswordInputfor sensitive keys likejwt-authsecrets.Tests - 50 Passing (0 Failures, 0 Warnings)
This PR includes the project's first comprehensive unit and integration test suite for
SchemaForm:validation.test.ts— AJAX schema resolution and logic.SchemaField.test.tsx— Widget mapping and labels.SchemaForm.test.tsx— Integration testing for conditional rendering and Form State Cleanup.Test Files 3 passed (3) Tests 50 passed (50) Duration ~2sInteractive Demo
Accessible at
http://localhost:5173/ui/schema_form_demo.Features a "Live Form Data" monitor that allows maintainers to verify that fields are purged from the JSON payload in real-time as branches are toggled.
Video Proof
Screencast.from.2026-03-24.18-14-13.mp4
What the recording shows:
proxy-rewritedemo.pnpm testandpnpm lintto show a perfectly clean environment.