Union Rules
Validate values against multiple rule sets with union types, allowing flexible validation where a value can match any of several different validation patterns.
Overview
Union rules allow you to validate a value against multiple different rule sets, where the validation passes if the value matches any one of the rule sets. This is particularly useful for fields that can accept different types of data or formats.
Key Concepts
- Multiple Rule Sets: Define several different validation patterns
- First Match Wins: Validation passes as soon as one rule set matches
- Flexible Types: Accept different data types for the same field
- Performance Optimized: Supports parallel validation and early termination
Basic Union Validation
Create union rules using different validation formats:
String Format
import { Validator } from 'validlyjs_2';
const validator = new Validator({
// Accept either email or phone number
contact: 'union:(string|email;string|regex:^\\+[1-9][0-9]{1,14}$)',
// Accept either positive number or string with minimum length
value: 'union:(number|positive;string|min:3)',
// Accept boolean, number between 1-10, or specific strings
mixed: 'union:(boolean;number|between:1,10;string|in:yes,no,maybe)'
});
const result = await validator.validate({
contact: 'user@example.com', // matches email rule
value: 42, // matches positive number rule
mixed: true // matches boolean rule
});
console.log(result.isValid); // true
Fluent API
import { Validator, union, string, number, boolean } from 'validlyjs_2';
const validator = new Validator({
// ID can be UUID string, positive integer, or custom format
id: union()
.add(string().uuid())
.add(number().integer().positive())
.add(string().regex(/^ID[0-9]+$/)),
// File can be URL string or actual file
file: union()
.add(string().url())
.add(file().extensions(['pdf', 'doc', 'docx'])),
// Status can be boolean or specific strings
status: union()
.add(boolean())
.add(string().in(['active', 'inactive', 'pending']))
});
const result = await validator.validate({
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', // UUID
file: 'https://example.com/document.pdf', // URL
status: 'active' // String
});
Array Format
const validator = new Validator({
identifier: [
[
['string', 'email'],
['string', 'min:8', 'max:20'],
['number', 'integer', 'positive']
]
]
});
Complex Union Scenarios
Handle sophisticated validation patterns with nested rules and complex combinations:
User Identification
// Accept username, email, or phone number for login
const loginValidator = new Validator({
identifier: 'union:(string|min:3|max:20|regex:^[a-zA-Z0-9_]+$;string|email;string|regex:^\\+[1-9][0-9]{1,14}$)'
// username or email or phone
//within a union using the string format, the semicolon breaks the ruleset
});
// Valid inputs:
await loginValidator.validate({ identifier: 'john_doe123' }); // username
await loginValidator.validate({ identifier: 'john@example.com' }); // email
await loginValidator.validate({ identifier: '+1234567890' }); // phone
Flexible Data Input
// Accept different data formats for API endpoints
const apiValidator = new Validator({
// Accept JSON string, object, or array
data: 'union:(string|json;object;array)',
// Accept timestamp as number, ISO string, or Date object
timestamp: 'union:(number|min:0;string|date_format:ISO8601;date)',
// Accept coordinates as string, array, or object
location: 'union:(' +
'string|regex:^-?\\d+\\.\\d+,-?\\d+\\.\\d+$;' + // "lat,lng"
'array|size:2|each:numeric;' + // [lat, lng]
'object|shape:{lat:numeric,lng:numeric}' + // {lat, lng}
')'
});
// Valid inputs:
await apiValidator.validate({
data: '{"name": "John"}', // JSON string
timestamp: 1640995200000, // Unix timestamp
location: '40.7128,-74.0060' // String coordinates
});
await apiValidator.validate({
data: { name: 'John' }, // Object
timestamp: '2022-01-01T00:00:00Z', // ISO string
location: [40.7128, -74.0060] // Array coordinates
});
File Upload Validation
// Accept file as URL, base64 string, or file object
const fileValidator = new Validator({
document: 'union:(' +
'string|url|regex:\\.pdf$;' + // PDF URL
'string|regex:^data:application/pdf;base64,;' + // Base64 PDF
'file|extensions:pdf|max_size:5MB' + // File object
')',
image: 'union:(' +
'string|url|regex:\\.(jpg|jpeg|png|gif)$;' + // Image URL
'string|regex:^data:image/;' + // Base64 image
'file|image|max_size:2MB' + // Image file
')'
});
Conditional Union Rules
Combine union rules with conditional validation for dynamic behavior:
Dynamic Validation Based on Type
const dynamicValidator = new Validator({
type: 'required|string|in:user,admin,guest',
// Different validation based on user type
identifier: 'required_if:type,user|union:(' +
'string|email;' + // Email for users
'string|min:8|max:20' + // Username for users
')',
permissions: 'required_if:type,admin|union:(' +
'array|each:string;' + // Array of permission strings
'string|in:all,read,write' + // Single permission level
')',
// Guest users need different validation
session: 'required_if:type,guest|union:(' +
'string|uuid;' + // Session UUID
'string|min:32|max:64' + // Session token
')'
});
// User with email
await dynamicValidator.validate({
type: 'user',
identifier: 'john@example.com'
});
// Admin with permissions array
await dynamicValidator.validate({
type: 'admin',
identifier: 'admin_user',
permissions: ['read', 'write', 'delete']
});
// Guest with session token
await dynamicValidator.validate({
type: 'guest',
session: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
});
Performance Options
Optimize union validation performance with various configuration options:
Stop on First Pass
This is enabled by default and cannot be disabled (except in fluent api). It passes the field if the first ruleset is valid.
// Using fluent API
const fluentValidator = new Validator({
value: union()
.add(string().email())
.add(number().positive())
.add(boolean())
.stopOnFirstPass(true)
});
Parallel Validation
// Validate all rule sets in parallel for better performance
const validator = new Validator({
data: 'union:(string|json;object|shape:{id:integer};array|min:1)'
}, {
performance: {
parallelValidation: true
}
});
Custom Union Error Messages
const validator = new Validator({
contact: 'union:(string|email;string|regex:^\\+[1-9][0-9]{1,14}$)'
}, {
messages: {
'union': 'The {field} must be either a valid email address or phone number.',
'union.email_or_phone': 'Please provide a valid email or phone number for {field}.'
},
fieldMessages: {
contact: {
'union': 'Contact information must be an email or phone number.'
}
}
});
Advanced Patterns
Sophisticated union validation patterns for complex use cases:
Nested Union Rules
// Union rules within object validation
const validator = new Validator({
user: 'object|shape:{' +
'id:union:(string|uuid;number|integer|positive),' +
'contact:union:(string|email;string|regex:^\\+[1-9][0-9]{1,14}$),' +
'profile:object|shape:{' +
'avatar:union:(string|url;file|image),' +
'bio:union:(string|max:500;null)' +
'}' +
'}'
});
await validator.validate({
user: {
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
contact: 'john@example.com',
profile: {
avatar: 'https://example.com/avatar.jpg',
bio: 'Software developer and tech enthusiast.'
}
}
});
Array with Union Elements
// Array where each element can match different rule sets
const validator = new Validator({
// Mixed array of different data types
items: 'array|each:union:(string|min:1;number|positive;boolean;object|shape:{type:string,value:any})',
// Array of different ID formats
ids: 'array|each:union:(string|uuid;number|integer|positive;string|regex:^[A-Z]{2}[0-9]{6}$)',
// Array of contact methods
contacts: 'array|each:union:(string|email;string|regex:^\\+[1-9][0-9]{1,14}$;object|shape:{type:string,value:string})'
});
await validator.validate({
items: [
'hello', // string
42, // number
true, // boolean
{ type: 'custom', value: 'data' } // object
],
ids: [
'f47ac10b-58cc-4372-a567-0e02b2c3d479', // UUID
12345, // number
'AB123456' // custom format
],
contacts: [
'john@example.com', // email
'+1234567890', // phone
{ type: 'skype', value: 'john.doe' } // custom contact
]
});
Best Practices
Union Validation Best Practices
- Order Matters: Place more specific rules before general ones
- Performance: Use
stopOnFirstPass
for better performance when order doesn't matter - Error Messages: Provide clear, user-friendly error messages for union failures
- Type Safety: Use TypeScript interfaces to ensure type safety with union rules
- Testing: Test all possible union paths to ensure comprehensive coverage
- Documentation: Document expected union types clearly for API consumers
- Complexity: Avoid overly complex union rules that are hard to understand
- Validation Strategy: Choose the right union strategy based on your use case
TypeScript Integration
// Define union types for better type safety
type UserId = string | number;
type ContactMethod = string; // email or phone
type FileInput = string | File; // URL or file object
interface UserData {
id: UserId;
contact: ContactMethod;
avatar?: FileInput;
}
// Create validator with proper typing
const typedValidator = new Validator({
id: 'union:(string|uuid;number|integer|positive)',
contact: 'union:(string|email;string|regex:^\\+[1-9][0-9]{1,14}$)',
avatar: 'optional|union:(string|url;file|image)'
});
// Type-safe validation
const userData: UserData = {
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
contact: 'john@example.com'
};
const result = await typedValidator.validate(userData);