napojse / openapi-psr7-validator
分叉:验证PSR-7消息是否符合用YAML或JSON表达的OpenAPI(3.0.2)规范
Requires
- php: >=7.2
- ext-json: *
- cebe/php-openapi: ^1.6
- league/uri: ^6.3
- psr/cache: ^1.0 || ^2.0 || ^3.0
- psr/http-message: ^1.0
- psr/http-server-middleware: ^1.0
- respect/validation: ^1.1.3 || ^2.0
- riverline/multipart-parser: ^2.0.3
- webmozart/assert: ^1.4
Requires (Dev)
- doctrine/coding-standard: ^8.0
- guzzlehttp/psr7: ^2.0
- hansott/psr7-cookies: ^3.0.2
- phpstan/extension-installer: ^1.0
- phpstan/phpstan: ^1
- phpstan/phpstan-phpunit: ^1
- phpstan/phpstan-webmozart-assert: ^1
- phpunit/phpunit: ^7 || ^8 || ^9
- symfony/cache: ^5.1
Replaces
- thephpleague/openapi-psr7-validator: v0.2
This package is auto-updated.
Last update: 2024-09-06 16:09:24 UTC
README
OpenAPI PSR-7消息(HTTP请求/响应)验证器
此包可以验证PSR-7消息是否符合用YAML或JSON表达的OpenAPI(3.0.x)规范。
安装
composer require league/openapi-psr7-validator
OpenAPI(OAS)术语
包中使用了以下一些特定术语。这些术语来自OpenAPI
specification
- 一个OpenAPI文档,描述了一个API,用JSON或YAML文件表达data
- 实际的验证内容,包括正文和元数据schema
- 规范中描述请求/响应正文的那个部分keyword
- 用以描述实例的属性称为关键字,或称为模式关键字path
- 到单个端点的相对路径operation
- 应用在路径上的方法(如get /password
)response
- 描述的响应(包括状态码、内容类型等)
如何验证
ServerRequest消息
您可以像这样验证\Psr\Http\Message\ServerRequestInterface
实例
$yamlFile = "api.yaml"; $jsonFile = "api.json"; $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getServerRequestValidator(); #or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYaml(file_get_contents($yamlFile))->getServerRequestValidator(); #or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJson(file_get_contents($jsonFile))->getServerRequestValidator(); #or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonFile)->getServerRequestValidator(); #or $schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getServerRequestValidator(); $match = $validator->validate($request);
结果,您将获得一个OperationAddress $match
,它匹配了给定的请求。如果您已经知道应该匹配您请求的操作(即您的项目中已经有路由),您可以使用RouterRequestValidator
$address = new \League\OpenAPIValidation\PSR7\OperationAddress('/some/operation', 'post'); $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getRoutedRequestValidator(); $validator->validate($address, $request);
这将大大简化验证,并提供更好的性能。
请求消息
您可以像这样验证\Psr\Http\Message\RequestInterface
实例
$yamlFile = "api.yaml"; $jsonFile = "api.json"; $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getRequestValidator(); #or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYaml(file_get_contents($yamlFile))->getRequestValidator(); #or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJson(file_get_contents($jsonFile))->getRequestValidator(); #or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonFile)->getRequestValidator(); #or $schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getRequestValidator(); $match = $validator->validate($request);
响应消息
\Psr\Http\Message\ResponseInterface
的验证稍微复杂一些。因为您不仅需要YAML文件和响应本身,还需要知道这个响应属于哪个操作(在OpenAPI的术语中)。
示例
$yamlFile = "api.yaml"; $jsonFile = "api.json"; $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getResponseValidator(); #or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYaml(file_get_contents($yamlFile))->getResponseValidator(); #or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJson(file_get_contents($jsonFile))->getResponseValidator(); #or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromJsonFile($jsonFile)->getResponseValidator(); #or $schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromSchema($schema)->getResponseValidator(); $operation = new \League\OpenAPIValidation\PSR7\OperationAddress('/password/gen', 'get') ; $validator->validate($operation, $response);
验证后重用模式
\League\OpenAPIValidation\PSR7\ValidatorBuilder
将读取和编译模式为内存中的\cebe\openapi\spec\OpenApi
实例。验证器使用此实例执行验证逻辑。您可以在验证后重用此实例,如下所示
$validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getServerRequestValidator(); # or $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder)->fromYamlFile($yamlFile)->getResponseValidator(); /** @var \cebe\openapi\spec\OpenApi */ $openApi = $validator->getSchema();
PSR-15中间件
PSR-15中间件可以使用如下方式使用
$yamlFile = 'api.yaml'; $jsonFile = 'api.json'; $psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYamlFile($yamlFile)->getValidationMiddleware(); #or $psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYaml(file_get_contents($yamlFile))->getValidationMiddleware(); #or $psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJsonFile($jsonFile)->getValidationMiddleware(); #or $psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJson(file_get_contents($jsonFile))->getValidationMiddleware(); #or $schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand $validator = (new \League\OpenAPIValidation\PSR7\ValidationMiddlewareBuilder)->fromSchema($schema)->getValidationMiddleware();
SlimFramework中间件
Slim框架使用稍微不同的中间件接口,因此这里有一个适配器,您可以像这样使用它
$yamlFile = 'api.yaml'; $jsonFile = 'api.json'; $psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYamlFile($yamlFile)->getValidationMiddleware(); #or $psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromYaml(file_get_contents($yamlFile))->getValidationMiddleware(); #or $psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJsonFile($jsonFile)->getValidationMiddleware(); #or $psr15Middleware = (new \League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder)->fromJson(file_get_contents($jsonFile))->getValidationMiddleware(); #or $schema = new \cebe\openapi\spec\OpenApi(); // generate schema object by hand $validator = (new \League\OpenAPIValidation\PSR7\ValidationMiddlewareBuilder)->fromSchema($schema)->getValidationMiddleware(); $slimMiddleware = new \League\OpenAPIValidation\PSR15\SlimAdapter($psr15Middleware); /** @var \Slim\App $app */ $app->add($slimMiddleware);
缓存层/PSR-6支持
PSR-7验证器具有一个内置的缓存层(基于PSR-6接口),可以节省解析OpenAPI规范的时间。这是可选的。如果您在静态构造函数中传递一个配置好的缓存池对象,则可以启用缓存,如下所示
// Configure a PSR-6 Cache Pool $cachePool = new ArrayCachePool(); // Pass it as a 2nd argument $validator = (new \League\OpenAPIValidation\PSR7\ValidatorBuilder) ->fromYamlFile($yamlFile) ->setCache($cachePool) ->getResponseValidator(); # or $psr15Middleware = (new \OpenAPIValidation\PSR15\ValidationMiddlewareBuilder) ->fromYamlFile($yamlFile) ->setCache($cachePool) ->getValidationMiddleware();
您可以使用->setCache($pool, $ttl)
调用为PSR-7和PSR-15构建器设置适当的过期时间(以秒为单位)或显式null
如果您想控制模式项的缓存键,或者您的缓存不支持自动生成缓存键,您可以使用->overrideCacheKey('my_custom_key')
来确保缓存使用您想要的键。
独立OpenAPI验证器
该包包含一个独立的验证器,可以像这样验证任何数据是否符合OpenAPI模式
$spec = <<<SPEC schema: type: string enum: - a - b SPEC; $data = "c"; $spec = cebe\openapi\Reader::readFromYaml($spec); # (optional) reference resolving $spec->resolveReferences(new ReferenceContext($spec, "/")); $schema = new cebe\openapi\spec\Schema($spec->schema); try { (new \League\OpenAPIValidation\Schema\SchemaValidator())->validate($data, $schema); } catch(\League\OpenAPIValidation\Schema\Exception\KeywordMismatch $e) { // you can evaluate failure details // $e->keyword() == "enum" // $e->data() == "c" // $e->dataBreadCrumb()->buildChain() -- only for nested data }
自定义类型格式
如您所知,OpenAPI允许您向类型添加格式
schema: type: string format: binary
本包包含一组内置格式验证器
- 字符串类型
字节
日期
日期时间
电子邮件
主机名
IPv4
IPv6
URI
uuid
(uuid4)
- 数字类型
浮点数
双精度浮点数
您也可以添加自己的格式。例如这样
# A format validator must be a callable # It must return bool value (true if format matched the data, false otherwise) # A callable class: $customFormat = new class() { function __invoke($value): bool { return $value === "good value"; } }; # Or just a closure: $customFormat = function ($value): bool { return $value === "good value"; }; # Register your callable like this before validating your data \League\OpenAPIValidation\Schema\TypeFormats\FormatsContainer::registerFormat('string', 'custom', $customFormat);
异常
该包抛出一组各种异常,您可以捕获并处理。其中一些是
- 模式相关
\League\OpenAPIValidation\Schema\Exception\KeywordMismatch
- 表示数据未与模式的键匹配\League\OpenAPIValidation\Schema\Exception\TypeMismatch
- 对type
键的验证失败。例如type:string
和值是12
\League\OpenAPIValidation\Schema\Exception\FormatMismatch
- 数据未匹配给定的类型格式。例如type: string, format: email
不会匹配not-email
。
- PSR7 消息相关
\League\OpenAPIValidation\PSR7\Exception\NoContentType
- HTTP消息(请求/响应)不包含Content-Type头。通用HTTP错误。\League\OpenAPIValidation\PSR7\Exception\NoPath
- 路径在规范中未找到\League\OpenAPIValidation\PSR7\Exception\NoOperation
- 路径中找不到操作\League\OpenAPIValidation\PSR7\Exception\NoResponseCode
- 规范中的操作下未找到响应代码- 验证异常(检查父异常以确定可能的根本原因)
\League\OpenAPIValidation\PSR7\Exception\ValidationFailed
- 失败的PSR-7消息的通用异常\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody
- 主体不匹配模式\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidCookies
- cookies不匹配模式或缺少必需的cookie\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders
- 标头不匹配模式或缺少必需的标头\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidPath
- 路径不匹配模式或模式值不匹配模式\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidQueryArgs
- 查询参数不匹配模式或缺少必需的参数\League\OpenAPIValidation\PSR7\Exception\Validation\InvalidSecurity
- 请求不匹配安全模式或安全头无效
- 请求相关
\League\OpenAPIValidation\PSR7\Exception\MultipleOperationsMismatchForRequest
- 请求在规范中匹配了多个操作,但所有操作均未通过验证。
测试
您可以使用以下命令运行测试
vendor/bin/phpunit
贡献指南
您可以自由地打开一个 Issue 或添加一个 Pull request。该包遵循一定的代码风格: doctrine/coding-standard。
为了符合此风格,请使用随包提供的 git 钩,位于 .githooks/pre-commit
。
如何使用它
- 在本地克隆包并导航到文件夹
- 创建一个钩子符号链接,如下所示:
ln -s -f ../../.githooks/pre-commit .git/hooks/pre-commit
- 添加执行权限:
chmod +x .git/hooks/pre-commit
- 现在提交任何新的更改,代码将被相应地检查和格式化。
- 如果您的代码有任何问题,请在此检查日志:
.phpcs-report.txt
鸣谢
人物
- Dmitry Lezhnev
- Carsten Brandt
- Samuel Nela
- Pavel Batanov
- Christopher L Bray
- David Pauli
- Jason Judge
- Yannick Chenot
- TarasBK
- Jason B. Standing
- Dmytro Demchyna
- Will Chambers
- Ignacio
- 非常感谢 Henrik Karlström,他启发我开发了这个包。
资源
- 图标由 Freepik 制作,许可协议 CC 3.0 BY
- cebe/php-openapi 包用于读取 OpenAPI 文件
- slim3-psr15 包用于 Slim 中间件适配器
许可
麻省理工学院许可证(MIT)。有关更多信息,请参阅License.md
文件。
待办事项
- 支持判别器对象(注意:这似乎并不直接,因为判别器可以指向任何外部方案)