Make WordPress Core


Ignore:
Timestamp:
10/20/2020 06:22:39 PM (6 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.