napojse/openapi-psr7-validator

分叉:验证PSR-7消息是否符合用YAML或JSON表达的OpenAPI(3.0.2)规范

v0.2 2022-12-06 09:21 UTC

This package is auto-updated.

Last update: 2024-09-06 16:09:24 UTC


README

Latest Stable Version Build Status License contributions welcome

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

如何使用它

  1. 在本地克隆包并导航到文件夹
  2. 创建一个钩子符号链接,如下所示: ln -s -f ../../.githooks/pre-commit .git/hooks/pre-commit
  3. 添加执行权限: chmod +x .git/hooks/pre-commit
  4. 现在提交任何新的更改,代码将被相应地检查和格式化。
  5. 如果您的代码有任何问题,请在此检查日志: .phpcs-report.txt

鸣谢

人物

资源

  • 图标由 Freepik 制作,许可协议 CC 3.0 BY
  • cebe/php-openapi 包用于读取 OpenAPI 文件
  • slim3-psr15 包用于 Slim 中间件适配器

许可

麻省理工学院许可证(MIT)。有关更多信息,请参阅License.md文件。

待办事项

  • 支持判别器对象(注意:这似乎并不直接,因为判别器可以指向任何外部方案)