zjkiza / http-response-validator
A Symfony bundle for HTTP responses validating using a simple Result monad and handler chains.
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:symfony-bundle
pkg:composer/zjkiza/http-response-validator
Requires
- php: >=8.2
- psr/log: ^3.0
- symfony/config: ^5.0|^6.0|^7.0
- symfony/dependency-injection: ^5.0|^6.0|^7.0
- symfony/http-kernel: ^5.0|^6.0|^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.52
- icanhazstring/composer-unused: ^0.9.5
- phpstan/phpstan: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^12.4
- psalm/plugin-phpunit: ^0.19
- rector/rector: ^2.0
- symfony/framework-bundle: ^7.3
- symfony/http-client: ^8.0
- symfony/yaml: ^8.0
- vimeo/psalm: ^6.13
README
A Symfony bundle for HTTP responses validating using a simple Result monad and handler chains.
The main idea: the input is ResponseInterface (eg from symfony/http-client), then through a series of handlers (pipelines) the status code is validated, the content is logged, JSON is extracted and the structure is checked. Each handler returns a `Result', so the chain breaks on the first error and throws an exception with a unique message ID.
Characteristics
- Declarative stacking steps over
Result::success(...)->bind(...) - Built-in ready-to-use handlers:
ZJKiza\HttpResponseValidator\Handler\HttpResponseLoggerHandler– validates the expected status, logs the response, and masks sensitive keysZJKiza\HttpResponseValidator\Handler\ExtractResponseJsonHandler– decodes the JSON body (associatively or as an object)ZJKiza\HttpResponseValidator\Handler\ValidateArrayKeysExistHandler– checks whether certain keys are present in the associative array
- Simple extension: add your own handler and register it with a single service tag
- Clear error messages with
Message ID=<hex>for easy tracking in logs
Installation
Add "zjkiza/http-response-validator" to your composer.json file.
composer require zjkiza/http-response-validator
Symfony integration
Bundle wires up all classes together and provides method to easily setup.
- Register bundle within your configuration (i.e:
bundles.php).
<?php declare(strict_types=1); return [ // ... ZJKiza\HttpResponseValidator\ZJKizaHttpResponseValidatorBundle::class => ['all' => true], ];
- Bundle automatically registers services and factory class for handlers via service tag
zjkiza.http_response_validate.handler_factory.
Quick start
Example with symfony/http-client and built-in handlers:
use Symfony\Component\HttpClient\HttpClient; use ZJKiza\HttpResponseValidator\Monad\Result; use ZJKiza\HttpResponseValidator\Contract\HandlerFactoryInterface; use ZJKiza\HttpResponseValidator\Handler\HttpResponseLoggerHandler; use ZJKiza\HttpResponseValidator\Handler\ExtractResponseJsonHandler; use ZJKiza\HttpResponseValidator\Handler\ValidateArrayKeysExistHandler; // ... in the service/controller with DI you get a handler factory public function __construct(private HandlerFactoryInterface $handlerFactory) {} public function fetch(): array { $client = HttpClient::create(); $response = $client->request('GET', 'https://example.com/api/user'); // We arrange pipeline steps; getOrThrow() will throw an exception on error $data = Result::success($response) ->bind($this->handlerFactory->create(HttpResponseLoggerHandler::class) ->setExpectedStatus(200) ->addSensitiveKeys(['password', 'token'])) ->bind($this->handlerFactory->create(ExtractResponseJsonHandler::class) ->setAssociative(true)) ->bind($this->handlerFactory->create(ValidateArrayKeysExistHandler::class) ->setKeys(['id', 'email'])) ->getOrThrow(); // $data is now an associative array with the required keys return $data; }
If a step fails, an exception will be thrown with a message containing a unique `Message ID=...', and the error will be logged via the PSR‑3 logger.
Ugrađeni handler‑i
All handlers implement ZJKiza\HttpResponseValidator\Contract\HandlerInterface and expose a fluent API for configuration.
-
HttpResponseLoggerHandler- What it does: Validates the expected HTTP status, logs the response, and masks the values for the defined keys in the body
- Essential methods:
setExpectedStatus(int $status): selfaddSensitiveKeys(string[] $keys): self
-
ExtractResponseJsonHandler- What it does: calls
$response->getContent(false), decodes the JSON, and returns the result as a string or object - Essential methods:
setAssociative(bool $assoc = true): self– whentrue' returns an associative array; whenfalse' is an object
- What it does: calls
-
ValidateArrayKeysExistHandler- What it does: Checks whether the passed associative string contains required keys
- Essential methods:
setKeys(string[] $keys): self
How to add your own Handler
- Create a class that implements
HandlerInterface(recommendation: inherit fromAbstractHandlerand use theTagIndexMethodtrait). Example: validating the format of an email field in a string.
<?php declare(strict_types=1); namespace App\HttpResponse\Handler; use ZJKiza\HttpResponseValidator\Handler\AbstractHandler; use ZJKiza\HttpResponseValidator\Handler\Factory\TagIndexMethod; use ZJKiza\HttpResponseValidator\Contract\HandlerInterface; use ZJKiza\HttpResponseValidator\Monad\Result; /** * @implements HandlerInterface<array<string,mixed>, array<string,mixed>> */ final class ValidateEmailFormatHandler extends AbstractHandler implements HandlerInterface { use TagIndexMethod; // automates the static index for factory registration public function __invoke(mixed $value): Result { if (!isset($value['email']) || !\filter_var($value['email'], FILTER_VALIDATE_EMAIL)) { return $this->fail('Email is not valid..'); } return Result::success($value); } }
- Register the service and be sure to add a tag
zjkiza.http_response_validate.handler_factory:
# config/services.yaml services: App\HttpResponse\Handler\ValidateEmailFormatHandler: tags: - { name: 'zjkiza.http_response_validate.handler_factory' }
- Use it in a chain across
HandlerFactoryInterface:
$result = Result::success($response) ->bind($handlerFactory->create(HttpResponseLoggerHandler::class)->setExpectedStatus(200)) ->bind($handlerFactory->create(ExtractResponseJsonHandler::class)->setAssociative(true)) ->bind($handlerFactory->create(ValidateEmailFormatHandler::class)) ->getOrThrow();
Note: if you don't want to use the TagIndexMethod, make sure your class implements the static getIndex(): string method that returns a unique index (most commonly FQCN).
Logging in and message ID
Errors are logged via the PSR‑3 logger. Messages are automatically prefixed with Message ID=<hex> (see helper ZJKiza\HttpResponseValidator\addIdInMessage()), which facilitates correlation between logs and client messages.
Development and tests (optional for contributors)
composer phpunit
composer phpstan
composer psalm
composer php-cs-fixer
License
MIT. View the `LICENSE' file in the repository.
What else needs to be done
[ ] Update readme [+] When ValidateArrayKeysExistHandler encounters the first error in the keys, it does not immediately throw an exception, but collects all the missing keys and only then throws the exception.