diff --git a/README.md b/README.md
index 45deee4..3684ec4 100644
--- a/README.md
+++ b/README.md
@@ -9,10 +9,14 @@
---
+## Important to understanding
+
+* [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification/blob/6ba1577240b79c9f613c2ea8d745c6ef6c832e50/versions/3.0.2.md)
+
## Installation
```bash
-composer require 'sunrise/http-router-openapi:^2.0'
+composer require 'sunrise/http-router-openapi:^2.1'
```
## QuickStart
@@ -21,7 +25,7 @@ composer require 'sunrise/http-router-openapi:^2.0'
use Psr\SimpleCache\CacheInterface;
use Sunrise\Http\Router\OpenApi\Object\Info;
use Sunrise\Http\Router\OpenApi\OpenApi;
-use Sunrise\Http\Router\Router;
+use Sunrise\Http\Router\OpenApi\RouteInterface;
$openapi = new OpenApi(new Info('Acme', '1.0.0'));
@@ -30,7 +34,11 @@ $openapi = new OpenApi(new Info('Acme', '1.0.0'));
$openapi->setCache($cache);
// Passing all routes to the openapi object:
-/** @var Router $router */
+/** @var RouteInterface[] $routes */
+$openapi->addRoute(...$routes);
+
+// When using Sunrise Router:
+/** @var \Sunrise\Http\Router\Router $router */
$openapi->addRoute(...$router->getRoutes());
```
@@ -41,7 +49,7 @@ $openapi->addRoute(...$router->getRoutes());
$openapi->toJson();
// Converting the openapi object to YAML document:
$openapi->toYaml();
-// Converting the openapi object to an array
+// Converting the openapi object to an array:
$openapi->toArray();
```
@@ -66,7 +74,6 @@ $openapi->getResponseBodyJsonSchema();
```php
use Sunrise\Http\Router\OpenApi\Middleware\RequestValidationMiddleware;
use Sunrise\Http\Router\OpenApi\OpenApi;
-use Sunrise\Http\Router\Route;
/** @var OpenApi $openapi */
$middleware = new RequestValidationMiddleware($openapi);
diff --git a/composer.json b/composer.json
index 5a26af8..742a1b3 100644
--- a/composer.json
+++ b/composer.json
@@ -23,13 +23,13 @@
],
"require": {
"php": "^7.1|^8.0",
- "sunrise/http-router": "^2.10",
"doctrine/annotations": "^1.6"
},
"require-dev": {
"phpunit/phpunit": "7.5.20|9.5.0",
"sunrise/coding-standard": "1.0.0",
"sunrise/http-factory": "1.1.0",
+ "sunrise/http-router": "^2.11",
"symfony/console": "^4.4",
"justinrainbow/json-schema": "5.2.10"
},
@@ -47,6 +47,10 @@
"test": [
"phpcs",
"XDEBUG_MODE=coverage phpunit --coverage-text"
+ ],
+ "build": [
+ "phpdoc -d src/ -t phpdoc/",
+ "XDEBUG_MODE=coverage phpunit --coverage-html coverage/"
]
}
}
diff --git a/src/AbstractAnnotation.php b/src/AbstractAnnotation.php
index 7b755ff..4f1a1ed 100644
--- a/src/AbstractAnnotation.php
+++ b/src/AbstractAnnotation.php
@@ -106,4 +106,36 @@ public function collectReferencedObjects(AnnotationReader $annotationReader) : a
return $this->referencedObjects;
}
+
+ /**
+ * Serializes the object
+ *
+ * @return array
+ */
+ public function __serialize() : array
+ {
+ // reflector can't be serialized...
+ $this->holder = null;
+
+ $data = [];
+ foreach ($this as $key => $value) {
+ $data[$key] = $value;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Unserializes the object
+ *
+ * @param array $data
+ *
+ * @return void
+ */
+ public function __unserialize(array $data) : void
+ {
+ foreach ($data as $key => $value) {
+ $this->$key = $value;
+ }
+ }
}
diff --git a/src/Annotation/OpenApi/Parameter.php b/src/Annotation/OpenApi/Parameter.php
index 005ae9c..25f437f 100644
--- a/src/Annotation/OpenApi/Parameter.php
+++ b/src/Annotation/OpenApi/Parameter.php
@@ -29,7 +29,7 @@
*
* @link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameter-object
*
- * @final Don't use the object outside of the package.
+ * @final
*/
class Parameter extends AbstractAnnotation implements ParameterInterface, ComponentInterface
{
@@ -147,7 +147,7 @@ class Parameter extends AbstractAnnotation implements ParameterInterface, Compon
/**
* {@inheritdoc}
*/
- public function getComponentName() : string
+ final public function getComponentName() : string
{
return 'parameters';
}
@@ -155,7 +155,7 @@ public function getComponentName() : string
/**
* {@inheritdoc}
*/
- public function getReferenceName() : string
+ final public function getReferenceName() : string
{
return $this->refName ?? spl_object_hash($this);
}
diff --git a/src/Bridge/Sunrise/SunriseRouteProxy.php b/src/Bridge/Sunrise/SunriseRouteProxy.php
new file mode 100644
index 0000000..33e10c7
--- /dev/null
+++ b/src/Bridge/Sunrise/SunriseRouteProxy.php
@@ -0,0 +1,127 @@
+
+ * @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\Bridge\Sunrise;
+
+/**
+ * Import classes
+ */
+use Sunrise\Http\Router\OpenApi\RouteInterface as OpenapiRouteInterface;
+use Sunrise\Http\Router\RequestHandler\CallableRequestHandler as SunriseCallableRequestHandler;
+use Sunrise\Http\Router\RouteInterface as SunriseRouteInterface;
+use ReflectionClass;
+use ReflectionMethod;
+use Reflector;
+
+/**
+ * Import functions
+ */
+use function is_array;
+use function Sunrise\Http\Router\path_parse;
+use function Sunrise\Http\Router\path_plain;
+
+/**
+ * Sunrise Route Proxy
+ */
+final class SunriseRouteProxy implements OpenapiRouteInterface
+{
+
+ /**
+ * Proxied Sunrise Route
+ *
+ * @var SunriseRouteInterface
+ */
+ private $route;
+
+ /**
+ * Constructor of the class
+ *
+ * @param SunriseRouteInterface $route
+ */
+ public function __construct(SunriseRouteInterface $route)
+ {
+ $this->route = $route;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName() : string
+ {
+ return $this->route->getName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMethods() : array
+ {
+ return $this->route->getMethods();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPlainPath() : string
+ {
+ return path_plain($this->route->getPath());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPathAttributes() : array
+ {
+ return path_parse($this->route->getPath());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSummary() : string
+ {
+ return $this->route->getSummary();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescription() : string
+ {
+ return $this->route->getDescription();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTags() : array
+ {
+ return $this->route->getTags();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHolder() : ?Reflector
+ {
+ $handler = $this->route->getRequestHandler();
+ if (!($handler instanceof SunriseCallableRequestHandler)) {
+ return new ReflectionClass($handler);
+ }
+
+ $callback = $handler->getCallback();
+ if (is_array($callback)) {
+ return new ReflectionMethod(...$callback);
+ }
+
+ return null;
+ }
+}
diff --git a/src/Command/GenerateJsonSchemaCommand.php b/src/Command/GenerateJsonSchemaCommand.php
index 689e22d..c392605 100644
--- a/src/Command/GenerateJsonSchemaCommand.php
+++ b/src/Command/GenerateJsonSchemaCommand.php
@@ -14,6 +14,7 @@
/**
* Import classes
*/
+use RuntimeException;
use Sunrise\Http\Router\OpenApi\OpenApi;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -25,6 +26,7 @@
* Import functions
*/
use function json_encode;
+use function sprintf;
/**
* Import constants
@@ -34,29 +36,47 @@
use const JSON_UNESCAPED_UNICODE;
/**
- * GenerateJsonSchemaCommand
+ * This command generates JSON schema
+ *
+ * If you cannot pass the openapi to the constructor,
+ * or your architecture has problems with autowiring,
+ * then inherit this class and override the getOpenapi method.
+ *
+ * @since 2.0.0
*/
-final class GenerateJsonSchemaCommand extends Command
+class GenerateJsonSchemaCommand extends Command
{
/**
- * Openapi instance
+ * {@inheritdoc}
+ */
+ protected static $defaultName = 'router:generate-json-schema';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $defaultDescription = 'Generates JSON schema';
+
+ /**
+ * The openapi instance
*
- * @var OpenApi
+ * @var OpenApi|null
*/
private $openapi;
/**
- * {@inheritdoc}
+ * Constructor of the class
*
- * @param OpenApi $openapi
- * @param string|null $name
+ * @param OpenApi|null $openapi
*/
- public function __construct(OpenApi $openapi, ?string $name = null)
+ public function __construct(?OpenApi $openapi = null)
{
$this->openapi = $openapi;
- parent::__construct($name ?? 'router:generate-json-schema');
+ parent::__construct();
+
+ $this->setName(static::$defaultName);
+ $this->setDescription(static::$defaultDescription);
$this->addArgument(
'operation-id',
@@ -80,47 +100,60 @@ public function __construct(OpenApi $openapi, ?string $name = null)
}
/**
- * {@inheritdoc}
+ * Gets the openapi instance
*
- * @param InputInterface $input
- * @param OutputInterface $output
+ * @return OpenApi
*
- * @return int Exit code
+ * @throws RuntimeException
+ * If the class doesn't contain the openapi instance.
*/
- public function execute(InputInterface $input, OutputInterface $output) : int
+ protected function getOpenapi() : OpenApi
{
+ if (null === $this->openapi) {
+ throw new RuntimeException(sprintf(
+ 'The %2$s() method MUST return the %1$s class instance. ' .
+ 'Pass the %1$s class instance to the constructor, or override the %2$s() method.',
+ OpenApi::class,
+ __METHOD__
+ ));
+ }
+
+ return $this->openapi;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ final protected function execute(InputInterface $input, OutputInterface $output) : int
+ {
+ $openapi = $this->getOpenapi();
$operationId = $input->getArgument('operation-id');
$operationSection = $input->getArgument('operation-section');
- $contentType = $input->getOption('content-type');
switch ($operationSection) {
case 'cookie':
- $output->writeln($this->jsonify($this->openapi->getRequestCookieJsonSchema($operationId)));
- return 0;
+ $jsonSchema = $openapi->getRequestCookieJsonSchema($operationId);
+ break;
case 'header':
- $output->writeln($this->jsonify($this->openapi->getRequestHeaderJsonSchema($operationId)));
- return 0;
+ $jsonSchema = $openapi->getRequestHeaderJsonSchema($operationId);
+ break;
case 'query':
- $output->writeln($this->jsonify($this->openapi->getRequestQueryJsonSchema($operationId)));
- return 0;
+ $jsonSchema = $openapi->getRequestQueryJsonSchema($operationId);
+ break;
case 'body':
- $output->writeln($this->jsonify($this->openapi->getRequestBodyJsonSchema($operationId, $contentType)));
- return 0;
+ $jsonSchema = $openapi->getRequestBodyJsonSchema($operationId, $input->getOption('content-type'));
+ break;
default:
$output->writeln('Unknown operation section ("cookie", "header", "query", "body")');
return 1;
}
- }
- /**
- * Jsonifies the given data and returns the result
- *
- * @param array|null $data
- *
- * @return string
- */
- private function jsonify(?array $data) : string
- {
- return json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
+ if (!isset($jsonSchema)) {
+ $output->writeln('Not enough data to build JSON schema');
+ return 1;
+ }
+
+ $output->writeln(json_encode($jsonSchema, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE));
+ return 0;
}
}
diff --git a/src/Command/GenerateOpenapiDocumentCommand.php b/src/Command/GenerateOpenapiDocumentCommand.php
index 36f7396..7e68d6b 100644
--- a/src/Command/GenerateOpenapiDocumentCommand.php
+++ b/src/Command/GenerateOpenapiDocumentCommand.php
@@ -14,6 +14,7 @@
/**
* Import classes
*/
+use RuntimeException;
use Sunrise\Http\Router\OpenApi\OpenApi;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
@@ -21,29 +22,52 @@
use Symfony\Component\Console\Output\OutputInterface;
/**
- * GenerateOpenapiDocumentCommand
+ * Import functions
*/
-final class GenerateOpenapiDocumentCommand extends Command
+use function sprintf;
+
+/**
+ * This command generates OpenAPI document
+ *
+ * If you cannot pass the openapi to the constructor,
+ * or your architecture has problems with autowiring,
+ * then inherit this class and override the getOpenapi method.
+ *
+ * @since 2.0.0
+ */
+class GenerateOpenapiDocumentCommand extends Command
{
/**
- * Openapi instance
+ * {@inheritdoc}
+ */
+ protected static $defaultName = 'router:generate-openapi-document';
+
+ /**
+ * {@inheritdoc}
+ */
+ protected static $defaultDescription = 'Generates OpenAPI document';
+
+ /**
+ * The openapi instance
*
- * @var OpenApi
+ * @var OpenApi|null
*/
private $openapi;
/**
- * {@inheritdoc}
+ * Constructor of the class
*
- * @param OpenApi $openapi
- * @param string|null $name
+ * @param OpenApi|null $openapi
*/
- public function __construct(OpenApi $openapi, ?string $name = null)
+ public function __construct(?OpenApi $openapi = null)
{
$this->openapi = $openapi;
- parent::__construct($name ?? 'router:generate-openapi-document');
+ parent::__construct();
+
+ $this->setName(static::$defaultName);
+ $this->setDescription(static::$defaultDescription);
$this->addOption(
'output-format',
@@ -55,21 +79,40 @@ public function __construct(OpenApi $openapi, ?string $name = null)
}
/**
- * {@inheritdoc}
+ * Gets the openapi instance
*
- * @param InputInterface $input
- * @param OutputInterface $output
+ * @return OpenApi
*
- * @return int Exit code
+ * @throws RuntimeException
+ * If the class doesn't contain the openapi instance.
+ */
+ protected function getOpenapi() : OpenApi
+ {
+ if (null === $this->openapi) {
+ throw new RuntimeException(sprintf(
+ 'The %2$s() method MUST return the %1$s class instance. ' .
+ 'Pass the %1$s class instance to the constructor, or override the %2$s() method.',
+ OpenApi::class,
+ __METHOD__
+ ));
+ }
+
+ return $this->openapi;
+ }
+
+ /**
+ * {@inheritdoc}
*/
- public function execute(InputInterface $input, OutputInterface $output) : int
+ final protected function execute(InputInterface $input, OutputInterface $output) : int
{
+ $openapi = $this->getOpenapi();
+
switch ($input->getOption('output-format')) {
case 'json':
- $output->writeln($this->openapi->toJson());
+ $output->writeln($openapi->toJson());
return 0;
case 'yaml':
- $output->writeln($this->openapi->toYaml());
+ $output->writeln($openapi->toYaml());
return 0;
default:
$output->writeln('Unknown output format ("json", "yaml").');
diff --git a/src/Middleware/RequestValidationMiddleware.php b/src/Middleware/RequestValidationMiddleware.php
index a6d382e..b65ae88 100644
--- a/src/Middleware/RequestValidationMiddleware.php
+++ b/src/Middleware/RequestValidationMiddleware.php
@@ -24,55 +24,91 @@
use Sunrise\Http\Router\Exception\UnsupportedMediaTypeException;
use Sunrise\Http\Router\OpenApi\OpenApi;
use Sunrise\Http\Router\RouteInterface;
-use Sunrise\Http\Router\Route;
use RuntimeException;
/**
* Import functions
*/
use function class_exists;
+use function sprintf;
use function strpos;
use function substr;
/**
- * RequestValidationMiddleware
+ * Validates the given request using all possible JSON schemes
+ *
+ * If you cannot pass the openapi to the constructor,
+ * or your architecture has problems with autowiring,
+ * then inherit this class and override the getOpenapi method.
+ *
+ * @since 2.0.0
*/
-final class RequestValidationMiddleware implements MiddlewareInterface
+class RequestValidationMiddleware implements MiddlewareInterface
{
/**
- * Openapi instance
+ * Default validation options
+ *
+ * @var int
*
- * @var OpenApi
+ * @link https://github.com/justinrainbow/json-schema/tree/4c74da50b0ca56469f5c7b1903ab5f2c7bf68f4d#configuration-options
*/
- private $openapi;
+ public const DEFAULT_VALIDATION_OPTIONS = Constraint::CHECK_MODE_TYPE_CAST|Constraint::CHECK_MODE_COERCE_TYPES;
/**
- * Validator instance
+ * The openapi instance
*
- * @var Validator
+ * @var OpenApi|null
*/
- private $validator;
+ private $openapi;
/**
- * Validation options
- *
- * @var int
+ * Cookie validation options
*
- * @link https://github.com/justinrainbow/json-schema/tree/4c74da50b0ca56469f5c7b1903ab5f2c7bf68f4d#configuration-options
+ * @var int|null
*/
private $cookieValidationOptions;
+
+ /**
+ * Header validation options
+ *
+ * @var int|null
+ */
private $headerValidationOptions;
+
+ /**
+ * Query validation options
+ *
+ * @var int|null
+ */
private $queryValidationOptions;
+
+ /**
+ * Body validation options
+ *
+ * @var int|null
+ */
private $bodyValidationOptions;
/**
* Constructor of the class
*
- * @param OpenApi $openapi
+ * @param OpenApi|null $openapi
+ * @param int|null $cookieValidationOptions
+ * @param int|null $headerValidationOptions
+ * @param int|null $queryValidationOptions
+ * @param int|null $bodyValidationOptions
+ *
+ * @throws RuntimeException
+ * If the "justinrainbow/json-schema" isn't installed.
*/
- public function __construct(OpenApi $openapi)
- {
+ public function __construct(
+ ?OpenApi $openapi = null,
+ ?int $cookieValidationOptions = self::DEFAULT_VALIDATION_OPTIONS,
+ ?int $headerValidationOptions = self::DEFAULT_VALIDATION_OPTIONS,
+ ?int $queryValidationOptions = self::DEFAULT_VALIDATION_OPTIONS,
+ ?int $bodyValidationOptions = self::DEFAULT_VALIDATION_OPTIONS
+ ) {
if (!class_exists(Validator::class)) {
// @codeCoverageIgnoreStart
throw new RuntimeException('To use request validation, install the "justinrainbow/json-schema".');
@@ -80,12 +116,33 @@ public function __construct(OpenApi $openapi)
}
$this->openapi = $openapi;
- $this->validator = new Validator();
- $this->cookieValidationOptions = Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_COERCE_TYPES;
- $this->headerValidationOptions = Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_COERCE_TYPES;
- $this->queryValidationOptions = Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_COERCE_TYPES;
- $this->bodyValidationOptions = Constraint::CHECK_MODE_TYPE_CAST | Constraint::CHECK_MODE_COERCE_TYPES;
+ $this->cookieValidationOptions = $cookieValidationOptions;
+ $this->headerValidationOptions = $headerValidationOptions;
+ $this->queryValidationOptions = $queryValidationOptions;
+ $this->bodyValidationOptions = $bodyValidationOptions;
+ }
+
+ /**
+ * Gets the openapi instance
+ *
+ * @return OpenApi
+ *
+ * @throws RuntimeException
+ * If the class doesn't contain the openapi instance.
+ */
+ protected function getOpenapi() : OpenApi
+ {
+ if (null === $this->openapi) {
+ throw new RuntimeException(sprintf(
+ 'The %2$s() method MUST return the %1$s class instance. ' .
+ 'Pass the %1$s class instance to the constructor, or override the %2$s() method.',
+ OpenApi::class,
+ __METHOD__
+ ));
+ }
+
+ return $this->openapi;
}
/**
@@ -102,13 +159,15 @@ public function __construct(OpenApi $openapi)
* @throws UnsupportedMediaTypeException
* If the request body contains an unsupported type.
*/
- public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
+ final public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
- $route = $request->getAttribute(Route::ATTR_NAME_FOR_ROUTE);
+ $route = $request->getAttribute(RouteInterface::ATTR_ROUTE);
if (!($route instanceof RouteInterface)) {
return $handler->handle($request);
}
+ $openapi = $this->getOpenapi();
+ $validator = new Validator();
$operationId = $route->getName();
// https://tools.ietf.org/html/rfc7231#section-3.1.1.1
@@ -117,24 +176,52 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
$contentType = substr($contentType, 0, $semicolon);
}
- $cookieJsonSchema = $this->openapi->getRequestCookieJsonSchema($operationId);
- if (isset($cookieJsonSchema)) {
- $this->validateRequestCookie($request, $cookieJsonSchema);
+ if (isset($this->cookieValidationOptions)) {
+ $jsonSchema = $openapi->getRequestCookieJsonSchema($operationId);
+ if (isset($jsonSchema)) {
+ $request = $this->validateRequestCookie(
+ $request,
+ $jsonSchema,
+ $validator,
+ $this->cookieValidationOptions
+ );
+ }
}
- $headerJsonSchema = $this->openapi->getRequestHeaderJsonSchema($operationId);
- if (isset($headerJsonSchema)) {
- $this->validateRequestHeader($request, $headerJsonSchema);
+ if (isset($this->headerValidationOptions)) {
+ $jsonSchema = $openapi->getRequestHeaderJsonSchema($operationId);
+ if (isset($jsonSchema)) {
+ $request = $this->validateRequestHeader(
+ $request,
+ $jsonSchema,
+ $validator,
+ $this->headerValidationOptions
+ );
+ }
}
- $queryJsonSchema = $this->openapi->getRequestQueryJsonSchema($operationId);
- if (isset($queryJsonSchema)) {
- $this->validateRequestQuery($request, $queryJsonSchema);
+ if (isset($this->queryValidationOptions)) {
+ $jsonSchema = $openapi->getRequestQueryJsonSchema($operationId);
+ if (isset($jsonSchema)) {
+ $request = $this->validateRequestQuery(
+ $request,
+ $jsonSchema,
+ $validator,
+ $this->queryValidationOptions
+ );
+ }
}
- $bodyJsonSchema = $this->openapi->getRequestBodyJsonSchema($operationId, $contentType);
- if (isset($bodyJsonSchema)) {
- $this->validateRequestBody($request, $bodyJsonSchema);
+ if (isset($this->bodyValidationOptions)) {
+ $jsonSchema = $openapi->getRequestBodyJsonSchema($operationId, $contentType);
+ if (isset($jsonSchema)) {
+ $request = $this->validateRequestBody(
+ $request,
+ $jsonSchema,
+ $validator,
+ $this->bodyValidationOptions
+ );
+ }
}
return $handler->handle($request);
@@ -145,23 +232,30 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
*
* @param ServerRequestInterface $request
* @param array $jsonSchema
+ * @param Validator $validator
+ * @param int $validationOptions
*
- * @return void
+ * @return ServerRequestInterface
+ * New request with changed data.
*
* @throws BadRequestException
- * If the request cookie isn't valid.
+ * If the validation data isn't valid.
*/
- private function validateRequestCookie(ServerRequestInterface $request, array $jsonSchema) : void
- {
+ private function validateRequestCookie(
+ ServerRequestInterface $request,
+ array $jsonSchema,
+ Validator $validator,
+ int $validationOptions
+ ) : ServerRequestInterface {
$cookies = $request->getCookieParams();
-
- $this->validator->validate($cookies, $jsonSchema, $this->cookieValidationOptions);
- if (!$this->validator->isValid()) {
+ $validator->validate($cookies, $jsonSchema, $validationOptions);
+ if (!$validator->isValid()) {
throw new BadRequestException('The request cookie is not valid for this resource.', [
- 'jsonSchema' => $jsonSchema,
- 'errors' => $this->validator->getErrors(),
+ 'errors' => $validator->getErrors(),
]);
}
+
+ return $request;
}
/**
@@ -169,26 +263,34 @@ private function validateRequestCookie(ServerRequestInterface $request, array $j
*
* @param ServerRequestInterface $request
* @param array $jsonSchema
+ * @param Validator $validator
+ * @param int $validationOptions
*
- * @return void
+ * @return ServerRequestInterface
+ * New request with changed data.
*
* @throws BadRequestException
- * If the request header isn't valid.
+ * If the validation data isn't valid.
*/
- private function validateRequestHeader(ServerRequestInterface $request, array $jsonSchema) : void
- {
+ private function validateRequestHeader(
+ ServerRequestInterface $request,
+ array $jsonSchema,
+ Validator $validator,
+ int $validationOptions
+ ) : ServerRequestInterface {
$headers = [];
foreach ($request->getHeaders() as $header => $_) {
$headers[$header] = $request->getHeaderLine($header);
}
- $this->validator->validate($headers, $jsonSchema, $this->headerValidationOptions);
- if (!$this->validator->isValid()) {
+ $validator->validate($headers, $jsonSchema, $validationOptions);
+ if (!$validator->isValid()) {
throw new BadRequestException('The request header is not valid for this resource.', [
- 'jsonSchema' => $jsonSchema,
- 'errors' => $this->validator->getErrors(),
+ 'errors' => $validator->getErrors(),
]);
}
+
+ return $request;
}
/**
@@ -196,23 +298,30 @@ private function validateRequestHeader(ServerRequestInterface $request, array $j
*
* @param ServerRequestInterface $request
* @param array $jsonSchema
+ * @param Validator $validator
+ * @param int $validationOptions
*
- * @return void
+ * @return ServerRequestInterface
+ * New request with changed data.
*
* @throws BadRequestException
- * If the request query isn't valid.
+ * If the validation data isn't valid.
*/
- private function validateRequestQuery(ServerRequestInterface $request, array $jsonSchema) : void
- {
+ private function validateRequestQuery(
+ ServerRequestInterface $request,
+ array $jsonSchema,
+ Validator $validator,
+ int $validationOptions
+ ) : ServerRequestInterface {
$query = $request->getQueryParams();
-
- $this->validator->validate($query, $jsonSchema, $this->queryValidationOptions);
- if (!$this->validator->isValid()) {
+ $validator->validate($query, $jsonSchema, $validationOptions);
+ if (!$validator->isValid()) {
throw new BadRequestException('The request query is not valid for this resource.', [
- 'jsonSchema' => $jsonSchema,
- 'errors' => $this->validator->getErrors(),
+ 'errors' => $validator->getErrors(),
]);
}
+
+ return $request->withQueryParams($query);
}
/**
@@ -220,22 +329,29 @@ private function validateRequestQuery(ServerRequestInterface $request, array $js
*
* @param ServerRequestInterface $request
* @param array $jsonSchema
+ * @param Validator $validator
+ * @param int $validationOptions
*
- * @return void
+ * @return ServerRequestInterface
+ * New request with changed data.
*
* @throws BadRequestException
- * If the request body isn't valid.
+ * If the validation data isn't valid.
*/
- private function validateRequestBody(ServerRequestInterface $request, array $jsonSchema) : void
- {
+ private function validateRequestBody(
+ ServerRequestInterface $request,
+ array $jsonSchema,
+ Validator $validator,
+ int $validationOptions
+ ) : ServerRequestInterface {
$body = $request->getParsedBody();
-
- $this->validator->validate($body, $jsonSchema, $this->bodyValidationOptions);
- if (!$this->validator->isValid()) {
+ $validator->validate($body, $jsonSchema, $validationOptions);
+ if (!$validator->isValid()) {
throw new BadRequestException('The request body is not valid for this resource.', [
- 'jsonSchema' => $jsonSchema,
- 'errors' => $this->validator->getErrors(),
+ 'errors' => $validator->getErrors(),
]);
}
+
+ return $request->withParsedBody($body);
}
}
diff --git a/src/Object/SecurityRequirement.php b/src/Object/SecurityRequirement.php
index 7962f3a..5e6c771 100644
--- a/src/Object/SecurityRequirement.php
+++ b/src/Object/SecurityRequirement.php
@@ -21,19 +21,6 @@
*
* Lists the required security schemes to execute this operation.
*
- * The name used for each property MUST correspond to a security scheme declared in the Security Schemes under the
- * Components Object. Security Requirement Objects that contain multiple schemes require that all schemes MUST be
- * satisfied for a request to be authorized. This enables support for scenarios where multiple query parameters or HTTP
- * headers are required to convey security information. When a list of Security Requirement Objects is defined on the
- * OpenAPI Object or Operation Object, only one of the Security Requirement Objects in the list needs to be satisfied to
- * authorize the request.
- *
- * Each name MUST correspond to a security scheme which is declared in the Security Schemes under the Components Object.
- * If the security scheme is of type "oauth2" or "openIdConnect", then the value is a list of scope names required for
- * the execution, and the list MAY be empty if authorization does not require a specified scope. For other security
- * scheme types, the array MAY contain a list of role names which are required for the execution, but are not otherwise
- * defined or exchanged in-band.
- *
* @link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#security-requirement-object
*/
final class SecurityRequirement implements ObjectInterface
diff --git a/src/Object/SecurityScheme.php b/src/Object/SecurityScheme.php
index acb8a73..356e79a 100644
--- a/src/Object/SecurityScheme.php
+++ b/src/Object/SecurityScheme.php
@@ -22,12 +22,6 @@
*
* Defines a security scheme that can be used by the operations.
*
- * Supported schemes are HTTP authentication, an API key (either as a header, a cookie parameter or as a query
- * parameter), mutual TLS (use of a client certificate), OAuth2's common flows (implicit, password, client credentials
- * and authorization code) as defined in RFC6749, and OpenID Connect Discovery. Please note that as of 2020, the
- * implicit flow is about to be deprecated by OAuth 2.0 Security Best Current Practice. Recommended for most use case is
- * Authorization Code Grant flow with PKCE.
- *
* @link https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#security-scheme-object
* @link https://datatracker.ietf.org/doc/html/rfc6749
* @link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-discovery-06
diff --git a/src/OpenApi.php b/src/OpenApi.php
index 345a8fe..0a1a247 100644
--- a/src/OpenApi.php
+++ b/src/OpenApi.php
@@ -20,29 +20,26 @@
use Sunrise\Http\Router\OpenApi\Annotation\OpenApi\Operation;
use Sunrise\Http\Router\OpenApi\Annotation\OpenApi\Parameter;
use Sunrise\Http\Router\OpenApi\Annotation\OpenApi\Schema;
+use Sunrise\Http\Router\OpenApi\Bridge\Sunrise\SunriseRouteProxy;
use Sunrise\Http\Router\OpenApi\Object\ExternalDocumentation;
use Sunrise\Http\Router\OpenApi\Object\Info;
use Sunrise\Http\Router\OpenApi\Object\SecurityRequirement;
use Sunrise\Http\Router\OpenApi\Object\Server;
use Sunrise\Http\Router\OpenApi\Object\Tag;
use Sunrise\Http\Router\OpenApi\Utility\OperationConverter;
-use Sunrise\Http\Router\RequestHandler\CallableRequestHandler;
-use Sunrise\Http\Router\RouteInterface;
+use Sunrise\Http\Router\RouteInterface as SunriseRouteInterface;
use ReflectionClass;
-use ReflectionMethod;
-use Reflector;
use RuntimeException;
+use TypeError;
/**
* Import functions
*/
-use function Sunrise\Http\Router\path_parse;
-use function Sunrise\Http\Router\path_plain;
use function extension_loaded;
use function hash;
-use function is_array;
use function json_encode;
use function strtolower;
+use function sprintf;
use function yaml_emit;
/**
@@ -52,7 +49,7 @@
use const JSON_UNESCAPED_SLASHES;
use const JSON_UNESCAPED_UNICODE;
use const YAML_ANY_BREAK;
-use const YAML_ANY_ENCODING;
+use const YAML_UTF8_ENCODING;
/**
* OAS OpenAPI Object
@@ -347,13 +344,28 @@ public function setCache(?CacheInterface $cache) : void
}
/**
- * @param RouteInterface ...$routes
+ * @param RouteInterface|SunriseRouteInterface ...$routes
*
* @return void
+ *
+ * @throws TypeError
*/
- public function addRoute(RouteInterface ...$routes) : void
+ public function addRoute(...$routes) : void
{
foreach ($routes as $route) {
+ // BC for Sunrise Router...
+ if ($route instanceof SunriseRouteInterface) {
+ $route = new SunriseRouteProxy($route);
+ }
+
+ if (!($route instanceof RouteInterface)) {
+ throw new TypeError(sprintf(
+ 'The %s method expects an object that implements %s.',
+ __METHOD__,
+ RouteInterface::class
+ ));
+ }
+
$this->routes[$route->getName()] = $route;
}
}
@@ -384,7 +396,7 @@ public function toYaml() : string
// @codeCoverageIgnoreEnd
}
- return yaml_emit($this->toArray(), YAML_ANY_ENCODING, YAML_ANY_BREAK);
+ return yaml_emit($this->toArray(), YAML_UTF8_ENCODING, YAML_ANY_BREAK);
}
/**
@@ -441,8 +453,10 @@ private function build() : array
if (isset($this->routes[$operation->operationId])) {
$this->addComponent(...$operation->getReferencedObjects());
- $path = path_plain($this->routes[$operation->operationId]->getPath());
- foreach ($this->routes[$operation->operationId]->getMethods() as $method) {
+ $path = $this->routes[$operation->operationId]->getPlainPath();
+ $methods = $this->routes[$operation->operationId]->getMethods();
+
+ foreach ($methods as $method) {
// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#fixed-fields-7
$lcmethod = strtolower($method);
@@ -488,7 +502,7 @@ private function getOperations() : array
$operations = [];
foreach ($this->routes as $route) {
- $operation = $this->getRouteOperation($route, $annotationReader);
+ $operation = $this->fetchOperation($route, $annotationReader);
if (isset($operation)) {
$operations[$operation->operationId] = $operation;
}
@@ -498,19 +512,16 @@ private function getOperations() : array
}
/**
- * Gets the given route operation
- *
- * TODO: Can be moved to a new abstract layer,
- * which would make support for any router...
+ * Fetches an operation object from the given route
*
* @param RouteInterface $route
* @param AnnotationReader $annotationReader
*
* @return Operation|null
*/
- private function getRouteOperation(RouteInterface $route, AnnotationReader $annotationReader) : ?Operation
+ private function fetchOperation(RouteInterface $route, AnnotationReader $annotationReader) : ?Operation
{
- $holder = $this->getRouteHolder($route);
+ $holder = $route->getHolder();
if (null === $holder) {
return null;
}
@@ -523,35 +534,39 @@ private function getRouteOperation(RouteInterface $route, AnnotationReader $anno
return null;
}
- // override the operation ID...
$operation->operationId = $route->getName();
- if (empty($operation->summary) && !empty($summary = $route->getSummary())) {
+ if (null === $operation->summary && '' !== ($summary = $route->getSummary())) {
$operation->summary = $summary;
}
- if (empty($operation->description) && !empty($description = $route->getDescription())) {
+ if (null === $operation->description && '' !== ($description = $route->getDescription())) {
$operation->description = $description;
}
- if (empty($operation->tags) && !empty($tags = $route->getTags())) {
+ if (null === $operation->tags && [] !== ($tags = $route->getTags())) {
$operation->tags = $tags;
}
- $attributes = path_parse($route->getPath());
+ $attributes = $route->getPathAttributes();
foreach ($attributes as $attribute) {
- $parameter = new Parameter();
- $parameter->in = 'path';
- $parameter->name = $attribute['name'];
- $parameter->required = !$attribute['isOptional'];
-
- if (isset($attribute['pattern'])) {
- $parameter->schema = new Schema();
- $parameter->schema->type = 'string';
- $parameter->schema->pattern = $attribute['pattern'];
- }
+ if (isset($attribute['name'])) {
+ $parameter = new Parameter();
+ $parameter->in = 'path';
+ $parameter->name = $attribute['name'];
+
+ if (isset($attribute['pattern'])) {
+ $parameter->schema = new Schema();
+ $parameter->schema->type = 'string';
+ $parameter->schema->pattern = $attribute['pattern'];
+ }
+
+ if (isset($attribute['isOptional'])) {
+ $parameter->required = !$attribute['isOptional'];
+ }
- $operation->parameters[] = $parameter;
+ $operation->parameters[] = $parameter;
+ }
}
$operation->setHolder($holder);
@@ -559,31 +574,4 @@ private function getRouteOperation(RouteInterface $route, AnnotationReader $anno
return $operation;
}
-
- /**
- * Gets the given route holder
- *
- * @param RouteInterface $route
- *
- * @return ReflectionClass|ReflectionMethod|null
- */
- private function getRouteHolder(RouteInterface $route) : ?Reflector
- {
- $holder = $route->getHolder();
- if (isset($holder)) {
- return $holder;
- }
-
- $handler = $route->getRequestHandler();
- if (!($handler instanceof CallableRequestHandler)) {
- return new ReflectionClass($handler);
- }
-
- $callback = $handler->getCallback();
- if (is_array($callback)) {
- return new ReflectionMethod(...$callback);
- }
-
- return null;
- }
}
diff --git a/src/RouteInterface.php b/src/RouteInterface.php
new file mode 100644
index 0000000..4ce972b
--- /dev/null
+++ b/src/RouteInterface.php
@@ -0,0 +1,90 @@
+
+ * @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;
+
+/**
+ * Import classes
+ */
+use ReflectionClass;
+use ReflectionMethod;
+use Reflector;
+
+/**
+ * RouteInterface
+ */
+interface RouteInterface
+{
+
+ /**
+ * Gets the route name (aka ID)
+ *
+ * @return string
+ */
+ public function getName() : string;
+
+ /**
+ * Gets the route methods
+ *
+ * @return string[]
+ */
+ public function getMethods() : array;
+
+ /**
+ * Gets the route plain path
+ *
+ * @return string
+ */
+ public function getPlainPath() : string;
+
+ /**
+ * Gets the route path attributes
+ *
+ * ```php
+ * [
+ * 'name' => 'foo',
+ * 'pattern' => '\w+',
+ * 'isOptional' => false,
+ * ]
+ * ```
+ *
+ * @return array[]
+ */
+ public function getPathAttributes() : array;
+
+ /**
+ * Gets the route summary
+ *
+ * @return string
+ */
+ public function getSummary() : string;
+
+ /**
+ * Gets the route description
+ *
+ * @return string
+ */
+ public function getDescription() : string;
+
+ /**
+ * Gets the route tags
+ *
+ * @return string[]
+ */
+ public function getTags() : array;
+
+ /**
+ * Gets the route holder
+ *
+ * @return ReflectionClass|ReflectionMethod|null
+ */
+ public function getHolder() : ?Reflector;
+}
diff --git a/src/Test/OpenapiTestKit.php b/src/Test/OpenapiTestKit.php
index b3ea866..3f3a3ca 100644
--- a/src/Test/OpenapiTestKit.php
+++ b/src/Test/OpenapiTestKit.php
@@ -43,7 +43,7 @@ trait OpenapiTestKit
{
/**
- * Gets Openapi instance
+ * Gets the openapi instance
*
* @return OpenApi
*/
@@ -62,7 +62,7 @@ protected function assertResponseBodyMatchesDescription(string $operationId, Res
{
if (!class_exists(Validator::class)) {
// @codeCoverageIgnoreStart
- $this->markTestSkipped('To use Openapi Test Kit, install the "justinrainbow/json-schema".');
+ $this->markTestSkipped('To use OpenAPI Test Kit, install the "justinrainbow/json-schema".');
// @codeCoverageIgnoreEnd
}
diff --git a/src/Utility/OperationConverter.php b/src/Utility/OperationConverter.php
index 24c4ce9..0dae17e 100644
--- a/src/Utility/OperationConverter.php
+++ b/src/Utility/OperationConverter.php
@@ -37,7 +37,7 @@ final class OperationConverter
{
/**
- * Blank JSON Schema
+ * Blank JSON schema
*
* @var array
*
diff --git a/tests/Command/GenerateJsonSchemaCommandTest.php b/tests/Command/GenerateJsonSchemaCommandTest.php
index ddeb8cc..7ea13c6 100644
--- a/tests/Command/GenerateJsonSchemaCommandTest.php
+++ b/tests/Command/GenerateJsonSchemaCommandTest.php
@@ -6,9 +6,11 @@
* Import classes
*/
use PHPUnit\Framework\TestCase;
+use Sunrise\Http\Router\OpenApi\OpenApi;
use Sunrise\Http\Router\OpenApi\Command\GenerateJsonSchemaCommand;
use Sunrise\Http\Router\OpenApi\Tests\Fixtures\OpenapiAwareTrait;
use Symfony\Component\Console\Tester\CommandTester;
+use RuntimeException;
/**
* GenerateJsonSchemaCommandTest
@@ -50,5 +52,83 @@ public function testRun() : void
'operation-id' => 'users.list',
'operation-section' => 'unknown',
]));
+
+ $this->assertSame(1, $commandTester->execute([
+ 'operation-id' => 'unknown',
+ 'operation-section' => 'body',
+ ]));
+ }
+
+ /**
+ * @return void
+ */
+ public function testRunInheritedCommand() : void
+ {
+ // @codingStandardsIgnoreStart
+ $command = new class($this->getOpenapi()) extends GenerateJsonSchemaCommand {
+ private $_openapi;
+
+ public function __construct(OpenApi $openapi) {
+ $this->_openapi = $openapi;
+ parent::__construct(null);
+ }
+
+ protected function getOpenapi() : OpenApi {
+ return $this->_openapi;
+ }
+ };
+ // @codingStandardsIgnoreEnd
+
+ $this->assertSame('router:generate-json-schema', $command->getName());
+
+ $commandTester = new CommandTester($command);
+
+ $this->assertSame(0, $commandTester->execute([
+ 'operation-id' => 'users.create',
+ 'operation-section' => 'body',
+ ]));
+ }
+
+ /**
+ * @return void
+ */
+ public function testRunRenamedCommand() : void
+ {
+ // @codingStandardsIgnoreStart
+ $command = new class ($this->getOpenapi()) extends GenerateJsonSchemaCommand {
+ protected static $defaultName = 'foo';
+ protected static $defaultDescription = 'bar';
+
+ public function __construct(OpenApi $openapi) {
+ parent::__construct($openapi);
+ }
+ };
+ // @codingStandardsIgnoreEnd
+
+ $this->assertSame('foo', $command->getName());
+ $this->assertSame('bar', $command->getDescription());
+
+ $commandTester = new CommandTester($command);
+
+ $this->assertSame(0, $commandTester->execute([
+ 'operation-id' => 'users.create',
+ 'operation-section' => 'body',
+ ]));
+ }
+
+ /**
+ * @return void
+ */
+ public function testRunWithoutOpenapi() : void
+ {
+ $command = new GenerateJsonSchemaCommand();
+ $commandTester = new CommandTester($command);
+
+ $this->expectException(RuntimeException::class);
+
+ $commandTester->execute([
+ 'operation-id' => 'users.create',
+ 'operation-section' => 'body',
+ ]);
}
}
diff --git a/tests/Command/GenerateOpenapiDocumentCommandTest.php b/tests/Command/GenerateOpenapiDocumentCommandTest.php
index 27e23ee..98f6aef 100644
--- a/tests/Command/GenerateOpenapiDocumentCommandTest.php
+++ b/tests/Command/GenerateOpenapiDocumentCommandTest.php
@@ -6,9 +6,11 @@
* Import classes
*/
use PHPUnit\Framework\TestCase;
+use Sunrise\Http\Router\OpenApi\OpenApi;
use Sunrise\Http\Router\OpenApi\Command\GenerateOpenapiDocumentCommand;
use Sunrise\Http\Router\OpenApi\Tests\Fixtures\OpenapiAwareTrait;
use Symfony\Component\Console\Tester\CommandTester;
+use RuntimeException;
/**
* GenerateOpenapiDocumentCommandTest
@@ -37,4 +39,68 @@ public function testRun() : void
'--output-format' => 'unknown',
]));
}
+
+ /**
+ * @return void
+ */
+ public function testRunInheritedCommand() : void
+ {
+ // @codingStandardsIgnoreStart
+ $command = new class($this->getOpenapi()) extends GenerateOpenapiDocumentCommand {
+ private $_openapi;
+
+ public function __construct(OpenApi $openapi) {
+ $this->_openapi = $openapi;
+ parent::__construct(null);
+ }
+
+ protected function getOpenapi() : OpenApi {
+ return $this->_openapi;
+ }
+ };
+ // @codingStandardsIgnoreEnd
+
+ $this->assertSame('router:generate-openapi-document', $command->getName());
+
+ $commandTester = new CommandTester($command);
+
+ $this->assertSame(0, $commandTester->execute([]));
+ }
+
+ /**
+ * @return void
+ */
+ public function testRunRenamedCommand() : void
+ {
+ // @codingStandardsIgnoreStart
+ $command = new class ($this->getOpenapi()) extends GenerateOpenapiDocumentCommand {
+ protected static $defaultName = 'foo';
+ protected static $defaultDescription = 'bar';
+
+ public function __construct(OpenApi $openapi) {
+ parent::__construct($openapi);
+ }
+ };
+ // @codingStandardsIgnoreEnd
+
+ $this->assertSame('foo', $command->getName());
+ $this->assertSame('bar', $command->getDescription());
+
+ $commandTester = new CommandTester($command);
+
+ $this->assertSame(0, $commandTester->execute([]));
+ }
+
+ /**
+ * @return void
+ */
+ public function testRunWithoutOpenapi() : void
+ {
+ $command = new GenerateOpenapiDocumentCommand();
+ $commandTester = new CommandTester($command);
+
+ $this->expectException(RuntimeException::class);
+
+ $this->assertSame(0, $commandTester->execute([]));
+ }
}
diff --git a/tests/Middleware/RequestValidationMiddlewareTest.php b/tests/Middleware/RequestValidationMiddlewareTest.php
index 4cd9c4f..0c52a0d 100644
--- a/tests/Middleware/RequestValidationMiddlewareTest.php
+++ b/tests/Middleware/RequestValidationMiddlewareTest.php
@@ -12,7 +12,9 @@
use Sunrise\Http\Router\Exception\UnsupportedMediaTypeException;
use Sunrise\Http\Router\OpenApi\Middleware\RequestValidationMiddleware;
use Sunrise\Http\Router\OpenApi\Tests\Fixtures\OpenapiAwareTrait;
+use Sunrise\Http\Router\OpenApi\Openapi;
use Sunrise\Http\Router\Route;
+use RuntimeException;
/**
* RequestValidationMiddlewareTest
@@ -228,4 +230,53 @@ public function testRunWithoutRoute() : void
$this->assertSame(201, $response->getStatusCode());
}
+
+ /**
+ * @return void
+ */
+ public function testRunWithoutOpenapi() : void
+ {
+ $route = $this->getRouter()->getRoute('users.create');
+
+ $request = (new ServerRequestFactory)
+ ->createServerRequest('GET', '/')
+ ->withAttribute(Route::ATTR_NAME_FOR_ROUTE, $route);
+
+ $middleware = new RequestValidationMiddleware();
+
+ $this->expectException(RuntimeException::class);
+
+ $middleware->process($request, $route);
+ }
+
+ /**
+ * @return void
+ */
+ public function testRunInheritedMiddleware() : void
+ {
+ // @codingStandardsIgnoreStart
+ $middleware = new class($this->getOpenapi()) extends RequestValidationMiddleware {
+ private $_openapi;
+
+ public function __construct(Openapi $openapi) {
+ $this->_openapi = $openapi;
+ }
+
+ protected function getOpenapi() : OpenApi {
+ return $this->_openapi;
+ }
+ };
+ // @codingStandardsIgnoreEnd
+
+ $route = $this->getRouter()->getRoute('users.list');
+
+ $request = (new ServerRequestFactory)
+ ->createServerRequest('GET', '/')
+ ->withCookieParams(['limit' => '100'])
+ ->withAttribute(Route::ATTR_NAME_FOR_ROUTE, $route);
+
+ $response = $middleware->process($request, $route);
+
+ $this->assertSame(200, $response->getStatusCode());
+ }
}
diff --git a/tests/OpenApiTest.php b/tests/OpenApiTest.php
index bba4dab..cafebdc 100644
--- a/tests/OpenApiTest.php
+++ b/tests/OpenApiTest.php
@@ -20,6 +20,7 @@
use Sunrise\Http\Router\OpenApi\Tests\Fixtures\SomeApp\Controller\InvalidController;
use Sunrise\Http\Router\RequestHandler\CallableRequestHandler;
use Sunrise\Http\Router\Route;
+use TypeError;
/**
* Import functions
@@ -313,13 +314,11 @@ public function testBuildCache() : void
$document = $openapi->toArray();
- $this->assertArrayHasKey($openapi->getBuildCacheKey(), $cache->storage);
+ $this->assertSame($document, $cache->get($openapi->getBuildCacheKey()));
- $this->assertSame($document, $cache->storage[$openapi->getBuildCacheKey()]);
+ $cache->set($openapi->getBuildCacheKey(), ['foo' => 'bar']);
- $cache->storage[$openapi->getBuildCacheKey()] = ['foo' => 'bar'];
-
- $this->assertSame($cache->storage[$openapi->getBuildCacheKey()], $openapi->toArray());
+ $this->assertSame($cache->get($openapi->getBuildCacheKey()), $openapi->toArray());
}
/**
@@ -332,24 +331,29 @@ public function testOperationsCache() : void
$openapi = $this->getOpenapi();
$openapi->setCache($cache);
- // background caching of operations...
- $openapi->toArray();
-
- $this->assertArrayHasKey($openapi->getOperationsCacheKey(), $cache->storage);
-
- $this->assertArrayHasKey('home', $cache->storage[$openapi->getOperationsCacheKey()]);
-
- $testOperation = new Operation();
- $testOperation->operationId = 'home';
- $testOperation->summary = '7AC99FFC-6AB0-4EF1-A75E-49E0B85E7849';
-
- $cache->storage[$openapi->getBuildCacheKey()] = null;
- $cache->storage[$openapi->getOperationsCacheKey()] = [];
- $cache->storage[$openapi->getOperationsCacheKey()][$testOperation->operationId] = $testOperation;
+ $operation = new Operation();
+ $operation->operationId = 'home';
+ $operation->description = '7AC99FFC-6AB0-4EF1-A75E-49E0B85E7849';
- $document = $openapi->toArray();
+ $cache->set($openapi->getOperationsCacheKey(), [
+ $operation->operationId => $operation,
+ ]);
- $this->assertSame($testOperation->summary, $document['paths']['/']['get']['summary'] ?? null);
+ $this->assertSame([
+ 'openapi' => '3.0.2',
+ 'info' => [
+ 'title' => 'Some application',
+ 'version' => '1.0.0',
+ ],
+ 'paths' => [
+ '/' => [
+ 'get' => [
+ 'operationId' => $operation->operationId,
+ 'description' => $operation->description,
+ ],
+ ],
+ ],
+ ], $openapi->toArray());
}
/**
@@ -489,4 +493,16 @@ public function testReferenceToClassPropertyWithoutTarget() : void
$openapi->toArray();
}
+
+ /**
+ * @return void
+ */
+ public function testAddInvalidRoute() : void
+ {
+ $openapi = $this->getOpenapi();
+
+ $this->expectException(TypeError::class);
+
+ $openapi->addRoute(new \stdClass);
+ }
}
diff --git a/tests/fixtures/CacheAwareTrait.php b/tests/fixtures/CacheAwareTrait.php
index dba7397..60ef288 100644
--- a/tests/fixtures/CacheAwareTrait.php
+++ b/tests/fixtures/CacheAwareTrait.php
@@ -4,6 +4,9 @@
use Psr\SimpleCache\CacheInterface;
+use function serialize;
+use function unserialize;
+
trait CacheAwareTrait
{
private static $permanentCacheStorage = [];
@@ -21,7 +24,7 @@ private function getCache(bool $permanentStorage = false) : CacheInterface
}
$cache->method('get')->will($this->returnCallback(function ($key) use ($cache) {
- return $cache->storage[$key] ?? null;
+ return isset($cache->storage[$key]) ? unserialize($cache->storage[$key]) : null;
}));
$cache->method('has')->will($this->returnCallback(function ($key) use ($cache) {
@@ -29,7 +32,7 @@ private function getCache(bool $permanentStorage = false) : CacheInterface
}));
$cache->method('set')->will($this->returnCallback(function ($key, $value) use ($cache) {
- $cache->storage[$key] = $value;
+ $cache->storage[$key] = serialize($value);
}));
return $cache;