Make WordPress Core

Changeset 49246


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.

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.