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.

PadawanForge v1.4.1