Make WordPress Core


Ignore:
Timestamp:
10/20/2020 06:22:39 PM (4 years ago)
Author:
TimothyBlynJacobs
Message:

REST API: Add support for the oneOf and anyOf keywords.

This allows for REST API routes to define more complex validation requirements as JSON Schema instead of procedural validation.

The error code returned from rest_validate_value_from_schema for invalid parameter types has been changed from the generic rest_invalid_param to the more specific rest_invalid_type.

Props yakimun, johnbillion, TimothyBlynJacobs.
Fixes #51025.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php

    r49082 r49246  
    12541254    }
    12551255
     1256    /**
     1257     * @ticket 51025
     1258     *
     1259     * @dataProvider data_any_of
     1260     *
     1261     * @param array $data
     1262     * @param array $schema
     1263     * @param bool $valid
     1264     */
     1265    public function test_any_of( $data, $schema, $valid ) {
     1266        $is_valid = rest_validate_value_from_schema( $data, $schema );
     1267
     1268        if ( $valid ) {
     1269            $this->assertTrue( $is_valid );
     1270        } else {
     1271            $this->assertWPError( $is_valid );
     1272        }
     1273    }
     1274
     1275    /**
     1276     * @return array
     1277     */
     1278    public function data_any_of() {
     1279        $suites = json_decode( file_get_contents( __DIR__ . '/json_schema_test_suite/anyof.json' ), true );
     1280        $skip   = array(
     1281            'anyOf with boolean schemas, all true',
     1282            'anyOf with boolean schemas, some true',
     1283            'anyOf with boolean schemas, all false',
     1284            'anyOf with one empty schema',
     1285            'nested anyOf, to check validation semantics',
     1286        );
     1287
     1288        $tests = array();
     1289
     1290        foreach ( $suites as $suite ) {
     1291            if ( in_array( $suite['description'], $skip, true ) ) {
     1292                continue;
     1293            }
     1294
     1295            foreach ( $suite['tests'] as $test ) {
     1296                $tests[ $suite['description'] . ': ' . $test['description'] ] = array(
     1297                    $test['data'],
     1298                    $suite['schema'],
     1299                    $test['valid'],
     1300                );
     1301            }
     1302        }
     1303
     1304        return $tests;
     1305    }
     1306
     1307    /**
     1308     * @ticket 51025
     1309     *
     1310     * @dataProvider data_one_of
     1311     *
     1312     * @param array $data
     1313     * @param array $schema
     1314     * @param bool $valid
     1315     */
     1316    public function test_one_of( $data, $schema, $valid ) {
     1317        $is_valid = rest_validate_value_from_schema( $data, $schema );
     1318
     1319        if ( $valid ) {
     1320            $this->assertTrue( $is_valid );
     1321        } else {
     1322            $this->assertWPError( $is_valid );
     1323        }
     1324    }
     1325
     1326    /**
     1327     * @return array
     1328     */
     1329    public function data_one_of() {
     1330        $suites = json_decode( file_get_contents( __DIR__ . '/json_schema_test_suite/oneof.json' ), true );
     1331        $skip   = array(
     1332            'oneOf with boolean schemas, all true',
     1333            'oneOf with boolean schemas, one true',
     1334            'oneOf with boolean schemas, more than one true',
     1335            'oneOf with boolean schemas, all false',
     1336            'oneOf with empty schema',
     1337            'nested oneOf, to check validation semantics',
     1338        );
     1339
     1340        $tests = array();
     1341
     1342        foreach ( $suites as $suite ) {
     1343            if ( in_array( $suite['description'], $skip, true ) ) {
     1344                continue;
     1345            }
     1346
     1347            foreach ( $suite['tests'] as $test ) {
     1348                $tests[ $suite['description'] . ': ' . $test['description'] ] = array(
     1349                    $test['data'],
     1350                    $suite['schema'],
     1351                    $test['valid'],
     1352                );
     1353            }
     1354        }
     1355
     1356        return $tests;
     1357    }
     1358
     1359    /**
     1360     * @ticket 51025
     1361     *
     1362     * @dataProvider data_combining_operation_error_message
     1363     *
     1364     * @param $data
     1365     * @param $schema
     1366     * @param $expected
     1367     */
     1368    public function test_combining_operation_error_message( $data, $schema, $expected ) {
     1369        $is_valid = rest_validate_value_from_schema( $data, $schema, 'foo' );
     1370
     1371        $this->assertWPError( $is_valid );
     1372        $this->assertSame( $expected, $is_valid->get_error_message() );
     1373    }
     1374
     1375    /**
     1376     * @return array
     1377     */
     1378    public function data_combining_operation_error_message() {
     1379        return array(
     1380            array(
     1381                10,
     1382                array(
     1383                    'anyOf' => array(
     1384                        array(
     1385                            'title'   => 'circle',
     1386                            'type'    => 'integer',
     1387                            'maximum' => 5,
     1388                        ),
     1389                    ),
     1390                ),
     1391                'foo is not a valid circle. Reason: foo must be less than or equal to 5',
     1392            ),
     1393            array(
     1394                10,
     1395                array(
     1396                    'anyOf' => array(
     1397                        array(
     1398                            'type'    => 'integer',
     1399                            'maximum' => 5,
     1400                        ),
     1401                    ),
     1402                ),
     1403                'foo does not match the expected format. Reason: foo must be less than or equal to 5',
     1404            ),
     1405            array(
     1406                array( 'a' => 1 ),
     1407                array(
     1408                    'anyOf' => array(
     1409                        array( 'type' => 'boolean' ),
     1410                        array(
     1411                            'title'      => 'circle',
     1412                            'type'       => 'object',
     1413                            'properties' => array(
     1414                                'a' => array( 'type' => 'string' ),
     1415                            ),
     1416                        ),
     1417                    ),
     1418                ),
     1419                'foo is not a valid circle. Reason: foo[a] is not of type string.',
     1420            ),
     1421            array(
     1422                array( 'a' => 1 ),
     1423                array(
     1424                    'anyOf' => array(
     1425                        array( 'type' => 'boolean' ),
     1426                        array(
     1427                            'type'       => 'object',
     1428                            'properties' => array(
     1429                                'a' => array( 'type' => 'string' ),
     1430                            ),
     1431                        ),
     1432                    ),
     1433                ),
     1434                'foo does not match the expected format. Reason: foo[a] is not of type string.',
     1435            ),
     1436            array(
     1437                array(
     1438                    'a' => 1,
     1439                    'b' => 2,
     1440                    'c' => 3,
     1441                ),
     1442                array(
     1443                    'anyOf' => array(
     1444                        array( 'type' => 'boolean' ),
     1445                        array(
     1446                            'type'       => 'object',
     1447                            'properties' => array(
     1448                                'a' => array( 'type' => 'string' ),
     1449                            ),
     1450                        ),
     1451                        array(
     1452                            'title'      => 'square',
     1453                            'type'       => 'object',
     1454                            'properties' => array(
     1455                                'b' => array( 'type' => 'string' ),
     1456                                'c' => array( 'type' => 'string' ),
     1457                            ),
     1458                        ),
     1459                        array(
     1460                            'type'       => 'object',
     1461                            'properties' => array(
     1462                                'b' => array( 'type' => 'boolean' ),
     1463                                'x' => array( 'type' => 'boolean' ),
     1464                            ),
     1465                        ),
     1466                    ),
     1467                ),
     1468                'foo is not a valid square. Reason: foo[b] is not of type string.',
     1469            ),
     1470            array(
     1471                array(
     1472                    'a' => 1,
     1473                    'b' => 2,
     1474                    'c' => 3,
     1475                ),
     1476                array(
     1477                    'anyOf' => array(
     1478                        array( 'type' => 'boolean' ),
     1479                        array(
     1480                            'type'       => 'object',
     1481                            'properties' => array(
     1482                                'a' => array( 'type' => 'string' ),
     1483                            ),
     1484                        ),
     1485                        array(
     1486                            'type'       => 'object',
     1487                            'properties' => array(
     1488                                'b' => array( 'type' => 'string' ),
     1489                                'c' => array( 'type' => 'string' ),
     1490                            ),
     1491                        ),
     1492                        array(
     1493                            'type'       => 'object',
     1494                            'properties' => array(
     1495                                'b' => array( 'type' => 'boolean' ),
     1496                                'x' => array( 'type' => 'boolean' ),
     1497                            ),
     1498                        ),
     1499                    ),
     1500                ),
     1501                'foo does not match the expected format. Reason: foo[b] is not of type string.',
     1502            ),
     1503            array(
     1504                'test',
     1505                array(
     1506                    'anyOf' => array(
     1507                        array(
     1508                            'title' => 'circle',
     1509                            'type'  => 'boolean',
     1510                        ),
     1511                        array(
     1512                            'title' => 'square',
     1513                            'type'  => 'integer',
     1514                        ),
     1515                        array(
     1516                            'title' => 'triangle',
     1517                            'type'  => 'null',
     1518                        ),
     1519                    ),
     1520                ),
     1521                'foo is not a valid circle, square, and triangle.',
     1522            ),
     1523            array(
     1524                'test',
     1525                array(
     1526                    'anyOf' => array(
     1527                        array( 'type' => 'boolean' ),
     1528                        array( 'type' => 'integer' ),
     1529                        array( 'type' => 'null' ),
     1530                    ),
     1531                ),
     1532                'foo does not match any of the expected formats.',
     1533            ),
     1534            array(
     1535                'test',
     1536                array(
     1537                    'oneOf' => array(
     1538                        array(
     1539                            'title' => 'circle',
     1540                            'type'  => 'string',
     1541                        ),
     1542                        array( 'type' => 'integer' ),
     1543                        array(
     1544                            'title' => 'triangle',
     1545                            'type'  => 'string',
     1546                        ),
     1547                    ),
     1548                ),
     1549                'foo matches circle and triangle, but should match only one.',
     1550            ),
     1551            array(
     1552                'test',
     1553                array(
     1554                    'oneOf' => array(
     1555                        array( 'type' => 'string' ),
     1556                        array( 'type' => 'integer' ),
     1557                        array( 'type' => 'string' ),
     1558                    ),
     1559                ),
     1560                'foo matches more than one of the expected formats.',
     1561            ),
     1562        );
     1563    }
    12561564}
Note: See TracChangeset for help on using the changeset viewer.