-
-
Notifications
You must be signed in to change notification settings - Fork 26
Open
Labels
Description
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 instanceBenefits
- 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.