league / openapi-psr7-validator
验证 PSR-7 消息是否符合 OpenAPI (3.0.2) 规范,规范用 YAML 或 JSON 表达
Requires
- php: >=7.2
- ext-json: *
- devizzent/cebe-php-openapi: ^1.0
- league/uri: ^6.3 || ^7.0
- psr/cache: ^1.0 || ^2.0 || ^3.0
- psr/http-message: ^1.0 || ^2.0
- psr/http-server-middleware: ^1.0
- respect/validation: ^1.1.3 || ^2.0
- riverline/multipart-parser: ^2.0.3
- symfony/polyfill-php80: ^1.27
- webmozart/assert: ^1.4
Requires (Dev)
- doctrine/coding-standard: ^8.0
- guzzlehttp/psr7: ^2.0
- hansott/psr7-cookies: ^3.0.2 || ^4.0
- 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
This package is auto-updated.
Last update: 2024-09-10 20:46:36 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
- 用于描述实例的属性被称为关键字,或 schema 关键字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 构建器设置 正确的过期 ttl(以秒为单位)或显式 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
此包包含一组内置格式验证器
string
类型字节
日期
日期时间
电子邮件
主机名
IPv4
IPv6
URI
uuid
(uuid4)
number
类型浮点数
double
您还可以添加自己的格式。例如这样
# 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 hook,位于 .githooks/pre-commit
。
如何使用它
- 在本地克隆包并导航到文件夹
- 创建到钩子的符号链接,如下所示:
ln -s -f ../../.githooks/pre-commit .git/hooks/pre-commit
- 添加执行权限:
chmod +x .git/hooks/pre-commit
- 现在提交任何新更改,代码将相应地进行检查和格式化。
- 如果您的代码有任何问题,请在此处检查日志:
.phpcs-report.txt
鸣谢
人物
- Дмитрий Лежнев
- Carsten Brandt
- Samuel Nela
- Павел Батанов
- 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 许可证(MIT)。请参阅 License.md
文件以获取更多信息。
待办事项
- 支持区分符对象(注意:显然,这并不简单,因为区分符可以指向任何外部模式)