我的代码可以正确验证从返回单篇文章的端点返回的文章。我很确定它正常工作,因为当我故意不在文章中包含必填字段时,它会给出验证错误。
我也有这个代码试图验证从返回文章数组的端点返回的文章数组。但是,我很确定它无法正常工作,因为它总是说数据有效,即使我故意不在文章中包含必填字段。
如何针对架构正确验证数据数组?
完整的测试代码在下面作为独立的可运行测试。两个测试都应该失败,但只有其中一个测试失败。
<?php
declare(strict_types=1);
error_reporting(E_ALL);
require_once __DIR__ . '/vendor/autoload.php';
// Return the definition of the schema, either as an array
// or a PHP object
function getSchema($asArray = false)
{
$schemaJson = <<< 'JSON'
{
"swagger": "2.0",
"info": {
"termsOfService": "http://swagger.io/terms/",
"version": "1.0.0",
"title": "Example api"
},
"paths": {
"/articles": {
"get": {
"tags": [
"article"
],
"summary": "Find all articles",
"description": "Returns a list of articles",
"operationId": "getArticleById",
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Article"
}
}
}
},
"parameters": [
]
}
},
"/articles/{articleId}": {
"get": {
"tags": [
"article"
],
"summary": "Find article by ID",
"description": "Returns a single article",
"operationId": "getArticleById",
"produces": [
"application/json"
],
"parameters": [
{
"name": "articleId",
"in": "path",
"description": "ID of article to return",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/Article"
}
}
}
}
}
},
"definitions": {
"Article": {
"type": "object",
"required": [
"id",
"title"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"title": {
"type": "string",
"description": "The title for the link of the article"
}
}
}
},
"schemes": [
"http"
],
"host": "example.com",
"basePath": "/",
"tags": [],
"securityDefinitions": {
},
"security": [
{
"ApiKeyAuth": []
}
]
}
JSON;
return json_decode($schemaJson, $asArray);
}
// Extract the schema of the 200 response of an api endpoint.
function getSchemaForPath($path)
{
$swaggerData = getSchema(true);
if (isset($swaggerData["paths"][$path]['get']["responses"][200]['schema']) !== true) {
echo "response not defined";
exit(-1);
}
return $swaggerData["paths"][$path]['get']["responses"][200]['schema'];
}
// JsonSchema needs to know about the ID used for the top-level
// schema apparently.
function aliasSchema($prefix, $schemaForPath)
{
$aliasedSchema = [];
foreach ($schemaForPath as $key => $value) {
if ($key === '$ref') {
$aliasedSchema[$key] = $prefix . $value;
}
else if (is_array($value) === true) {
$aliasedSchema[$key] = aliasSchema($prefix, $value);
}
else {
$aliasedSchema[$key] = $value;
}
}
return $aliasedSchema;
}
// Test the data matches the schema.
function testDataMatches($endpointData, $schemaForPath)
{
// Setup the top level schema and get a validator from it.
$schemaStorage = new \JsonSchema\SchemaStorage();
$id = 'file://example';
$swaggerClass = getSchema(false);
$schemaStorage->addSchema($id, $swaggerClass);
$factory = new \JsonSchema\Constraints\Factory($schemaStorage);
$jsonValidator = new \JsonSchema\Validator($factory);
// Alias the schema for the endpoint, so JsonSchema can work with it.
$schemaForPath = aliasSchema($id, $schemaForPath);
// Validate the things
$jsonValidator->check($endpointData, (object)$schemaForPath);
// Process the result
if ($jsonValidator->isValid()) {
echo "The supplied JSON validates against the schema definition: " . \json_encode($schemaForPath) . " \n";
return;
}
$messages = [];
$messages[] = "End points does not validate. Violations:\n";
foreach ($jsonValidator->getErrors() as $error) {
$messages[] = sprintf("[%s] %s\n", $error['property'], $error['message']);
}
$messages[] = "Data: " . \json_encode($endpointData, JSON_PRETTY_PRINT);
echo implode("\n", $messages);
echo "\n";
}
// We have two data sets to test. A list of articles.
$articleListJson = <<< JSON
[
{
"id": 19874
},
{
"id": 19873
}
]
JSON;
$articleListData = json_decode($articleListJson);
// A single article
$articleJson = <<< JSON
{
"id": 19874
}
JSON;
$articleData = json_decode($articleJson);
// This passes, when it shouldn't as none of the articles have a title
testDataMatches($articleListData, getSchemaForPath("/articles"));
// This fails correctly, as it is correct for it to fail to validate, as the article doesn't have a title
testDataMatches($articleData, getSchemaForPath("/articles/{articleId}"));
最小的composer.json是:
{
"require": {
"justinrainbow/json-schema": "^5.2"
}
}
答案 0 :(得分:3)
编辑-2:5月22日
我一直在进一步挖掘该问题是因为您的顶级转换为object
$jsonValidator->check($endpointData, (object)$schemaForPath);
你不应该这样做,而且一切都会有效
$jsonValidator->check($endpointData, $schemaForPath);
所以它似乎不是一个错误,它只是一个错误的用法。如果您只是删除(object)
并运行代码
$ php test.php
End points does not validate. Violations:
[[0].title] The property title is required
[[1].title] The property title is required
Data: [
{
"id": 19874
},
{
"id": 19873
}
]
End points does not validate. Violations:
[title] The property title is required
Data: {
"id": 19874
}
修改-1 强>
要修复原始代码,您需要更新CollectionConstraints.php
/**
* Validates the items
*
* @param array $value
* @param \stdClass $schema
* @param JsonPointer|null $path
* @param string $i
*/
protected function validateItems(&$value, $schema = null, JsonPointer $path = null, $i = null)
{
if (is_array($schema->items) && array_key_exists('$ref', $schema->items)) {
$schema->items = $this->factory->getSchemaStorage()->resolveRefSchema((object)$schema->items);
var_dump($schema->items);
};
if (is_object($schema->items)) {
这肯定会处理您的用例,但如果您不想更改依赖项中的代码,请使用我的原始答案
原始答案
图书馆有一个错误/限制,在src/JsonSchema/Constraints/CollectionConstraint.php
他们不能解决$ref
变量。如果我更新了您的代码,如下所示
// Alias the schema for the endpoint, so JsonSchema can work with it.
$schemaForPath = aliasSchema($id, $schemaForPath);
if (array_key_exists('items', $schemaForPath))
{
$schemaForPath['items'] = $factory->getSchemaStorage()->resolveRefSchema((object)$schemaForPath['items']);
}
// Validate the things
$jsonValidator->check($endpointData, (object)$schemaForPath);
再次运行它,我得到了所需的例外
$ php test2.php
End points does not validate. Violations:
[[0].title] The property title is required
[[1].title] The property title is required
Data: [
{
"id": 19874
},
{
"id": 19873
}
]
End points does not validate. Violations:
[title] The property title is required
Data: {
"id": 19874
}
您需要修复CollectionConstraint.php
或者与回购开发者打开一个问题。或者手动替换整个架构中的$ref
,如上所示。我的代码将解决特定于您的架构的问题,但修复任何其他架构应该不是一个大问题
答案 1 :(得分:2)
编辑:这里重要的是提供的架构文档是Swagger架构的实例,它使用extended subset of JSON Schema来定义请求和响应的一些情况。 Swagger 2.0 Schema本身可以通过其JSON Schema进行验证,但它不能直接作为API响应结构的JSON Schema。
如果实体模式与标准JSON模式兼容,您可以使用通用验证器执行验证,但是您必须提供所有相关定义,当您有绝对引用时可以很容易,但对于本地(相对)引用则更复杂以#/
开头。 IIRC必须在本地模式中定义。
这里的问题是您正在尝试使用与解析范围分离的模式引用。我已添加id
以使引用成为绝对引用,因此不需要在范围内。
"$ref": "http://example.com/my-schema#/definitions/Article"
以下代码运作良好。
<?php
require_once __DIR__ . '/vendor/autoload.php';
$swaggerSchemaData = json_decode(<<<'JSON'
{
"id": "http://example.com/my-schema",
"swagger": "2.0",
"info": {
"termsOfService": "http://swagger.io/terms/",
"version": "1.0.0",
"title": "Example api"
},
"paths": {
"/articles": {
"get": {
"tags": [
"article"
],
"summary": "Find all articles",
"description": "Returns a list of articles",
"operationId": "getArticleById",
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"type": "array",
"items": {
"$ref": "http://example.com/my-schema#/definitions/Article"
}
}
}
},
"parameters": [
]
}
},
"/articles/{articleId}": {
"get": {
"tags": [
"article"
],
"summary": "Find article by ID",
"description": "Returns a single article",
"operationId": "getArticleById",
"produces": [
"application/json"
],
"parameters": [
{
"name": "articleId",
"in": "path",
"description": "ID of article to return",
"required": true,
"type": "integer",
"format": "int64"
}
],
"responses": {
"200": {
"description": "successful operation",
"schema": {
"$ref": "http://example.com/my-schema#/definitions/Article"
}
}
}
}
}
},
"definitions": {
"Article": {
"type": "object",
"required": [
"id",
"title"
],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"title": {
"type": "string",
"description": "The title for the link of the article"
}
}
}
},
"schemes": [
"http"
],
"host": "example.com",
"basePath": "/",
"tags": [],
"securityDefinitions": {
},
"security": [
{
"ApiKeyAuth": []
}
]
}
JSON
);
$schemaStorage = new \JsonSchema\SchemaStorage();
$schemaStorage->addSchema('http://example.com/my-schema', $swaggerSchemaData);
$factory = new \JsonSchema\Constraints\Factory($schemaStorage);
$validator = new \JsonSchema\Validator($factory);
$schemaData = $swaggerSchemaData->paths->{"/articles"}->get->responses->{"200"}->schema;
$data = json_decode('[{"id":1},{"id":2,"title":"Title2"}]');
$validator->validate($data, $schemaData);
var_dump($validator->isValid()); // bool(false)
$data = json_decode('[{"id":1,"title":"Title1"},{"id":2,"title":"Title2"}]');
$validator->validate($data, $schemaData);
var_dump($validator->isValid()); // bool(true)
答案 2 :(得分:0)
我不确定我是否完全理解您的代码,但我有一些基于某些假设的想法。
假设$typeForEndPoint
是您用于验证的架构,那么您的item
关键字必须是对象而不是数组。
items
关键字可以是数组或对象。如果它是一个对象,那么该模式适用于数组中的每个项目。如果是数组,则该数组中的每个项都适用于与要验证的数组位于相同位置的项。
这意味着您只验证数组中的第一项。
如果“items”是架构,则验证成功,如果所有元素都在 数组成功验证该模式。
如果“items”是模式数组,则每个元素的验证都会成功 如果实例的实例在相同位置验证模式 任何
https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-6.4.1
答案 3 :(得分:0)
jsonValidator不喜欢混合对象和数组关联, 您可以使用:
$jsonValidator->check($endpointData, $schemaForPath);
或
$jsonValidator->check($endpointData, json_decode(json_encode($schemaForPath)));