The Auction Platform includes a flexible validation engine for form validation. It uses a registry pattern to map validation rule types to validator functions.
Architecture
The validation engine consists of three main components:
Validator Registry - Maps rule types to validator factory functions
Validation Rules - Type definitions for validation constraints
Attach Validators - Utility to attach validators to form fields
Type Definitions
The validation system uses TypeScript for type safety:
src/shared/validation-engine/types/rules.type.ts
export type ValidatorFnType = ( value : string , fieldName : string ) => string | null
export type FieldValidatorType =
| { type : "required" }
| { type : "minLength" , constraints : { minLength : number } }
| { type : "maxLength" , constraints : { minLength : number } }
export type BaseField = {
id : string
fieldValidators : FieldValidatorType []
}
export type ValidatorFactory = ( constraints ?: any ) => ValidatorFnType
export type LengthConstraints = {
minLength : number
}
Key Types
A validator function that takes a value and field name, returns an error message or null
Union type defining all available validation rules (required, minLength, maxLength)
Base structure for a form field with validation rules
Factory function that creates a validator with optional constraints
Validator Registry
The validator registry maps rule types to validator factory functions:
src/shared/validation-engine/validatiorRegistery.ts
import type { LengthConstraints , ValidatorFactory } from "./types/rules.type"
export const validatorRegistery : Record < string , ValidatorFactory > = {
required : () => {
return ( value : string ) => {
if ( ! value ) return "Field is required"
return null
}
},
minLength : ( constraints : LengthConstraints ) => {
return ( value : string ) => {
if ( value . trim (). length < constraints . minLength ) {
return `Must be at least ${ constraints . minLength } characters`
}
return null
}
},
maxLength : ( constraints : LengthConstraints ) => {
return ( value : string ) => {
if ( value . trim (). length > constraints . minLength ) {
return `Must be less than ${ constraints . minLength } characters`
}
return null
}
}
}
Built-in Validators
required Ensures the field has a value Returns: “Field is required” if empty
minLength Ensures minimum character count Constraints: { minLength: number }
maxLength Ensures maximum character count Constraints: { minLength: number }
Attaching Validators
The attachValidators function converts validation rules into validator functions:
src/shared/validation-engine/attachValidators.ts
import type { BaseField , ValidatorFnType } from "./types/rules.type"
import { validatorRegistery } from "./validatiorRegistery"
export function attachValidators < T extends BaseField >(
field : T
) : Omit < T , "fieldValidators" > & { fieldValidators : ValidatorFnType [] } {
return {
... field ,
fieldValidators: field . fieldValidators . map (( rule ) => {
const factory = validatorRegistery [ rule . type ]
if ( "constraints" in rule ) {
return factory ( rule . constraints )
}
return factory ()
})
}
}
How It Works
Takes a field with declarative validation rules
Looks up each rule type in the validator registry
Creates validator functions using the factory with constraints if provided
Returns the field with executable validator functions
Usage Examples
Basic Field Validation
import { attachValidators } from '@shared/validation-engine/attachValidators' ;
// Define a field with validation rules
const emailField = {
id: 'email' ,
fieldValidators: [
{ type: 'required' as const },
{ type: 'minLength' as const , constraints: { minLength: 5 } }
]
};
// Attach validators
const validatedField = attachValidators ( emailField );
// Run validators
const errors = validatedField . fieldValidators
. map ( validator => validator ( 'test' , 'Email' ))
. filter ( error => error !== null );
if ( errors . length > 0 ) {
console . log ( 'Validation errors:' , errors );
}
const formFields = [
{
id: 'username' ,
label: 'Username' ,
fieldValidators: [
{ type: 'required' as const },
{ type: 'minLength' as const , constraints: { minLength: 3 } },
{ type: 'maxLength' as const , constraints: { minLength: 20 } }
]
},
{
id: 'password' ,
label: 'Password' ,
fieldValidators: [
{ type: 'required' as const },
{ type: 'minLength' as const , constraints: { minLength: 8 } }
]
}
];
// Attach validators to all fields
const validatedFields = formFields . map ( attachValidators );
// Validation function
function validateField ( field : typeof validatedFields [ 0 ], value : string ) {
const errors = field . fieldValidators
. map ( validator => validator ( value , field . label ))
. filter ( Boolean );
return errors [ 0 ] || null ; // Return first error
}
React Hook Integration
import { useState } from 'react' ;
import { attachValidators } from '@shared/validation-engine/attachValidators' ;
function useFieldValidation ( fieldDefinition ) {
const [ value , setValue ] = useState ( '' );
const [ error , setError ] = useState < string | null >( null );
const validatedField = attachValidators ( fieldDefinition );
const validate = ( newValue : string ) => {
setValue ( newValue );
// Run all validators
const errors = validatedField . fieldValidators
. map ( validator => validator ( newValue , fieldDefinition . id ))
. filter ( Boolean );
setError ( errors [ 0 ] || null );
return errors . length === 0 ;
};
return { value , error , validate };
}
// Usage in component
function SignupForm () {
const username = useFieldValidation ({
id: 'username' ,
fieldValidators: [
{ type: 'required' as const },
{ type: 'minLength' as const , constraints: { minLength: 3 } }
]
});
return (
< div >
< input
value = { username . value }
onChange = { e => username . validate ( e . target . value ) }
/>
{ username . error && < span > { username . error } </ span > }
</ div >
);
}
Creating Custom Validators
Extend the validation engine with custom validators:
Step 1: Add Type Definition
export type FieldValidatorType =
| { type : "required" }
| { type : "minLength" , constraints : { minLength : number } }
| { type : "maxLength" , constraints : { minLength : number } }
| { type : "email" } // New validator
| { type : "pattern" , constraints : { pattern : RegExp , message : string } } // New
Step 2: Register Validator
import type { ValidatorFactory } from "./types/rules.type"
export const validatorRegistery : Record < string , ValidatorFactory > = {
// ... existing validators
email : () => {
const emailRegex = / ^ [ ^ \s@ ] + @ [ ^ \s@ ] + \. [ ^ \s@ ] + $ / ;
return ( value : string ) => {
if ( value && ! emailRegex . test ( value )) {
return "Please enter a valid email address"
}
return null
}
},
pattern : ( constraints : { pattern : RegExp , message : string }) => {
return ( value : string ) => {
if ( value && ! constraints . pattern . test ( value )) {
return constraints . message
}
return null
}
},
url : () => {
const urlRegex = / ^ https ? : \/\/ . + / ;
return ( value : string ) => {
if ( value && ! urlRegex . test ( value )) {
return "Please enter a valid URL"
}
return null
}
},
numeric : () => {
return ( value : string ) => {
if ( value && isNaN ( Number ( value ))) {
return "Please enter a number"
}
return null
}
}
}
Step 3: Use Custom Validator
const emailField = {
id: 'email' ,
fieldValidators: [
{ type: 'required' as const },
{ type: 'email' as const } // Use custom email validator
]
};
const websiteField = {
id: 'website' ,
fieldValidators: [
{ type: 'url' as const }
]
};
const bidAmountField = {
id: 'bidAmount' ,
fieldValidators: [
{ type: 'required' as const },
{ type: 'numeric' as const },
{
type: 'pattern' as const ,
constraints: {
pattern: / ^ [ 1-9 ] \d * $ / ,
message: 'Bid must be a positive number'
}
}
]
};
Advanced Patterns
Async Validators
export const validatorRegistery : Record < string , ValidatorFactory > = {
uniqueUsername : () => {
return async ( value : string ) => {
const response = await fetch ( `/api/check-username?username= ${ value } ` );
const { exists } = await response . json ();
if ( exists ) {
return "Username already taken"
}
return null
}
}
}
Dependent Field Validation
export const validatorRegistery : Record < string , ValidatorFactory > = {
matchField : ( constraints : { fieldToMatch : string , label : string }) => {
return ( value : string , fieldName : string , formValues : Record < string , string >) => {
if ( value !== formValues [ constraints . fieldToMatch ]) {
return ` ${ fieldName } must match ${ constraints . label } `
}
return null
}
}
}
// Usage: password confirmation
const confirmPasswordField = {
id: 'confirmPassword' ,
fieldValidators: [
{ type: 'required' as const },
{
type: 'matchField' as const ,
constraints: { fieldToMatch: 'password' , label: 'Password' }
}
]
};
Best Practices
Return null for valid values
Always return null for valid values, not empty strings or undefined: // Good
if ( isValid ) return null ;
// Avoid
if ( isValid ) return '' ;
Provide clear error messages
Error messages should be user-friendly and actionable: // Good
return "Must be at least 8 characters" ;
// Avoid
return "Invalid" ;
Always trim whitespace when checking string length: if ( value . trim (). length < minLength ) {
return `Must be at least ${ minLength } characters` ;
}
Use TypeScript for type safety
Leverage TypeScript’s type system to ensure validators match field types.
Reference
File Locations
src/shared/validation-engine/
├── attachValidators.ts # Attaches validators to fields
├── validatiorRegistery.ts # Validator registry
├── rules.ts # Legacy rule functions
└── types/
└── rules.type.ts # Type definitions
The validation engine is framework-agnostic and can be used with any form library or custom form solution.