From 5c898be01d660d6ae5eb360e1ad3ac530bcd2f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B0=D1=82=D0=BE=D0=BB=D0=B8=D0=B9=20=D0=9D?= =?UTF-8?q?=D0=B5=D1=85=D0=B0=D0=B9?= Date: Mon, 8 Jun 2020 20:17:58 +0300 Subject: [PATCH 1/3] build json schema for a request query params --- src/Utility/JsonSchemaBuilder.php | 56 +++++++++++++++++ tests/Utility/JsonSchemaBuilderTest.php | 80 +++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/src/Utility/JsonSchemaBuilder.php b/src/Utility/JsonSchemaBuilder.php index 598acec..6432aa1 100644 --- a/src/Utility/JsonSchemaBuilder.php +++ b/src/Utility/JsonSchemaBuilder.php @@ -16,6 +16,7 @@ */ use Doctrine\Common\Annotations\SimpleAnnotationReader; use Sunrise\Http\Router\OpenApi\Annotation\OpenApi\Operation; +use Sunrise\Http\Router\OpenApi\Annotation\OpenApi\ParameterReference; use Sunrise\Http\Router\OpenApi\Annotation\OpenApi\RequestBodyReference; use Sunrise\Http\Router\OpenApi\Annotation\OpenApi\ResponseReference; use Sunrise\Http\Router\OpenApi\Exception\UnsupportedMediaTypeException; @@ -26,6 +27,7 @@ * Import functions */ use function array_keys; +use function array_walk; use function array_walk_recursive; use function str_replace; @@ -65,6 +67,60 @@ public function __construct(ReflectionClass $operationSource) $this->annotationReader->addNamespace(OpenApi::ANNOTATIONS_NAMESPACE); } + /** + * Builds a JSON schema for a request query parameters + * + * @return null|array + */ + public function forRequestQueryParams() : ?array + { + $operation = $this->annotationReader->getClassAnnotation($this->operationSource, Operation::class); + if (empty($operation->parameters)) { + return null; + } + + $jsonSchema = $this->jsonSchemaBlank; + $jsonSchema['type'] = 'object'; + $jsonSchema['required'] = []; + $jsonSchema['properties'] = []; + $jsonSchema['definitions'] = []; + + foreach ($operation->parameters as $parameter) { + if ($parameter instanceof ParameterReference) { + $parameter = $parameter->getAnnotation($this->annotationReader); + } + + if (!('query' === $parameter->in)) { + continue; + } + + if ($parameter->required) { + $jsonSchema['required'][] = $parameter->name; + } + + if ($parameter->schema) { + $jsonSchema['properties'][$parameter->name] = $parameter->schema; + } + } + + if (empty($jsonSchema['required']) && empty($jsonSchema['properties'])) { + return null; + } + + $referencedObjects = $operation->getReferencedObjects($this->annotationReader); + foreach ($referencedObjects as $referencedObject) { + if ('schemas' === $referencedObject->getComponentName()) { + $jsonSchema['definitions'][$referencedObject->getReferenceName()] = $referencedObject->toArray(); + } + } + + array_walk($jsonSchema['properties'], function (&$schema) { + $schema = $schema->toArray(); + }); + + return $this->fixReferences($jsonSchema); + } + /** * Builds a JSON schema for a request body * diff --git a/tests/Utility/JsonSchemaBuilderTest.php b/tests/Utility/JsonSchemaBuilderTest.php index 7091951..338c92d 100644 --- a/tests/Utility/JsonSchemaBuilderTest.php +++ b/tests/Utility/JsonSchemaBuilderTest.php @@ -77,6 +77,86 @@ class JsonSchemaBuilderTest extends TestCase */ private $bar; + /** + * @OpenApi\Schema( + * refName="ReferencedBazProperty", + * type="string", + * ) + */ + private $baz; + + /** + * @return void + */ + public function testBuildJsonSchemaForRequestQuery() : void + { + /** + * @OpenApi\Operation( + * parameters={ + * @OpenApi\Parameter( + * in="cookie", + * name="foo", + * schema=@OpenApi\Schema( + * type="string", + * ), + * ), + * @OpenApi\Parameter( + * in="query", + * name="bar", + * schema=@OpenApi\Schema( + * type="string", + * ), + * ), + * @OpenApi\Parameter( + * in="query", + * name="baz", + * schema=@OpenApi\SchemaReference( + * class="Sunrise\Http\Router\OpenApi\Tests\Utility\JsonSchemaBuilderTest", + * property="baz", + * ), + * ), + * }, + * responses={ + * 200: @OpenApi\Response( + * description="OK", + * ), + * }, + * ) + */ + $class = new class + { + }; + + $classReflection = new ReflectionClass($class); + $jsonSchemaBuilder = new JsonSchemaBuilder($classReflection); + $jsonSchema = $jsonSchemaBuilder->forRequestQueryParams(); + + $this->assertSame([ + '$schema' => 'http://json-schema.org/draft-00/schema#', + 'type' => 'object', + 'required' => [], + 'properties' => [ + 'bar' => [ + 'type' => 'string', + ], + 'baz' => [ + '$ref' => '#/definitions/ReferencedBazProperty', + ], + ], + 'definitions' => [ + 'ReferencedBazProperty' => [ + 'type' => 'string', + ], + ], + ], $jsonSchema); + + $classReflection = new ReflectionClass(new \stdClass); + $jsonSchemaBuilder = new JsonSchemaBuilder($classReflection); + $jsonSchema = $jsonSchemaBuilder->forRequestQueryParams(); + + $this->assertNull($jsonSchema); + } + /** * @return void */ From 2525b641ba5b25ef945e8349f255f5918e36560b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B0=D1=82=D0=BE=D0=BB=D0=B8=D0=B9=20=D0=9D?= =?UTF-8?q?=D0=B5=D1=85=D0=B0=D0=B9?= Date: Mon, 8 Jun 2020 20:18:46 +0300 Subject: [PATCH 2/3] new middleware for validation the request query params --- .../RequestQueryValidationMiddleware.php | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/Middleware/RequestQueryValidationMiddleware.php diff --git a/src/Middleware/RequestQueryValidationMiddleware.php b/src/Middleware/RequestQueryValidationMiddleware.php new file mode 100644 index 0000000..d5fb4c2 --- /dev/null +++ b/src/Middleware/RequestQueryValidationMiddleware.php @@ -0,0 +1,111 @@ + + * @copyright Copyright (c) 2019, Anatoly Fenric + * @license https://github.com/sunrise-php/http-router-openapi/blob/master/LICENSE + * @link https://github.com/sunrise-php/http-router-openapi + */ + +namespace Sunrise\Http\Router\OpenApi\Middleware; + +/** + * Import classes + */ +use JsonSchema\Validator; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Server\MiddlewareInterface; +use Psr\Http\Server\RequestHandlerInterface; +use Sunrise\Http\Router\Exception\BadRequestException; +use Sunrise\Http\Router\OpenApi\Utility\JsonSchemaBuilder; +use Sunrise\Http\Router\Route; +use Sunrise\Http\Router\RouteInterface; +use ReflectionClass; +use RuntimeException; + +/** + * Import functions + */ +use function class_exists; +use function json_decode; +use function json_encode; + +/** + * RequestQueryValidationMiddleware + * + * Don't use this middleware globally! + */ +class RequestQueryValidationMiddleware implements MiddlewareInterface +{ + + /** + * Constructor of the class + * + * @throws RuntimeException + * + * @codeCoverageIgnore + */ + public function __construct() + { + if (!class_exists('JsonSchema\Validator')) { + throw new RuntimeException('To use request body validation, install the "justinrainbow/json-schema"'); + } + } + + /** + * {@inheritDoc} + * + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * + * @return ResponseInterface + */ + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface + { + $this->validate($request); + + return $handler->handle($request); + } + + /** + * Validates the given request + * + * @param ServerRequestInterface $request + * + * @return void + * + * @throws BadRequestException + */ + protected function validate(ServerRequestInterface $request) : void + { + $route = $request->getAttribute(Route::ATTR_NAME_FOR_ROUTE); + + if (!($route instanceof RouteInterface)) { + return; + } + + $operationSource = new ReflectionClass($route->getRequestHandler()); + $jsonSchemaBuilder = new JsonSchemaBuilder($operationSource); + $jsonSchema = $jsonSchemaBuilder->forRequestQueryParams(); + + if (null === $jsonSchema) { + return; + } + + $payload = json_encode($request->getQueryParams()); + $payload = json_decode($payload); + + $validator = new Validator(); + $validator->validate($payload, $jsonSchema); + + if (!$validator->isValid()) { + throw new BadRequestException('The request query parameters is not valid for this resource.', [ + 'jsonSchema' => $jsonSchema, + 'violations' => $validator->getErrors(), + ]); + } + } +} From 3be2ae4aea8d689c3589f419153e8a5616c64aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B0=D1=82=D0=BE=D0=BB=D0=B8=D0=B9=20=D0=9D?= =?UTF-8?q?=D0=B5=D1=85=D0=B0=D0=B9?= Date: Mon, 8 Jun 2020 20:21:45 +0300 Subject: [PATCH 3/3] minor changes --- src/Middleware/RequestQueryValidationMiddleware.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/RequestQueryValidationMiddleware.php b/src/Middleware/RequestQueryValidationMiddleware.php index d5fb4c2..e1b9c48 100644 --- a/src/Middleware/RequestQueryValidationMiddleware.php +++ b/src/Middleware/RequestQueryValidationMiddleware.php @@ -96,7 +96,7 @@ protected function validate(ServerRequestInterface $request) : void } $payload = json_encode($request->getQueryParams()); - $payload = json_decode($payload); + $payload = (object) json_decode($payload); $validator = new Validator(); $validator->validate($payload, $jsonSchema);