Skip to content

feat: implement custom validators for form authors#417

Merged
Mishael-2584 merged 6 commits intodevfrom
feature/custom-validators
Mar 2, 2026
Merged

feat: implement custom validators for form authors#417
Mishael-2584 merged 6 commits intodevfrom
feature/custom-validators

Conversation

@Mishael-2584
Copy link
Contributor

@Mishael-2584 Mishael-2584 commented Mar 2, 2026

Description

This PR implements a comprehensive custom validators feature that enables form authors to define reusable JavaScript validation logic that integrates seamlessly with the JSON Forms validation lifecycle. This feature extends the existing custom question types architecture and provides a flexible, type-safe way to add custom validation rules to forms.

Key Features

  • CustomValidatorLoader, Registry, and Executor services: Three new services handle loading, registration, and execution of custom validators
  • Extended FormInitData interface: Manifest now includes validators alongside custom question types
  • Validator scanning: FormplayerModal scans validators/ folder and includes source code in manifest
  • JSON Forms integration: Validators execute automatically on data changes within the JSON Forms lifecycle
  • AJV-compatible errors: Validator errors are converted to AJV format for consistent display
  • Cross-field validation: Validators receive full form data context for complex validation scenarios
  • Graceful failure handling: Validator failures are logged but don't crash the form

Implementation Highlights

React Native Side (formulus):

  • Extended FormInitData interface to include validators in manifest
  • Added scanning logic in FormplayerModal.tsx to read validator source code from validators/ folder
  • Validators are bundled with custom question types in the app bundle

Formplayer Side (formulus-formplayer):

  • Created CustomValidatorLoader.ts to load and evaluate validator modules from manifest
  • Created CustomValidatorRegistry.ts as singleton registry for loaded validators
  • Created CustomValidatorExecutor.ts to extract validators from UI schema and execute them
  • Created CustomValidatorContract.ts to define validator function interface
  • Integrated validator loading in App.tsx during form initialization
  • Integrated validator execution in handleDataChange callback
  • Merged custom errors with standard AJV validation via additionalErrors prop

User Stories Implemented

US01: Define custom validators as JavaScript modules registered in manifest
US02: Reference validators in UI schema via options.customValidators array
US03: Execute validators in JSON Forms lifecycle alongside AJV validation
US04: Provide full form context (data, value, path, config, ajv) to validators
US05: Return standardized validation errors in AJV-compatible format
US06: Display custom validation messages like standard validation errors
US07: Support reusable validators with different configurations per field
US08: Implement graceful failure handling for validator errors

Type of Change

  • Bug Fix
  • New Feature / Enhancement
  • Refactor / Code Cleanup
  • Documentation Update
  • Maintenance / Chore
  • Other (please specify):

Component(s) Affected

  • formulus (React Native mobile app)
  • formulus-formplayer (React web app)
  • synkronus (Go backend server)
  • synkronus-cli (Command-line utility)
  • Documentation
  • DevOps / CI/CD
  • Other:

Related Issue(s)

Closes/Fixes/Resolves:


Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manually tested
  • Tested on multiple platforms (if applicable)
  • Not applicable

Testing Notes:

  • Manual testing completed with example validators (isAdult, matchesRegex, passwordMatch, rangeValidator)
  • Test form created in AnthroCollect repository
  • Validators tested with various configurations and cross-field validation scenarios
  • Error display verified to match standard JSON Forms validation behavior
  • Graceful failure handling verified with intentionally broken validators

Breaking Changes

  • This PR introduces breaking changes
  • This PR does NOT introduce breaking changes

If breaking changes, please describe migration steps:

N/A - This is a new feature that extends existing functionality without breaking existing code.


Documentation Updates

  • Documentation has been updated
  • Documentation update is not required

Documentation Added:

  • CUSTOM_VALIDATORS_USAGE.md: Comprehensive guide for form authors on defining and using custom validators
  • CUSTOM_VALIDATORS_IMPLEMENTATION_PLAN.md: Detailed implementation documentation covering all 8 user stories

Files Changed

New Files

  • formulus-formplayer/src/services/CustomValidatorLoader.ts - Loads and evaluates validator modules
  • formulus-formplayer/src/services/CustomValidatorRegistry.ts - Registry for loaded validators
  • formulus-formplayer/src/services/CustomValidatorExecutor.ts - Executes validators in JSON Forms lifecycle
  • formulus-formplayer/src/types/CustomValidatorContract.ts - Validator function interface definition

Modified Files

  • formulus-formplayer/src/App.tsx - Integrated validator loading and execution
  • formulus-formplayer/src/types/FormulusInterfaceDefinition.ts - Extended FormInitData interface
  • formulus/src/components/FormplayerModal.tsx - Added validator scanning logic
  • formulus/src/webview/FormulusInterfaceDefinition.ts - Extended FormInitData interface (source of truth)

Example Validators (AnthroCollect)

  • validators/isAdult/index.js - Age validation example
  • validators/matchesRegex/index.js - Pattern matching example
  • validators/passwordMatch/index.js - Cross-field validation example
  • validators/rangeValidator/index.js - Range validation example

Checklist

  • Code follows project style guidelines
  • All existing tests pass
  • New tests added for new functionality
  • PR title follows Conventional Commits format
  • Linting passes (0 errors)
  • Prettier formatting passes
  • No build assets committed

Usage Example

Defining a Validator

javascript
// validators/isAdult/index.js
export default function validate({ data, value, path, config }) {
const minAge = config.minAge || 18;
if (typeof value !== 'number' || value < minAge) {
return [{
path,
message: Must be at least ${minAge} years old,
keyword: 'minimumAge'
}];
}
}

Using in UI Schema

{
"type": "Control",
"scope": "#/properties/age",
"options": {
"customValidators": [
{
"name": "isAdult",
"config": { "minAge": 18 }
}
]
}
}

Mishael-2584 and others added 6 commits March 2, 2026 20:27
- Add CustomValidatorLoader, Registry, and Executor services
- Extend FormInitData interface to include validators in manifest
- Scan validators folder in FormplayerModal and include in manifest
- Load validators during form initialization
- Execute validators on data changes in JSON Forms lifecycle
- Convert validator errors to AJV-compatible format
- Merge custom errors with standard validation via additionalErrors
- Support cross-field validation via full form data access
- Implement graceful failure handling

Implements all 8 user stories:
- US01: Define custom validators as JS modules
- US02: Reference validators in UI schema options
- US03: Execute in JSON Forms lifecycle
- US04: Provide full form context to validators
- US05: Return standardized validation errors
- US06: Display custom validation messages
- US07: Support reusable validators with different configs
- US08: Graceful validator failure handling
- Remove unused extractCustomValidators and executeCustomValidators imports from App.tsx
- Remove unused CustomValidatorFunction import from CustomValidatorExecutor.ts
@Mishael-2584 Mishael-2584 marked this pull request as ready for review March 2, 2026 21:37
Copy link
Contributor

@najuna-brian najuna-brian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved to be part of the code freeze

@Mishael-2584 Mishael-2584 merged commit b0e97b9 into dev Mar 2, 2026
15 checks passed
@najuna-brian najuna-brian deleted the feature/custom-validators branch March 2, 2026 21:48
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