mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-11-29 18:27:44 +08:00
Support validation for swagger. (#5395)
This commit is contained in:
parent
e1b7001483
commit
34c083ad0c
@ -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
|
||||
|
||||
|
@ -25,9 +25,8 @@
|
||||
"Hyperf\\Swagger\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
}
|
||||
"suggest": {
|
||||
"hyperf/validation": "Required to use SwaggerRequest."
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
19
src/swagger/src/Annotation/JsonContent.php
Normal file
19
src/swagger/src/Annotation/JsonContent.php
Normal 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
|
||||
{
|
||||
}
|
48
src/swagger/src/Annotation/MultipleAnnotationTrait.php
Normal file
48
src/swagger/src/Annotation/MultipleAnnotationTrait.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
109
src/swagger/src/Annotation/Property.php
Normal file
109
src/swagger/src/Annotation/Property.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
40
src/swagger/src/Annotation/RequestBody.php
Normal file
40
src/swagger/src/Annotation/RequestBody.php
Normal 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;
|
||||
}
|
||||
}
|
16
src/swagger/src/Exception/RuntimeException.php
Normal file
16
src/swagger/src/Exception/RuntimeException.php
Normal 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
|
||||
{
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
57
src/swagger/src/Request/RuleCollector.php
Normal file
57
src/swagger/src/Request/RuleCollector.php
Normal 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;
|
||||
}
|
||||
}
|
48
src/swagger/src/Request/SwaggerRequest.php
Normal file
48
src/swagger/src/Request/SwaggerRequest.php
Normal 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
35
src/swagger/src/Util.php
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user