Support validation for swagger. (#5395)

This commit is contained in:
李铭昕 2023-02-11 13:05:18 +08:00 committed by GitHub
parent e1b7001483
commit 34c083ad0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 390 additions and 51 deletions

View File

@ -11,6 +11,7 @@
- [#5376](https://github.com/hyperf/hyperf/pull/5376) Support coroutine server stats for `hyperf/metric`.
- [#5379](https://github.com/hyperf/hyperf/pull/5379) Added log records when nacos heartbeat failed.
- [#5389](https://github.com/hyperf/hyperf/pull/5389) Added swagger support.
- [#5395](https://github.com/hyperf/hyperf/pull/5395) Support validation for swagger.
# v3.0.5 - 2023-02-05

View File

@ -25,9 +25,8 @@
"Hyperf\\Swagger\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
}
"suggest": {
"hyperf/validation": "Required to use SwaggerRequest."
},
"config": {
"sort-packages": true

View File

@ -12,37 +12,21 @@ declare(strict_types=1);
namespace Hyperf\Swagger\Annotation;
use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\Di\Annotation\MultipleAnnotation;
trait AnnotationTrait
{
public function collectClass(string $className): void
{
$annotation = AnnotationCollector::getClassAnnotation($className, static::class);
AnnotationCollector::collectClass($className, static::class, $this->formatAnnotation($annotation));
AnnotationCollector::collectClass($className, static::class, $this);
}
public function collectMethod(string $className, ?string $target): void
{
$annotation = AnnotationCollector::getClassMethodAnnotation($className, $target)[static::class] ?? null;
AnnotationCollector::collectMethod($className, $target, static::class, $this->formatAnnotation($annotation));
AnnotationCollector::collectMethod($className, $target, static::class, $this);
}
public function collectProperty(string $className, ?string $target): void
{
$annotation = AnnotationCollector::getClassPropertyAnnotation($className, $target)[static::class] ?? null;
AnnotationCollector::collectProperty($className, $target, static::class, $this->formatAnnotation($annotation));
}
protected function formatAnnotation(?MultipleAnnotation $annotation): MultipleAnnotation
{
if ($annotation instanceof MultipleAnnotation) {
return $annotation->insert($this);
}
return new MultipleAnnotation($this);
AnnotationCollector::collectProperty($className, $target, static::class, $this);
}
}

View File

@ -17,5 +17,5 @@ use Hyperf\Di\Annotation\AnnotationInterface;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Get extends \OpenApi\Attributes\Get implements AnnotationInterface
{
use AnnotationTrait;
use MultipleAnnotationTrait;
}

View File

@ -17,5 +17,5 @@ use Hyperf\Di\Annotation\AnnotationInterface;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Head extends \OpenApi\Attributes\Head implements AnnotationInterface
{
use AnnotationTrait;
use MultipleAnnotationTrait;
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Swagger\Annotation;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class JsonContent extends \OpenApi\Attributes\JsonContent
{
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Swagger\Annotation;
use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\Di\Annotation\MultipleAnnotation;
trait MultipleAnnotationTrait
{
public function collectClass(string $className): void
{
$annotation = AnnotationCollector::getClassAnnotation($className, static::class);
AnnotationCollector::collectClass($className, static::class, $this->formatAnnotation($annotation));
}
public function collectMethod(string $className, ?string $target): void
{
$annotation = AnnotationCollector::getClassMethodAnnotation($className, $target)[static::class] ?? null;
AnnotationCollector::collectMethod($className, $target, static::class, $this->formatAnnotation($annotation));
}
public function collectProperty(string $className, ?string $target): void
{
$annotation = AnnotationCollector::getClassPropertyAnnotation($className, $target)[static::class] ?? null;
AnnotationCollector::collectProperty($className, $target, static::class, $this->formatAnnotation($annotation));
}
protected function formatAnnotation(?MultipleAnnotation $annotation): MultipleAnnotation
{
if ($annotation instanceof MultipleAnnotation) {
return $annotation->insert($this);
}
return new MultipleAnnotation($this);
}
}

View File

@ -17,5 +17,5 @@ use Hyperf\Di\Annotation\AnnotationInterface;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Patch extends \OpenApi\Attributes\Patch implements AnnotationInterface
{
use AnnotationTrait;
use MultipleAnnotationTrait;
}

View File

@ -17,5 +17,5 @@ use Hyperf\Di\Annotation\AnnotationInterface;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::IS_REPEATABLE)]
class PathParameter extends \OpenApi\Attributes\PathParameter implements AnnotationInterface
{
use AnnotationTrait;
use MultipleAnnotationTrait;
}

View File

@ -17,5 +17,5 @@ use Hyperf\Di\Annotation\AnnotationInterface;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Post extends \OpenApi\Attributes\Post implements AnnotationInterface
{
use AnnotationTrait;
use MultipleAnnotationTrait;
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Swagger\Annotation;
use Attribute;
use OpenApi\Attributes\AdditionalProperties;
use OpenApi\Attributes\Discriminator;
use OpenApi\Attributes\ExternalDocumentation;
use OpenApi\Attributes\Items;
use OpenApi\Attributes\Xml;
use OpenApi\Generator;
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::TARGET_CLASS_CONSTANT | Attribute::IS_REPEATABLE)]
class Property extends \OpenApi\Attributes\Property
{
public function __construct(
?string $property = null,
object|string|null $ref = null,
?string $schema = null,
?string $title = null,
?string $description = null,
?int $maxProperties = null,
?int $minProperties = null,
?array $required = null,
?array $properties = null,
?string $type = null,
?string $format = null,
?Items $items = null,
?string $collectionFormat = null,
mixed $default = Generator::UNDEFINED,
$maximum = null,
?bool $exclusiveMaximum = null,
$minimum = null,
?bool $exclusiveMinimum = null,
?int $maxLength = null,
?int $minLength = null,
?int $maxItems = null,
?int $minItems = null,
?bool $uniqueItems = null,
?string $pattern = null,
array|string|null $enum = null,
?Discriminator $discriminator = null,
?bool $readOnly = null,
?bool $writeOnly = null,
?Xml $xml = null,
?ExternalDocumentation $externalDocs = null,
mixed $example = Generator::UNDEFINED,
?bool $nullable = null,
?bool $deprecated = null,
?array $allOf = null,
?array $anyOf = null,
?array $oneOf = null,
AdditionalProperties|bool|null $additionalProperties = null,
?array $x = null,
?array $attachables = null,
public mixed $rules = null
) {
parent::__construct(
$property,
$ref,
$schema,
$title,
$description,
$maxProperties,
$minProperties,
$required,
$properties,
$type,
$format,
$items,
$collectionFormat,
$default,
$maximum,
$exclusiveMaximum,
$minimum,
$exclusiveMinimum,
$maxLength,
$minLength,
$maxItems,
$minItems,
$uniqueItems,
$pattern,
$enum,
$discriminator,
$readOnly,
$writeOnly,
$xml,
$externalDocs,
$example,
$nullable,
$deprecated,
$allOf,
$anyOf,
$oneOf,
$additionalProperties,
$x,
$attachables
);
}
}

View File

@ -17,5 +17,5 @@ use Hyperf\Di\Annotation\AnnotationInterface;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Put extends \OpenApi\Attributes\Put implements AnnotationInterface
{
use AnnotationTrait;
use MultipleAnnotationTrait;
}

View File

@ -22,7 +22,7 @@ use OpenApi\Generator;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER | Attribute::IS_REPEATABLE)]
class QueryParameter extends \OpenApi\Attributes\QueryParameter implements AnnotationInterface
{
use AnnotationTrait;
use MultipleAnnotationTrait;
public function __construct(
?string $parameter = null,

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Swagger\Annotation;
use Attribute;
use Hyperf\Di\Annotation\AnnotationInterface;
use OpenApi\Attributes\Attachable;
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\XmlContent;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
class RequestBody extends \OpenApi\Attributes\RequestBody implements AnnotationInterface
{
use AnnotationTrait;
public mixed $_content = null;
public function __construct(
object|string|null $ref = null,
?string $request = null,
?string $description = null,
?bool $required = null,
JsonContent|array|Attachable|XmlContent|null $content = null,
?array $x = null,
?array $attachables = null
) {
parent::__construct($ref, $request, $description, $required, $content, $x, $attachables);
$this->_content = $content;
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Swagger\Exception;
class RuntimeException extends \RuntimeException
{
}

View File

@ -28,6 +28,7 @@ use Hyperf\Swagger\Annotation\Post;
use Hyperf\Swagger\Annotation\Put;
use Hyperf\Swagger\Generator;
use Hyperf\Swagger\HttpServer;
use Hyperf\Swagger\Util;
use InvalidArgumentException;
use OpenApi\Annotations\Operation;
use Psr\Container\ContainerInterface;
@ -99,13 +100,13 @@ class BootSwaggerListener implements ListenerInterface
$classAnnotations = AnnotationCollector::getClassAnnotations($class);
$methodAnnotations = AnnotationCollector::getClassMethodAnnotation($class, $method);
$serverAnnotations = $this->findAnnotations($methodAnnotations, SA\Server::class);
$serverAnnotations = Util::findAnnotations($methodAnnotations, SA\Server::class);
if (! $serverAnnotations) {
$serverAnnotations = $this->findAnnotations($classAnnotations, SA\Server::class);
$serverAnnotations = Util::findAnnotations($classAnnotations, SA\Server::class);
}
$middlewareAnnotations = $this->findAnnotations($methodAnnotations, Middleware::class);
$middlewareAnnotations = array_merge($middlewareAnnotations, $this->findAnnotations($classAnnotations, Middleware::class));
$middlewareAnnotations = Util::findAnnotations($methodAnnotations, Middleware::class);
$middlewareAnnotations = array_merge($middlewareAnnotations, Util::findAnnotations($classAnnotations, Middleware::class));
/** @var Operation $opera */
foreach ($annotation->toAnnotations() as $opera) {
@ -132,22 +133,4 @@ class BootSwaggerListener implements ListenerInterface
}
}
}
public function findAnnotations(?array $classAnnotations, string $class): array
{
$result = [];
foreach ((array) $classAnnotations as $annotation) {
if ($annotation instanceof $class) {
$result[] = $annotation;
}
if ($annotation instanceof MultipleAnnotation) {
if ($annotation->className() === $class) {
$result = array_merge($result, $annotation->toAnnotations());
}
}
}
return $result;
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Swagger\Request;
use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\Swagger\Annotation\JsonContent;
use Hyperf\Swagger\Annotation\Property;
use Hyperf\Swagger\Annotation\QueryParameter;
use Hyperf\Swagger\Annotation\RequestBody;
use Hyperf\Swagger\Util;
class RuleCollector
{
protected static $rules = [];
public static function get(string $class, string $method): array
{
if (! empty(static::$rules[$class][$method])) {
return static::$rules[$class][$method];
}
$methodAnnotations = AnnotationCollector::getClassMethodAnnotation($class, $method);
/** @var QueryParameter[] $queryParameters */
$queryParameters = Util::findAnnotations($methodAnnotations, QueryParameter::class);
$rules = [];
foreach ($queryParameters as $parameter) {
if ($parameter->rules) {
$rules[$parameter->name] = $parameter->rules;
}
}
/** @var RequestBody $body */
$body = Util::findAnnotations($methodAnnotations, RequestBody::class)[0] ?? null;
if ($body) {
if ($body->_content instanceof JsonContent) {
foreach ($body->_content->properties as $property) {
if ($property instanceof Property) {
if ($property->rules) {
$rules[$property->property] = $property->rules;
}
}
}
}
}
return static::$rules[$class][$method] = $rules;
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Swagger\Request;
use Hyperf\Context\Context;
use Hyperf\HttpServer\Router\Dispatched;
use Hyperf\Swagger\Exception\RuntimeException;
use Hyperf\Validation\Request\FormRequest;
use Psr\Http\Message\ServerRequestInterface;
class SwaggerRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
/** @var Dispatched $dispatched */
$dispatched = Context::get(ServerRequestInterface::class)?->getAttribute(Dispatched::class);
if (! $dispatched) {
throw new RuntimeException('The request is invalid.');
}
$callback = $dispatched->handler?->callback;
if (! $callback || ! is_array($callback)) {
throw new RuntimeException('The SwaggerRequest is only used by swagger annotations.');
}
return RuleCollector::get($callback[0], $callback[1]);
}
}

35
src/swagger/src/Util.php Normal file
View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Swagger;
use Hyperf\Di\Annotation\MultipleAnnotation;
class Util
{
public static function findAnnotations(?array $annotations, string $class): array
{
$result = [];
foreach ((array) $annotations as $annotation) {
if ($annotation instanceof $class) {
$result[] = $annotation;
}
if ($annotation instanceof MultipleAnnotation) {
if ($annotation->className() === $class) {
$result = array_merge($result, $annotation->toAnnotations());
}
}
}
return $result;
}
}