Validation Framework
Overview
PadawanForge implements a comprehensive validation framework that provides consistent input validation across all application layers. This system supports schema-based validation, type checking, custom validation rules, and detailed error reporting.
Architecture
Core Components
- Validator Class: Central validation engine
- Validation Schemas: Predefined validation rules
- Type Validation: Built-in type checking
- Custom Rules: Extensible validation logic
- Error Reporting: Detailed validation error messages
Validation Features
- Schema-Based Validation: Define validation rules for objects
- Type Safety: Built-in type validation (email, URL, UUID)
- Custom Rules: Extensible validation logic
- Error Context: Rich error information
- Performance Optimized: Efficient validation algorithms
Implementation
Validator Class
import { Validator, ValidationRule, ValidationResult } from '@/lib/utils/validation';
// Create validation rule
const emailRule: ValidationRule = {
required: true,
type: 'email',
maxLength: 255
};
// Validate single value
const result = Validator.validateValue('user@example.com', emailRule);
console.log(result.isValid); // true
console.log(result.errors); // []
// Validate object with schema
const userSchema: ValidationSchema = {
email: { required: true, type: 'email' },
username: { required: true, minLength: 3, maxLength: 50 },
age: { type: 'number', custom: (value) => value >= 18 || 'Must be 18 or older' }
};
const userData = { email: 'user@example.com', username: 'john', age: 25 };
const validationResult = Validator.validateObject(userData, userSchema);
Validation Rule Interface
interface ValidationRule {
required?: boolean; // Field is required
minLength?: number; // Minimum string length
maxLength?: number; // Maximum string length
pattern?: RegExp; // Regex pattern validation
custom?: (value: any) => boolean | string; // Custom validation function
type?: 'string' | 'number' | 'email' | 'url' | 'uuid'; // Type validation
}
Validation Result Interface
interface ValidationResult {
isValid: boolean; // Overall validation result
errors: string[]; // Array of error messages
value?: any; // Validated value (if valid)
}
Usage Examples
Basic Validation
import { Validator } from '@/lib/utils/validation';
// Validate email
const emailResult = Validator.validateValue('user@example.com', {
required: true,
type: 'email'
});
if (!emailResult.isValid) {
console.log('Email errors:', emailResult.errors);
}
// Validate URL
const urlResult = Validator.validateValue('https://example.com', {
required: true,
type: 'url'
});
// Validate UUID
const uuidResult = Validator.validateValue('123e4567-e89b-12d3-a456-426614174000', {
required: true,
type: 'uuid'
});
Object Validation
// Define validation schema
const playerSchema: ValidationSchema = {
username: {
required: true,
minLength: 3,
maxLength: 50,
pattern: /^[a-zA-Z0-9_-]+$/
},
email: {
required: true,
type: 'email',
maxLength: 255
},
age: {
type: 'number',
custom: (value) => {
if (value < 13) return 'Must be 13 or older';
if (value > 120) return 'Invalid age';
return true;
}
},
bio: {
maxLength: 500,
custom: (value) => {
if (value && value.includes('spam')) {
return 'Bio contains inappropriate content';
}
return true;
}
}
};
// Validate player data
const playerData = {
username: 'john_doe',
email: 'john@example.com',
age: 25,
bio: 'Hello, I am a player!'
};
const result = Validator.validateObject(playerData, playerSchema);
if (result.isValid) {
console.log('Player data is valid:', result.value);
} else {
console.log('Validation errors:', result.errors);
}
Custom Validation Functions
// Custom validation for password strength
const passwordRule: ValidationRule = {
required: true,
minLength: 8,
custom: (value) => {
const hasUpperCase = /[A-Z]/.test(value);
const hasLowerCase = /[a-z]/.test(value);
const hasNumbers = /\d/.test(value);
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(value);
if (!hasUpperCase) return 'Password must contain uppercase letter';
if (!hasLowerCase) return 'Password must contain lowercase letter';
if (!hasNumbers) return 'Password must contain number';
if (!hasSpecialChar) return 'Password must contain special character';
return true;
}
};
// Custom validation for unique username
const uniqueUsernameRule: ValidationRule = {
required: true,
minLength: 3,
maxLength: 50,
custom: async (value) => {
const existingUser = await db.prepare(`
SELECT id FROM players WHERE username = ?
`).bind(value).first();
if (existingUser) {
return 'Username already taken';
}
return true;
}
};
Predefined Validation Schemas
Common Schemas
import { commonSchemas } from '@/lib/utils/validation';
// User profile validation
const userProfileSchema = commonSchemas.userProfile;
// Game session validation
const gameSessionSchema = commonSchemas.gameSession;
// API key validation
const apiKeySchema = commonSchemas.apiKey;
// NPC configuration validation
const npcConfigSchema = commonSchemas.npcConfiguration;
Schema Examples
// User profile schema
const userProfileSchema: ValidationSchema = {
username: {
required: true,
minLength: 3,
maxLength: 50,
pattern: /^[a-zA-Z0-9_-]+$/
},
email: {
required: true,
type: 'email',
maxLength: 255
},
displayName: {
maxLength: 100
},
bio: {
maxLength: 500
},
birthday: {
custom: (value) => {
if (!value) return true; // Optional
const date = new Date(value);
const now = new Date();
const age = now.getFullYear() - date.getFullYear();
if (age < 13) return 'Must be 13 or older';
if (age > 120) return 'Invalid birth date';
return true;
}
},
location: {
maxLength: 100
},
gender: {
custom: (value) => {
const validGenders = ['M', 'F', 'Other', 'Prefer not to say'];
if (value && !validGenders.includes(value)) {
return 'Invalid gender selection';
}
return true;
}
}
};
// Game session schema
const gameSessionSchema: ValidationSchema = {
sessionId: {
required: true,
type: 'uuid'
},
playerId: {
required: true,
type: 'uuid'
},
duration: {
required: true,
type: 'number',
custom: (value) => {
if (value < 60 || value > 3600) {
return 'Session duration must be between 60 and 3600 seconds';
}
return true;
}
},
difficulty: {
required: true,
custom: (value) => {
const validDifficulties = ['basic', 'intermediate', 'advanced'];
if (!validDifficulties.includes(value)) {
return 'Invalid difficulty level';
}
return true;
}
}
};
Utility Functions
Type Validation
import { isValidEmail, isValidUuid, isValidUrl } from '@/lib/utils/validation';
// Email validation
if (isValidEmail('user@example.com')) {
console.log('Valid email');
}
// UUID validation
if (isValidUuid('123e4567-e89b-12d3-a456-426614174000')) {
console.log('Valid UUID');
}
// URL validation
if (isValidUrl('https://example.com')) {
console.log('Valid URL');
}
Validation Helpers
// Validate user profile
export const validateUserProfile = (data: any) =>
Validator.validateObject(data, commonSchemas.userProfile);
// Validate game session
export const validateGameSession = (data: any) =>
Validator.validateObject(data, commonSchemas.gameSession);
// Validate API key
export const validateApiKey = (data: any) =>
Validator.validateObject(data, commonSchemas.apiKey);
// Validate NPC configuration
export const validateNpcConfiguration = (data: any) =>
Validator.validateObject(data, commonSchemas.npcConfiguration);
API Integration
Request Validation
import { ValidationError } from '@/lib/errors/types';
import { validateUserProfile } from '@/lib/utils/validation';
export async function POST(request: Request) {
try {
const body = await request.json();
// Validate request body
const validationResult = validateUserProfile(body);
if (!validationResult.isValid) {
throw new ValidationError('Invalid user profile data', {
errors: validationResult.errors
});
}
// Process valid data
const user = await createUser(validationResult.value);
return new Response(JSON.stringify({ success: true, user }), {
status: 201,
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return handleApiError(error);
}
}
Form Validation
// React component with validation
function UserProfileForm() {
const [formData, setFormData] = useState({});
const [errors, setErrors] = useState({});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Validate form data
const validationResult = validateUserProfile(formData);
if (!validationResult.isValid) {
// Convert validation errors to form errors
const formErrors = {};
validationResult.errors.forEach(error => {
const field = error.split(':')[0];
const message = error.split(':')[1];
formErrors[field] = message;
});
setErrors(formErrors);
return;
}
// Submit valid data
await updateProfile(validationResult.value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
{errors.email && <span className="error">{errors.email}</span>}
<button type="submit">Update Profile</button>
</form>
);
}
Advanced Validation Patterns
Conditional Validation
// Conditional validation based on other fields
const conditionalSchema: ValidationSchema = {
accountType: {
required: true,
custom: (value) => {
const validTypes = ['personal', 'business'];
if (!validTypes.includes(value)) {
return 'Invalid account type';
}
return true;
}
},
businessName: {
custom: (value, allData) => {
// Only required if account type is business
if (allData.accountType === 'business') {
if (!value) return 'Business name is required for business accounts';
if (value.length < 2) return 'Business name must be at least 2 characters';
}
return true;
}
},
taxId: {
custom: (value, allData) => {
// Only required for business accounts
if (allData.accountType === 'business') {
if (!value) return 'Tax ID is required for business accounts';
if (!/^\d{9}$/.test(value)) return 'Tax ID must be 9 digits';
}
return true;
}
}
};
Array Validation
// Validate array of objects
const validateArray = (array: any[], itemSchema: ValidationSchema) => {
const errors: string[] = [];
const validatedArray: any[] = [];
for (let i = 0; i < array.length; i++) {
const item = array[i];
const result = Validator.validateObject(item, itemSchema);
if (!result.isValid) {
result.errors.forEach(error => {
errors.push(`Item ${i}: ${error}`);
});
} else {
validatedArray.push(result.value);
}
}
return {
isValid: errors.length === 0,
errors,
value: validatedArray
};
};
// Usage
const playerSchema: ValidationSchema = {
username: { required: true, minLength: 3 },
level: { type: 'number', custom: (value) => value >= 1 || 'Level must be 1 or higher' }
};
const players = [
{ username: 'player1', level: 5 },
{ username: 'p2', level: 0 }, // Invalid
{ username: 'player3', level: 10 }
];
const result = validateArray(players, playerSchema);
Testing
Validation Testing
describe('Validation Framework', () => {
it('should validate email correctly', () => {
const validEmails = [
'user@example.com',
'user.name@domain.co.uk',
'user+tag@example.org'
];
const invalidEmails = [
'invalid-email',
'@example.com',
'user@',
'user@.com'
];
validEmails.forEach(email => {
const result = Validator.validateValue(email, { type: 'email' });
expect(result.isValid).toBe(true);
});
invalidEmails.forEach(email => {
const result = Validator.validateValue(email, { type: 'email' });
expect(result.isValid).toBe(false);
});
});
it('should validate object schema', () => {
const schema: ValidationSchema = {
name: { required: true, minLength: 2 },
age: { type: 'number', custom: (value) => value >= 18 || 'Must be 18 or older' }
};
const validData = { name: 'John', age: 25 };
const invalidData = { name: 'J', age: 15 };
const validResult = Validator.validateObject(validData, schema);
const invalidResult = Validator.validateObject(invalidData, schema);
expect(validResult.isValid).toBe(true);
expect(invalidResult.isValid).toBe(false);
expect(invalidResult.errors).toContain('name: Minimum length is 2 characters');
expect(invalidResult.errors).toContain('age: Must be 18 or older');
});
});
This validation framework provides robust, consistent input validation across the entire PadawanForge application.