Skip to content

Request/Response Validation SystemΒ #45

@Thavarshan

Description

@Thavarshan

Summary

Add comprehensive request and response validation with schema validation, data sanitization, and contract testing capabilities.

Motivation

Validation is crucial for:

  • Data Integrity: Ensure requests/responses meet expected schemas
  • Security: Validate and sanitize input data to prevent attacks
  • API Contracts: Enforce OpenAPI/JSON Schema specifications
  • Development Quality: Catch integration issues early
  • Documentation: Living documentation through validation schemas
  • Error Prevention: Fail fast on invalid data
  • Type Safety: Runtime type checking for dynamic data

Proposed API

// Schema-based validation
$response = fetch('/api/users/123')
    ->validateResponse(UserSchema::class)
    ->validateStatus([200, 404])
    ->expectJson()
    ->get();

// Request validation
$response = fetch('/api/users', [
    'method' => 'POST',
    'json' => $userData,
    'validate_request' => CreateUserSchema::class,
    'validate_response' => UserResponseSchema::class,
]);

// OpenAPI contract validation
$client = fetch_client()
    ->withOpenApiSpec('/path/to/api-spec.yaml')
    ->validateContracts(true);

// Custom validation rules
$response = fetch('/api/data')
    ->validateResponse([
        'id' => 'required|integer|min:1',
        'email' => 'required|email',
        'status' => 'required|in:active,inactive',
        'metadata' => 'array',
    ])
    ->onValidationFailure(function (ValidationException $e) {
        $this->logger->error('Response validation failed', $e->getErrors());
    });

Implementation Details

interface ValidatorInterface
{
    public function validate(mixed $data, array|string|object $schema): ValidationResult;
    public function sanitize(mixed $data, array $rules): mixed;
}

class ValidationResult
{
    public function __construct(
        private bool $isValid,
        private array $errors = [],
        private mixed $validatedData = null
    ) {}
    
    public function isValid(): bool { return $this->isValid; }
    public function getErrors(): array { return $this->errors; }
    public function getValidatedData(): mixed { return $this->validatedData; }
}

// JSON Schema validator
class JsonSchemaValidator implements ValidatorInterface
{
    public function validate(mixed $data, array|string|object $schema): ValidationResult
    {
        $validator = new \JsonSchema\Validator();
        $validator->validate($data, $schema);
        
        return new ValidationResult(
            $validator->isValid(),
            $validator->getErrors(),
            $data
        );
    }
}

// Laravel-style validator
class RuleBasedValidator implements ValidatorInterface
{
    public function validate(mixed $data, array|string|object $rules): ValidationResult
    {
        $errors = [];
        $validatedData = [];
        
        foreach ($rules as $field => $rule) {
            $value = $data[$field] ?? null;
            $fieldErrors = $this->validateField($field, $value, $rule);
            
            if (!empty($fieldErrors)) {
                $errors[$field] = $fieldErrors;
            } else {
                $validatedData[$field] = $this->sanitizeValue($value, $rule);
            }
        }
        
        return new ValidationResult(
            empty($errors),
            $errors,
            $validatedData
        );
    }
}

Use Cases

API Contract Testing

$client = fetch_client()
    ->withOpenApiSpec('/api/openapi.yaml')
    ->validateContracts(true)
    ->onContractViolation(function (ContractViolation $violation) {
        $this->testReporter->recordViolation($violation);
    });

Data Sanitization

$response = fetch('/api/user-input')
    ->sanitizeResponse([
        'name' => 'string|trim|escape',
        'email' => 'email|lowercase',
        'age' => 'integer|min:0|max:150',
    ]);

Type-Safe API Responses

class UserResponse
{
    public function __construct(
        public int $id,
        public string $name,
        public string $email,
        public array $permissions
    ) {}
}

$user = fetch('/api/users/123')
    ->validateResponse(UserResponse::class)
    ->getTypedData(); // Returns UserResponse instance

Benefits

  • Reliability: Catch data issues before they cause problems
  • Security: Validate and sanitize all external data
  • Documentation: Schemas serve as living documentation
  • Testing: Automated contract testing
  • Developer Experience: Clear error messages and type safety

Priority

Medium Impact, Medium Effort - Important for production APIs but not critical for basic functionality.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions