Skip to content

Commit

Permalink
Merge pull request #16 from sunrise-php/release/v1.5.0
Browse files Browse the repository at this point in the history
v1.5.0
  • Loading branch information
fenric authored Jun 8, 2020
2 parents e4b6dca + 3be2ae4 commit 998ac32
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 0 deletions.
111 changes: 111 additions & 0 deletions src/Middleware/RequestQueryValidationMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php declare(strict_types=1);

/**
* It's free open-source software released under the MIT License.
*
* @author Anatoly Fenric <anatoly@fenric.ru>
* @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 = (object) 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(),
]);
}
}
}
56 changes: 56 additions & 0 deletions src/Utility/JsonSchemaBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,6 +27,7 @@
* Import functions
*/
use function array_keys;
use function array_walk;
use function array_walk_recursive;
use function str_replace;

Expand Down Expand Up @@ -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
*
Expand Down
80 changes: 80 additions & 0 deletions tests/Utility/JsonSchemaBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down

0 comments on commit 998ac32

Please sign in to comment.