Make WordPress Core

Changeset 49246


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.

Location:
trunk
Files:
2 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api.php

    r49225 r49246  
    16661666
    16671667/**
     1668 * Formats a combining operation error into a WP_Error object.
     1669 *
     1670 * @since 5.6.0
     1671 *
     1672 * @param string $param The parameter name.
     1673 * @param array $error  The error details.
     1674 * @return WP_Error
     1675 */
     1676function rest_format_combining_operation_error( $param, $error ) {
     1677        $position = $error['index'];
     1678        $reason   = $error['error_object']->get_error_message();
     1679
     1680        if ( isset( $error['schema']['title'] ) ) {
     1681                $title = $error['schema']['title'];
     1682
     1683                return new WP_Error(
     1684                        'rest_invalid_param',
     1685                        /* translators: 1: Parameter, 2: Schema title, 3: Reason. */
     1686                        sprintf( __( '%1$s is not a valid %2$s. Reason: %3$s' ), $param, $title, $reason ),
     1687                        array( 'position' => $position )
     1688                );
     1689        }
     1690
     1691        return new WP_Error(
     1692                'rest_invalid_param',
     1693                /* translators: 1: Parameter, 2: Reason. */
     1694                sprintf( __( '%1$s does not match the expected format. Reason: %2$s' ), $param, $reason ),
     1695                array( 'position' => $position )
     1696        );
     1697}
     1698
     1699/**
     1700 * Gets the error of combining operation.
     1701 *
     1702 * @since 5.6.0
     1703 *
     1704 * @param array  $value  The value to validate.
     1705 * @param string $param  The parameter name, used in error messages.
     1706 * @param array  $errors The errors array, to search for possible error.
     1707 * @return WP_Error      The combining operation error.
     1708 */
     1709function rest_get_combining_operation_error( $value, $param, $errors ) {
     1710        // If there is only one error, simply return it.
     1711        if ( 1 === count( $errors ) ) {
     1712                return rest_format_combining_operation_error( $param, $errors[0] );
     1713        }
     1714
     1715        // Filter out all errors related to type validation.
     1716        $filtered_errors = array();
     1717        foreach ( $errors as $error ) {
     1718                $error_code = $error['error_object']->get_error_code();
     1719                $error_data = $error['error_object']->get_error_data();
     1720
     1721                if ( 'rest_invalid_type' !== $error_code || ( isset( $error_data['param'] ) && $param !== $error_data['param'] ) ) {
     1722                        $filtered_errors[] = $error;
     1723                }
     1724        }
     1725
     1726        // If there is only one error left, simply return it.
     1727        if ( 1 === count( $filtered_errors ) ) {
     1728                return rest_format_combining_operation_error( $param, $filtered_errors[0] );
     1729        }
     1730
     1731        // If there are only errors related to object validation, try choosing the most appropriate one.
     1732        if ( count( $filtered_errors ) > 1 && 'object' === $filtered_errors[0]['schema']['type'] ) {
     1733                $result = null;
     1734                $number = 0;
     1735
     1736                foreach ( $filtered_errors as $error ) {
     1737                        if ( isset( $error['schema']['properties'] ) ) {
     1738                                $n = count( array_intersect_key( $error['schema']['properties'], $value ) );
     1739                                if ( $n > $number ) {
     1740                                        $result = $error;
     1741                                        $number = $n;
     1742                                }
     1743                        }
     1744                }
     1745
     1746                if ( null !== $result ) {
     1747                        return rest_format_combining_operation_error( $param, $result );
     1748                }
     1749        }
     1750
     1751        // If each schema has a title, include those titles in the error message.
     1752        $schema_titles = array();
     1753        foreach ( $errors as $error ) {
     1754                if ( isset( $error['schema']['title'] ) ) {
     1755                        $schema_titles[] = $error['schema']['title'];
     1756                }
     1757        }
     1758
     1759        if ( count( $schema_titles ) === count( $errors ) ) {
     1760                /* translators: 1: Parameter, 2: Schema titles. */
     1761                return new WP_Error( 'rest_invalid_param', wp_sprintf( __( '%1$s is not a valid %2$l.' ), $param, $schema_titles ) );
     1762        }
     1763
     1764        /* translators: 1: Parameter. */
     1765        return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s does not match any of the expected formats.' ), $param ) );
     1766}
     1767
     1768/**
     1769 * Finds the matching schema among the "anyOf" schemas.
     1770 *
     1771 * @since 5.6.0
     1772 *
     1773 * @param mixed  $value   The value to validate.
     1774 * @param array  $args    The schema array to use.
     1775 * @param string $param   The parameter name, used in error messages.
     1776 * @return array|WP_Error The matching schema or WP_Error instance if all schemas do not match.
     1777 */
     1778function rest_find_any_matching_schema( $value, $args, $param ) {
     1779        $errors = array();
     1780
     1781        foreach ( $args['anyOf'] as $index => $schema ) {
     1782                if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
     1783                        $schema['type'] = $args['type'];
     1784                }
     1785
     1786                $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
     1787                if ( ! is_wp_error( $is_valid ) ) {
     1788                        return $schema;
     1789                }
     1790
     1791                $errors[] = array(
     1792                        'error_object' => $is_valid,
     1793                        'schema'       => $schema,
     1794                        'index'        => $index,
     1795                );
     1796        }
     1797
     1798        return rest_get_combining_operation_error( $value, $param, $errors );
     1799}
     1800
     1801/**
     1802 * Finds the matching schema among the "oneOf" schemas.
     1803 *
     1804 * @since 5.6.0
     1805 *
     1806 * @param mixed  $value                  The value to validate.
     1807 * @param array  $args                   The schema array to use.
     1808 * @param string $param                  The parameter name, used in error messages.
     1809 * @param bool   $stop_after_first_match Optional. Whether the process should stop after the first successful match.
     1810 * @return array|WP_Error                The matching schema or WP_Error instance if the number of matching schemas is not equal to one.
     1811 */
     1812function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first_match = false ) {
     1813        $matching_schemas = array();
     1814        $errors           = array();
     1815
     1816        foreach ( $args['oneOf'] as $index => $schema ) {
     1817                if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
     1818                        $schema['type'] = $args['type'];
     1819                }
     1820
     1821                $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
     1822                if ( ! is_wp_error( $is_valid ) ) {
     1823                        if ( $stop_after_first_match ) {
     1824                                return $schema;
     1825                        }
     1826
     1827                        $matching_schemas[] = array(
     1828                                'schema_object' => $schema,
     1829                                'index'         => $index,
     1830                        );
     1831                } else {
     1832                        $errors[] = array(
     1833                                'error_object' => $is_valid,
     1834                                'schema'       => $schema,
     1835                                'index'        => $index,
     1836                        );
     1837                }
     1838        }
     1839
     1840        if ( ! $matching_schemas ) {
     1841                return rest_get_combining_operation_error( $value, $param, $errors );
     1842        }
     1843
     1844        if ( count( $matching_schemas ) > 1 ) {
     1845                $schema_positions = array();
     1846                $schema_titles    = array();
     1847
     1848                foreach ( $matching_schemas as $schema ) {
     1849                        $schema_positions[] = $schema['index'];
     1850
     1851                        if ( isset( $schema['schema_object']['title'] ) ) {
     1852                                $schema_titles[] = $schema['schema_object']['title'];
     1853                        }
     1854                }
     1855
     1856                // If each schema has a title, include those titles in the error message.
     1857                if ( count( $schema_titles ) === count( $matching_schemas ) ) {
     1858                        return new WP_Error(
     1859                                'rest_invalid_param',
     1860                                /* translators: 1: Parameter, 2: Schema titles. */
     1861                                wp_sprintf( __( '%1$s matches %2$l, but should match only one.' ), $param, $schema_titles ),
     1862                                array( 'positions' => $schema_positions )
     1863                        );
     1864                }
     1865
     1866                return new WP_Error(
     1867                        'rest_invalid_param',
     1868                        /* translators: 1: Parameter. */
     1869                        sprintf( __( '%1$s matches more than one of the expected formats.' ), $param ),
     1870                        array( 'positions' => $schema_positions )
     1871                );
     1872        }
     1873
     1874        return $matching_schemas[0]['schema_object'];
     1875}
     1876
     1877/**
    16681878 * Validate a value based on a schema.
    16691879 *
     
    16801890 *              Support the "multipleOf" keyword for numbers and integers.
    16811891 *              Support the "patternProperties" keyword for objects.
     1892 *              Support the "anyOf" and "oneOf" keywords.
    16821893 *
    16831894 * @param mixed  $value The value to validate.
     
    16871898 */
    16881899function rest_validate_value_from_schema( $value, $args, $param = '' ) {
     1900        if ( isset( $args['anyOf'] ) ) {
     1901                $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
     1902                if ( is_wp_error( $matching_schema ) ) {
     1903                        return $matching_schema;
     1904                }
     1905
     1906                if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
     1907                        $args['type'] = $matching_schema['type'];
     1908                }
     1909        }
     1910
     1911        if ( isset( $args['oneOf'] ) ) {
     1912                $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
     1913                if ( is_wp_error( $matching_schema ) ) {
     1914                        return $matching_schema;
     1915                }
     1916
     1917                if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
     1918                        $args['type'] = $matching_schema['type'];
     1919                }
     1920        }
     1921
    16891922        $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
    16901923
     
    16981931
    16991932                if ( ! $best_type ) {
    1700                         /* translators: 1: Parameter, 2: List of types. */
    1701                         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) );
     1933                        return new WP_Error(
     1934                                'rest_invalid_type',
     1935                                /* translators: 1: Parameter, 2: List of types. */
     1936                                sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ),
     1937                                array( 'param' => $param )
     1938                        );
    17021939                }
    17031940
     
    17161953        if ( 'array' === $args['type'] ) {
    17171954                if ( ! rest_is_array( $value ) ) {
    1718                         /* translators: 1: Parameter, 2: Type name. */
    1719                         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
     1955                        return new WP_Error(
     1956                                'rest_invalid_type',
     1957                                /* translators: 1: Parameter, 2: Type name. */
     1958                                sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ),
     1959                                array( 'param' => $param )
     1960                        );
    17201961                }
    17211962
     
    17491990        if ( 'object' === $args['type'] ) {
    17501991                if ( ! rest_is_object( $value ) ) {
    1751                         /* translators: 1: Parameter, 2: Type name. */
    1752                         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
     1992                        return new WP_Error(
     1993                                'rest_invalid_type',
     1994                                /* translators: 1: Parameter, 2: Type name. */
     1995                                sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ),
     1996                                array( 'param' => $param )
     1997                        );
    17531998                }
    17541999
     
    18172062        if ( 'null' === $args['type'] ) {
    18182063                if ( null !== $value ) {
    1819                         /* translators: 1: Parameter, 2: Type name. */
    1820                         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ) );
     2064                        return new WP_Error(
     2065                                'rest_invalid_type',
     2066                                /* translators: 1: Parameter, 2: Type name. */
     2067                                sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ),
     2068                                array( 'param' => $param )
     2069                        );
    18212070                }
    18222071
     
    18332082        if ( in_array( $args['type'], array( 'integer', 'number' ), true ) ) {
    18342083                if ( ! is_numeric( $value ) ) {
    1835                         /* translators: 1: Parameter, 2: Type name. */
    1836                         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
     2084                        return new WP_Error(
     2085                                'rest_invalid_type',
     2086                                /* translators: 1: Parameter, 2: Type name. */
     2087                                sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ),
     2088                                array( 'param' => $param )
     2089                        );
    18372090                }
    18382091
     
    18442097
    18452098        if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) {
    1846                 /* translators: 1: Parameter, 2: Type name. */
    1847                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
     2099                return new WP_Error(
     2100                        'rest_invalid_type',
     2101                        /* translators: 1: Parameter, 2: Type name. */
     2102                        sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ),
     2103                        array( 'param' => $param )
     2104                );
    18482105        }
    18492106
    18502107        if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
    1851                 /* translators: 1: Parameter, 2: Type name. */
    1852                 return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ) );
     2108                return new WP_Error(
     2109                        'rest_invalid_type',
     2110                        /* translators: 1: Parameter, 2: Type name. */
     2111                        sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ),
     2112                        array( 'param' => $param )
     2113                );
    18532114        }
    18542115
    18552116        if ( 'string' === $args['type'] ) {
    18562117                if ( ! is_string( $value ) ) {
    1857                         /* translators: 1: Parameter, 2: Type name. */
    1858                         return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
     2118                        return new WP_Error(
     2119                                'rest_invalid_type',
     2120                                /* translators: 1: Parameter, 2: Type name. */
     2121                                sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ),
     2122                                array( 'param' => $param )
     2123                        );
    18592124                }
    18602125
     
    19772242 * @since 4.7.0
    19782243 * @since 5.5.0 Added the `$param` parameter.
     2244 * @since 5.6.0 Support the "anyOf" and "oneOf" keywords.
    19792245 *
    19802246 * @param mixed  $value The value to sanitize.
     
    19842250 */
    19852251function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
     2252        if ( isset( $args['anyOf'] ) ) {
     2253                $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
     2254                if ( is_wp_error( $matching_schema ) ) {
     2255                        return $matching_schema;
     2256                }
     2257
     2258                if ( ! isset( $args['type'] ) ) {
     2259                        $args['type'] = $matching_schema['type'];
     2260                }
     2261
     2262                $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
     2263        }
     2264
     2265        if ( isset( $args['oneOf'] ) ) {
     2266                $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
     2267                if ( is_wp_error( $matching_schema ) ) {
     2268                        return $matching_schema;
     2269                }
     2270
     2271                if ( ! isset( $args['type'] ) ) {
     2272                        $args['type'] = $matching_schema['type'];
     2273                }
     2274
     2275                $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
     2276        }
     2277
    19862278        $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
    19872279
     
    21992491 * @since 5.5.0
    22002492 * @since 5.6.0 Support the "patternProperties" keyword for objects.
     2493 *              Support the "anyOf" and "oneOf" keywords.
    22012494 *
    22022495 * @param array|object $data    The response data to modify.
     
    22062499 */
    22072500function rest_filter_response_by_context( $data, $schema, $context ) {
     2501        if ( isset( $schema['anyOf'] ) ) {
     2502                $matching_schema = rest_find_any_matching_schema( $data, $schema, '' );
     2503                if ( ! is_wp_error( $matching_schema ) ) {
     2504                        if ( ! isset( $schema['type'] ) ) {
     2505                                $schema['type'] = $matching_schema['type'];
     2506                        }
     2507
     2508                        $data = rest_filter_response_by_context( $data, $matching_schema, $context );
     2509                }
     2510        }
     2511
     2512        if ( isset( $schema['oneOf'] ) ) {
     2513                $matching_schema = rest_find_one_matching_schema( $data, $schema, '', true );
     2514                if ( ! is_wp_error( $matching_schema ) ) {
     2515                        if ( ! isset( $schema['type'] ) ) {
     2516                                $schema['type'] = $matching_schema['type'];
     2517                        }
     2518
     2519                        $data = rest_filter_response_by_context( $data, $matching_schema, $context );
     2520                }
     2521        }
     2522
    22082523        if ( ! is_array( $data ) && ! is_object( $data ) ) {
    22092524                return $data;
     
    24722787                'maxItems',
    24732788                'uniqueItems',
     2789                'anyOf',
     2790                'oneOf',
    24742791        );
    24752792
  • trunk/tests/phpunit/tests/rest-api.php

    r49082 r49246  
    10861086        public function _dp_rest_filter_response_by_context() {
    10871087                return array(
    1088                         'default'                             => array(
     1088                        'default'                                      => array(
    10891089                                array(
    10901090                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    11071107                                array( 'first' => 'a' ),
    11081108                        ),
    1109                         'keeps missing context'               => array(
     1109                        'keeps missing context'                        => array(
    11101110                                array(
    11111111                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    11301130                                ),
    11311131                        ),
    1132                         'removes empty context'               => array(
     1132                        'removes empty context'                        => array(
    11331133                                array(
    11341134                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    11511151                                array( 'first' => 'a' ),
    11521152                        ),
    1153                         'nested properties'                   => array(
     1153                        'nested properties'                            => array(
    11541154                                array(
    11551155                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    11801180                                array( 'parent' => array( 'child' => 'hi' ) ),
    11811181                        ),
    1182                         'grand child properties'              => array(
     1182                        'grand child properties'                       => array(
    11831183                                array(
    11841184                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    12161216                                array( 'parent' => array( 'child' => array( 'grand' => 'hi' ) ) ),
    12171217                        ),
    1218                         'array'                               => array(
     1218                        'array'                                        => array(
    12191219                                array(
    12201220                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    12511251                                array( 'arr' => array( array( 'visible' => 'hi' ) ) ),
    12521252                        ),
    1253                         'additional properties'               => array(
     1253                        'additional properties'                        => array(
    12541254                                array(
    12551255                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    12851285                                array( 'additional' => array( 'a' => '1' ) ),
    12861286                        ),
    1287                         'pattern properties'                  => array(
     1287                        'pattern properties'                           => array(
    12881288                                array(
    12891289                                        '$schema'              => 'http://json-schema.org/draft-04/schema#',
     
    13211321                                ),
    13221322                        ),
    1323                         'multiple types object'               => array(
     1323                        'multiple types object'                        => array(
    13241324                                array(
    13251325                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    13501350                                array( 'multi' => array( 'a' => '1' ) ),
    13511351                        ),
    1352                         'multiple types array'                => array(
     1352                        'multiple types array'                         => array(
    13531353                                array(
    13541354                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    13851385                                array( 'multi' => array( array( 'visible' => '1' ) ) ),
    13861386                        ),
    1387                         'does not traverse missing context'   => array(
     1387                        'does not traverse missing context'            => array(
    13881388                                array(
    13891389                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    14281428                                ),
    14291429                        ),
    1430                         'object with no matching properties'  => array(
     1430                        'object with no matching properties'           => array(
    14311431                                array(
    14321432                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    14491449                                array(),
    14501450                        ),
    1451                         'array whose type does not match'     => array(
     1451                        'array whose type does not match'              => array(
    14521452                                array(
    14531453                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    14691469                                array( 'arr' => array() ),
    14701470                        ),
    1471                         'array and object type passed object' => array(
     1471                        'array and object type passed object'          => array(
    14721472                                array(
    14731473                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    15071507                                ),
    15081508                        ),
    1509                         'array and object type passed array'  => array(
     1509                        'array and object type passed array'           => array(
    15101510                                array(
    15111511                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     
    15481548                                array(),
    15491549                        ),
     1550                        'anyOf applies the correct schema'             => array(
     1551                                array(
     1552                                        '$schema' => 'http://json-schema.org/draft-04/schema#',
     1553                                        'type'    => 'object',
     1554                                        'anyOf'   => array(
     1555                                                array(
     1556                                                        'properties' => array(
     1557                                                                'a' => array(
     1558                                                                        'type'    => 'string',
     1559                                                                        'context' => array( 'view' ),
     1560                                                                ),
     1561                                                                'b' => array(
     1562                                                                        'type'    => 'string',
     1563                                                                        'context' => array( 'edit' ),
     1564                                                                ),
     1565                                                        ),
     1566                                                ),
     1567                                                array(
     1568                                                        'properties' => array(
     1569                                                                'a' => array(
     1570                                                                        'type'    => 'integer',
     1571                                                                        'context' => array( 'edit' ),
     1572                                                                ),
     1573                                                                'b' => array(
     1574                                                                        'type'    => 'integer',
     1575                                                                        'context' => array( 'view' ),
     1576                                                                ),
     1577                                                        ),
     1578                                                ),
     1579                                        ),
     1580                                ),
     1581                                array(
     1582                                        'a' => 1,
     1583                                        'b' => 2,
     1584                                ),
     1585                                array(
     1586                                        'b' => 2,
     1587                                ),
     1588                        ),
     1589                        'anyOf is ignored if no valid schema is found' => array(
     1590                                array(
     1591                                        '$schema' => 'http://json-schema.org/draft-04/schema#',
     1592                                        'type'    => 'object',
     1593                                        'anyOf'   => array(
     1594                                                array(
     1595                                                        'properties' => array(
     1596                                                                'a' => array(
     1597                                                                        'type'    => 'string',
     1598                                                                        'context' => array( 'view' ),
     1599                                                                ),
     1600                                                                'b' => array(
     1601                                                                        'type'    => 'string',
     1602                                                                        'context' => array( 'edit' ),
     1603                                                                ),
     1604                                                        ),
     1605                                                ),
     1606                                                array(
     1607                                                        'properties' => array(
     1608                                                                'a' => array(
     1609                                                                        'type'    => 'integer',
     1610                                                                        'context' => array( 'edit' ),
     1611                                                                ),
     1612                                                                'b' => array(
     1613                                                                        'type'    => 'integer',
     1614                                                                        'context' => array( 'view' ),
     1615                                                                ),
     1616                                                        ),
     1617                                                ),
     1618                                        ),
     1619                                ),
     1620                                array(
     1621                                        'a' => true,
     1622                                        'b' => false,
     1623                                ),
     1624                                array(
     1625                                        'a' => true,
     1626                                        'b' => false,
     1627                                ),
     1628                        ),
     1629                        'oneOf applies the correct schema'             => array(
     1630                                array(
     1631                                        '$schema' => 'http://json-schema.org/draft-04/schema#',
     1632                                        'type'    => 'object',
     1633                                        'oneOf'   => array(
     1634                                                array(
     1635                                                        'properties' => array(
     1636                                                                'a' => array(
     1637                                                                        'type'    => 'string',
     1638                                                                        'context' => array( 'view' ),
     1639                                                                ),
     1640                                                                'b' => array(
     1641                                                                        'type'    => 'string',
     1642                                                                        'context' => array( 'edit' ),
     1643                                                                ),
     1644                                                        ),
     1645                                                ),
     1646                                                array(
     1647                                                        'properties' => array(
     1648                                                                'a' => array(
     1649                                                                        'type'    => 'integer',
     1650                                                                        'context' => array( 'edit' ),
     1651                                                                ),
     1652                                                                'b' => array(
     1653                                                                        'type'    => 'integer',
     1654                                                                        'context' => array( 'view' ),
     1655                                                                ),
     1656                                                        ),
     1657                                                ),
     1658                                        ),
     1659                                ),
     1660                                array(
     1661                                        'a' => 1,
     1662                                        'b' => 2,
     1663                                ),
     1664                                array(
     1665                                        'b' => 2,
     1666                                ),
     1667                        ),
     1668                        'oneOf ignored if no valid schema was found'   => array(
     1669                                array(
     1670                                        '$schema' => 'http://json-schema.org/draft-04/schema#',
     1671                                        'type'    => 'object',
     1672                                        'anyOf'   => array(
     1673                                                array(
     1674                                                        'properties' => array(
     1675                                                                'a' => array(
     1676                                                                        'type'    => 'string',
     1677                                                                        'context' => array( 'view' ),
     1678                                                                ),
     1679                                                                'b' => array(
     1680                                                                        'type'    => 'string',
     1681                                                                        'context' => array( 'edit' ),
     1682                                                                ),
     1683                                                        ),
     1684                                                ),
     1685                                                array(
     1686                                                        'properties' => array(
     1687                                                                'a' => array(
     1688                                                                        'type'    => 'integer',
     1689                                                                        'context' => array( 'edit' ),
     1690                                                                ),
     1691                                                                'b' => array(
     1692                                                                        'type'    => 'integer',
     1693                                                                        'context' => array( 'view' ),
     1694                                                                ),
     1695                                                        ),
     1696                                                ),
     1697                                        ),
     1698                                ),
     1699                                array(
     1700                                        'a' => true,
     1701                                        'b' => false,
     1702                                ),
     1703                                array(
     1704                                        'a' => true,
     1705                                        'b' => false,
     1706                                ),
     1707                        ),
     1708                        'oneOf combined with base'                     => array(
     1709                                array(
     1710                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
     1711                                        'type'       => 'object',
     1712                                        'properties' => array(
     1713                                                'c' => array(
     1714                                                        'type'    => 'integer',
     1715                                                        'context' => array( 'edit' ),
     1716                                                ),
     1717                                        ),
     1718                                        'oneOf'      => array(
     1719                                                array(
     1720                                                        'properties' => array(
     1721                                                                'a' => array(
     1722                                                                        'type'    => 'string',
     1723                                                                        'context' => array( 'view' ),
     1724                                                                ),
     1725                                                                'b' => array(
     1726                                                                        'type'    => 'string',
     1727                                                                        'context' => array( 'edit' ),
     1728                                                                ),
     1729                                                        ),
     1730                                                ),
     1731                                                array(
     1732                                                        'properties' => array(
     1733                                                                'a' => array(
     1734                                                                        'type'    => 'integer',
     1735                                                                        'context' => array( 'edit' ),
     1736                                                                ),
     1737                                                                'b' => array(
     1738                                                                        'type'    => 'integer',
     1739                                                                        'context' => array( 'view' ),
     1740                                                                ),
     1741                                                        ),
     1742                                                ),
     1743                                        ),
     1744                                ),
     1745                                array(
     1746                                        'a' => 1,
     1747                                        'b' => 2,
     1748                                        'c' => 3,
     1749                                ),
     1750                                array(
     1751                                        'b' => 2,
     1752                                ),
     1753                        ),
    15501754                );
    15511755        }
  • trunk/tests/phpunit/tests/rest-api/rest-controller.php

    r49082 r49246  
    6767
    6868                $this->assertErrorResponse(
    69                         'rest_invalid_param',
     69                        'rest_invalid_type',
    7070                        rest_validate_request_arg( 'abc', $this->request, 'someinteger' )
    7171                );
     
    141141
    142142                $this->assertErrorResponse(
    143                         'rest_invalid_param',
     143                        'rest_invalid_type',
    144144                        rest_validate_request_arg( '123', $this->request, 'someboolean' )
    145145                );
     
    153153
    154154                $this->assertErrorResponse(
    155                         'rest_invalid_param',
     155                        'rest_invalid_type',
    156156                        rest_validate_request_arg( array( 'foo' => 'bar' ), $this->request, 'somestring' )
    157157                );
     
    298298                        'minProperties',
    299299                        'maxProperties',
     300                        'anyOf',
     301                        'oneOf',
    300302                );
    301303                foreach ( $object_properties as $property ) {
  • trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php

    r48937 r49246  
    693693
    694694                $response = rest_get_server()->dispatch( $request );
    695                 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
     695                $this->assertErrorResponse( 'rest_invalid_type', $response, 400 );
    696696        }
    697697
     
    718718
    719719                $response = rest_get_server()->dispatch( $request );
    720                 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
     720                $this->assertErrorResponse( 'rest_invalid_type', $response, 400 );
    721721        }
    722722
  • trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php

    r49082 r49246  
    588588                $this->assertWPError( rest_sanitize_value_from_schema( $data, $schema ) );
    589589        }
     590
     591        /**
     592         * @ticket 51025
     593         */
     594        public function test_any_of() {
     595                $schema = array(
     596                        'anyOf' => array(
     597                                array(
     598                                        'type'       => 'integer',
     599                                        'multipleOf' => 2,
     600                                ),
     601                                array(
     602                                        'type'      => 'string',
     603                                        'maxLength' => 1,
     604                                ),
     605                        ),
     606                );
     607
     608                $this->assertSame( 4, rest_sanitize_value_from_schema( '4', $schema ) );
     609                $this->assertSame( '5', rest_sanitize_value_from_schema( '5', $schema ) );
     610                $this->assertWPError( rest_sanitize_value_from_schema( true, $schema ) );
     611                $this->assertWPError( rest_sanitize_value_from_schema( '11', $schema ) );
     612        }
     613
     614        /**
     615         * @ticket 51025
     616         */
     617        public function test_one_of() {
     618                $schema = array(
     619                        'oneOf' => array(
     620                                array(
     621                                        'type'       => 'integer',
     622                                        'multipleOf' => 2,
     623                                ),
     624                                array(
     625                                        'type'      => 'string',
     626                                        'maxLength' => 1,
     627                                ),
     628                        ),
     629                );
     630
     631                $this->assertSame( 10, rest_sanitize_value_from_schema( '10', $schema ) );
     632                $this->assertSame( '5', rest_sanitize_value_from_schema( '5', $schema ) );
     633                $this->assertWPError( rest_sanitize_value_from_schema( true, $schema ) );
     634                $this->assertWPError( rest_sanitize_value_from_schema( '11', $schema ) );
     635                $this->assertWPError( rest_sanitize_value_from_schema( '4', $schema ) );
     636        }
    590637}
  • 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}
  • trunk/tests/phpunit/tests/rest-api/rest-term-meta-fields.php

    r48937 r49246  
    640640
    641641                $response = rest_get_server()->dispatch( $request );
    642                 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
     642                $this->assertErrorResponse( 'rest_invalid_type', $response, 400 );
    643643        }
    644644
     
    665665
    666666                $response = rest_get_server()->dispatch( $request );
    667                 $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
     667                $this->assertErrorResponse( 'rest_invalid_type', $response, 400 );
    668668        }
    669669
  • trunk/tests/phpunit/tests/rest-api/rest-test-controller.php

    r49082 r49246  
    129129                                        'minProperties'        => 1,
    130130                                        'maxProperties'        => 10,
     131                                        'anyOf'                => array(
     132                                                array(
     133                                                        'properties' => array(
     134                                                                'object_id' => array(
     135                                                                        'type'    => 'integer',
     136                                                                        'minimum' => 100,
     137                                                                ),
     138                                                        ),
     139                                                ),
     140                                                array(
     141                                                        'properties' => array(
     142                                                                'object_id' => array(
     143                                                                        'type'    => 'integer',
     144                                                                        'maximum' => 100,
     145                                                                ),
     146                                                        ),
     147                                                ),
     148                                        ),
     149                                        'oneOf'                => array(
     150                                                array(
     151                                                        'properties' => array(
     152                                                                'object_id' => array(
     153                                                                        'type'    => 'integer',
     154                                                                        'minimum' => 100,
     155                                                                ),
     156                                                        ),
     157                                                ),
     158                                                array(
     159                                                        'properties' => array(
     160                                                                'object_id' => array(
     161                                                                        'type'    => 'integer',
     162                                                                        'maximum' => 100,
     163                                                                ),
     164                                                        ),
     165                                                ),
     166                                        ),
    131167                                        'ignored_prop'         => 'ignored_prop',
    132168                                        'context'              => array( 'view' ),
Note: See TracChangeset for help on using the changeset viewer.