REST API: Add support for the uniqueItems keyword.

Props sorenbronsted.
Fixes #48821.


git-svn-id: https://develop.svn.wordpress.org/trunk@48357 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Timothy Jacobs
2020-07-07 03:20:34 +00:00
parent abfd9441b7
commit 41912bcece
4 changed files with 615 additions and 17 deletions

View File

@@ -0,0 +1,374 @@
[
{
"description": "uniqueItems validation",
"schema": {"uniqueItems": true},
"tests": [
{
"description": "unique array of integers is valid",
"data": [1, 2],
"valid": true
},
{
"description": "non-unique array of integers is invalid",
"data": [1, 1],
"valid": false
},
{
"description": "numbers are unique if mathematically unequal",
"data": [1.0, 1.00, 1],
"valid": false
},
{
"description": "false is not equal to zero",
"data": [0, false],
"valid": true
},
{
"description": "true is not equal to one",
"data": [1, true],
"valid": true
},
{
"description": "unique array of objects is valid",
"data": [{"foo": "bar"}, {"foo": "baz"}],
"valid": true
},
{
"description": "non-unique array of objects is invalid",
"data": [{"foo": "bar"}, {"foo": "bar"}],
"valid": false
},
{
"description": "unique array of nested objects is valid",
"data": [
{"foo": {"bar" : {"baz" : true}}},
{"foo": {"bar" : {"baz" : false}}}
],
"valid": true
},
{
"description": "non-unique array of nested objects is invalid",
"data": [
{"foo": {"bar" : {"baz" : true}}},
{"foo": {"bar" : {"baz" : true}}}
],
"valid": false
},
{
"description": "unique array of arrays is valid",
"data": [["foo"], ["bar"]],
"valid": true
},
{
"description": "non-unique array of arrays is invalid",
"data": [["foo"], ["foo"]],
"valid": false
},
{
"description": "1 and true are unique",
"data": [1, true],
"valid": true
},
{
"description": "0 and false are unique",
"data": [0, false],
"valid": true
},
{
"description": "[1] and [true] are unique",
"data": [[1], [true]],
"valid": true
},
{
"description": "[0] and [false] are unique",
"data": [[0], [false]],
"valid": true
},
{
"description": "nested [1] and [true] are unique",
"data": [[[1], "foo"], [[true], "foo"]],
"valid": true
},
{
"description": "nested [0] and [false] are unique",
"data": [[[0], "foo"], [[false], "foo"]],
"valid": true
},
{
"description": "unique heterogeneous types are valid",
"data": [{}, [1], true, null, 1, "{}"],
"valid": true
},
{
"description": "non-unique heterogeneous types are invalid",
"data": [{}, [1], true, null, {}, 1],
"valid": false
},
{
"description": "different objects are unique",
"data": [{"a": 1, "b": 2}, {"a": 2, "b": 1}],
"valid": true
},
{
"description": "objects are non-unique despite key order",
"data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}],
"valid": false
}
]
},
{
"description": "uniqueItems with an array of items",
"schema": {
"items": [{"type": "boolean"}, {"type": "boolean"}],
"uniqueItems": true
},
"tests": [
{
"description": "[false, true] from items array is valid",
"data": [false, true],
"valid": true
},
{
"description": "[true, false] from items array is valid",
"data": [true, false],
"valid": true
},
{
"description": "[false, false] from items array is not valid",
"data": [false, false],
"valid": false
},
{
"description": "[true, true] from items array is not valid",
"data": [true, true],
"valid": false
},
{
"description": "unique array extended from [false, true] is valid",
"data": [false, true, "foo", "bar"],
"valid": true
},
{
"description": "unique array extended from [true, false] is valid",
"data": [true, false, "foo", "bar"],
"valid": true
},
{
"description": "non-unique array extended from [false, true] is not valid",
"data": [false, true, "foo", "foo"],
"valid": false
},
{
"description": "non-unique array extended from [true, false] is not valid",
"data": [true, false, "foo", "foo"],
"valid": false
}
]
},
{
"description": "uniqueItems with an array of items and additionalItems=false",
"schema": {
"items": [{"type": "boolean"}, {"type": "boolean"}],
"uniqueItems": true,
"additionalItems": false
},
"tests": [
{
"description": "[false, true] from items array is valid",
"data": [false, true],
"valid": true
},
{
"description": "[true, false] from items array is valid",
"data": [true, false],
"valid": true
},
{
"description": "[false, false] from items array is not valid",
"data": [false, false],
"valid": false
},
{
"description": "[true, true] from items array is not valid",
"data": [true, true],
"valid": false
},
{
"description": "extra items are invalid even if unique",
"data": [false, true, null],
"valid": false
}
]
},
{
"description": "uniqueItems=false validation",
"schema": { "uniqueItems": false },
"tests": [
{
"description": "unique array of integers is valid",
"data": [1, 2],
"valid": true
},
{
"description": "non-unique array of integers is valid",
"data": [1, 1],
"valid": true
},
{
"description": "numbers are unique if mathematically unequal",
"data": [1.0, 1.00, 1],
"valid": true
},
{
"description": "false is not equal to zero",
"data": [0, false],
"valid": true
},
{
"description": "true is not equal to one",
"data": [1, true],
"valid": true
},
{
"description": "unique array of objects is valid",
"data": [{"foo": "bar"}, {"foo": "baz"}],
"valid": true
},
{
"description": "non-unique array of objects is valid",
"data": [{"foo": "bar"}, {"foo": "bar"}],
"valid": true
},
{
"description": "unique array of nested objects is valid",
"data": [
{"foo": {"bar" : {"baz" : true}}},
{"foo": {"bar" : {"baz" : false}}}
],
"valid": true
},
{
"description": "non-unique array of nested objects is valid",
"data": [
{"foo": {"bar" : {"baz" : true}}},
{"foo": {"bar" : {"baz" : true}}}
],
"valid": true
},
{
"description": "unique array of arrays is valid",
"data": [["foo"], ["bar"]],
"valid": true
},
{
"description": "non-unique array of arrays is valid",
"data": [["foo"], ["foo"]],
"valid": true
},
{
"description": "1 and true are unique",
"data": [1, true],
"valid": true
},
{
"description": "0 and false are unique",
"data": [0, false],
"valid": true
},
{
"description": "unique heterogeneous types are valid",
"data": [{}, [1], true, null, 1],
"valid": true
},
{
"description": "non-unique heterogeneous types are valid",
"data": [{}, [1], true, null, {}, 1],
"valid": true
}
]
},
{
"description": "uniqueItems=false with an array of items",
"schema": {
"items": [{"type": "boolean"}, {"type": "boolean"}],
"uniqueItems": false
},
"tests": [
{
"description": "[false, true] from items array is valid",
"data": [false, true],
"valid": true
},
{
"description": "[true, false] from items array is valid",
"data": [true, false],
"valid": true
},
{
"description": "[false, false] from items array is valid",
"data": [false, false],
"valid": true
},
{
"description": "[true, true] from items array is valid",
"data": [true, true],
"valid": true
},
{
"description": "unique array extended from [false, true] is valid",
"data": [false, true, "foo", "bar"],
"valid": true
},
{
"description": "unique array extended from [true, false] is valid",
"data": [true, false, "foo", "bar"],
"valid": true
},
{
"description": "non-unique array extended from [false, true] is valid",
"data": [false, true, "foo", "foo"],
"valid": true
},
{
"description": "non-unique array extended from [true, false] is valid",
"data": [true, false, "foo", "foo"],
"valid": true
}
]
},
{
"description": "uniqueItems=false with an array of items and additionalItems=false",
"schema": {
"items": [{"type": "boolean"}, {"type": "boolean"}],
"uniqueItems": false,
"additionalItems": false
},
"tests": [
{
"description": "[false, true] from items array is valid",
"data": [false, true],
"valid": true
},
{
"description": "[true, false] from items array is valid",
"data": [true, false],
"valid": true
},
{
"description": "[false, false] from items array is valid",
"data": [false, false],
"valid": true
},
{
"description": "[true, true] from items array is valid",
"data": [true, true],
"valid": true
},
{
"description": "extra items are invalid even if unique",
"data": [false, true, null],
"valid": false
}
]
}
]

View File

@@ -464,4 +464,26 @@ class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase {
$this->assertNull( rest_sanitize_value_from_schema( array( 'Hello!' ), $schema ) );
}
/**
* @ticket 48821
*/
public function test_unique_items_after_sanitization() {
$schema = array(
'type' => 'array',
'uniqueItems' => true,
'items' => array(
'type' => 'string',
'format' => 'uri',
),
);
$data = array(
'https://example.org/hello%20world',
'https://example.org/hello world',
);
$this->assertTrue( rest_validate_value_from_schema( $data, $schema ) );
$this->assertWPError( rest_sanitize_value_from_schema( $data, $schema ) );
}
}

View File

@@ -507,7 +507,7 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
}
/**
* @ticket 48818
* @ticket 48818
*
* @dataProvider data_required_property
*/
@@ -535,7 +535,7 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
}
/**
* @ticket 48818
* @ticket 48818
*
* @dataProvider data_required_property
*/
@@ -577,7 +577,7 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
}
/**
* @ticket 48818
* @ticket 48818
*
* @dataProvider data_required_nested_property
*/
@@ -610,7 +610,7 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
}
/**
* @ticket 48818
* @ticket 48818
*
* @dataProvider data_required_nested_property
*/
@@ -669,7 +669,7 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
}
/**
* @ticket 48818
* @ticket 48818
*
* @dataProvider data_required_deeply_nested_property
*/
@@ -709,7 +709,7 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
}
/**
* @ticket 48818
* @ticket 48818
*
* @dataProvider data_required_deeply_nested_property
*/
@@ -749,7 +749,7 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
}
/**
* @ticket 48818
* @ticket 48818
*
* @dataProvider data_required_deeply_nested_property
*/
@@ -905,4 +905,139 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
$this->assertWPError( rest_validate_value_from_schema( array( 1, 2, 3 ), $schema ) );
$this->assertWPError( rest_validate_value_from_schema( 'foobar', $schema ) );
}
/**
* @ticket 48821
*
* @dataProvider data_unique_items
*/
public function test_unique_items( $test, $suite ) {
$test_description = $suite['description'] . ': ' . $test['description'];
$message = $test_description . ': ' . var_export( $test['data'], true );
$valid = rest_validate_value_from_schema( $test['data'], $suite['schema'] );
if ( $test['valid'] ) {
$this->assertTrue( $valid, $message );
} else {
$this->assertWPError( $valid, $message );
}
}
public function data_unique_items() {
$all_types = array( 'object', 'array', 'null', 'number', 'integer', 'boolean', 'string' );
// the following test suites is not supported at the moment
$skip = array(
'uniqueItems with an array of items',
'uniqueItems with an array of items and additionalItems=false',
'uniqueItems=false with an array of items',
'uniqueItems=false with an array of items and additionalItems=false',
);
$suites = json_decode( file_get_contents( __DIR__ . '/json_schema_test_suite/uniqueitems.json' ), true );
$tests = array();
foreach ( $suites as $suite ) {
if ( in_array( $suite['description'], $skip, true ) ) {
continue;
}
// type is required for our implementation
if ( ! isset( $suite['schema']['type'] ) ) {
$suite['schema']['type'] = 'array';
}
// items is required for our implementation
if ( ! isset( $suite['schema']['items'] ) ) {
$suite['schema']['items'] = array(
'type' => $all_types,
'items' => array(
'type' => $all_types,
),
);
}
foreach ( $suite['tests'] as $test ) {
$tests[] = array( $test, $suite );
}
}
return $tests;
}
/**
* @ticket 48821
*/
public function test_unique_items_deep_objects() {
$schema = array(
'type' => 'array',
'uniqueItems' => true,
'items' => array(
'type' => 'object',
'properties' => array(
'release' => array(
'type' => 'object',
'properties' => array(
'name' => array(
'type' => 'string',
),
'version' => array(
'type' => 'string',
),
),
),
),
),
);
$data = array(
array(
'release' => array(
'name' => 'Kirk',
'version' => '5.3',
),
),
array(
'release' => array(
'version' => '5.3',
'name' => 'Kirk',
),
),
);
$this->assertWPError( rest_validate_value_from_schema( $data, $schema ) );
$data[0]['release']['version'] = '5.3.0';
$this->assertTrue( rest_validate_value_from_schema( $data, $schema ) );
}
/**
* @ticket 48821
*/
public function test_unique_items_deep_arrays() {
$schema = array(
'type' => 'array',
'uniqueItems' => true,
'items' => array(
'type' => 'array',
'items' => array(
'type' => 'string',
),
),
);
$data = array(
array(
'Kirk',
'Jaco',
),
array(
'Kirk',
'Jaco',
),
);
$this->assertWPError( rest_validate_value_from_schema( $data, $schema ) );
$data[1] = array_reverse( $data[1] );
$this->assertTrue( rest_validate_value_from_schema( $data, $schema ) );
}
}