REST API: Make multi-typed schemas more robust.

A multi-type schema is a schema where the `type` keyword is an array of possible types instead of a single type. For instance, `[ 'object', 'string' ]` would allow objects or string values.

In [46249] basic support for these schemas was introduced. The validator would loop over each schema type trying to find a version that matched. This worked for valid values, but for invalid values it provided unhelpful error messages. The sanitizer also had its utility restricted.

In this commit, the validators and sanitizers will first determine the best type of the passed value and then apply the schema with that set type. In the case that a value could match multiple types, the schema of the first matching type will be used.

To maintain backward compatibility, if unsupported schema types are used, the value will always pass validation. A doing it wrong notice is issued in this case.

Fixes #50300.
Props pentatonicfunk, dlh, TimothyBlynJacobs.


git-svn-id: https://develop.svn.wordpress.org/trunk@48306 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Timothy Jacobs 2020-07-05 00:13:37 +00:00
parent 83f8654283
commit 205eb1abc9
4 changed files with 697 additions and 107 deletions

View File

@ -985,6 +985,50 @@ function rest_cookie_collect_status() {
$wp_rest_auth_cookie = true;
}
/**
* Retrieves the avatar urls in various sizes.
*
* @since 4.7.0
*
* @see get_avatar_url()
*
* @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
* user email, WP_User object, WP_Post object, or WP_Comment object.
* @return array Avatar URLs keyed by size. Each value can be a URL string or boolean false.
*/
function rest_get_avatar_urls( $id_or_email ) {
$avatar_sizes = rest_get_avatar_sizes();
$urls = array();
foreach ( $avatar_sizes as $size ) {
$urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) );
}
return $urls;
}
/**
* Retrieves the pixel sizes for avatars.
*
* @since 4.7.0
*
* @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
*/
function rest_get_avatar_sizes() {
/**
* Filters the REST avatar sizes.
*
* Use this filter to adjust the array of sizes returned by the
* `rest_get_avatar_sizes` function.
*
* @since 4.4.0
*
* @param int[] $sizes An array of int values that are the pixel sizes for avatars.
* Default `[ 24, 48, 96 ]`.
*/
return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
}
/**
* Parses an RFC3339 time into a Unix timestamp.
*
@ -1116,7 +1160,7 @@ function rest_sanitize_request_arg( $value, $request, $param ) {
}
$args = $attributes['args'][ $param ];
return rest_sanitize_value_from_schema( $value, $args );
return rest_sanitize_value_from_schema( $value, $args, $param );
}
/**
@ -1219,47 +1263,179 @@ function rest_is_boolean( $maybe_bool ) {
}
/**
* Retrieves the avatar urls in various sizes.
* Determines if a given value is integer-like.
*
* @since 4.7.0
* @since 5.5.0
*
* @see get_avatar_url()
*
* @param mixed $id_or_email The Gravatar to retrieve a URL for. Accepts a user_id, gravatar md5 hash,
* user email, WP_User object, WP_Post object, or WP_Comment object.
* @return array Avatar URLs keyed by size. Each value can be a URL string or boolean false.
* @param mixed $maybe_integer The value being evaluated.
* @return bool True if an integer, otherwise false.
*/
function rest_get_avatar_urls( $id_or_email ) {
$avatar_sizes = rest_get_avatar_sizes();
$urls = array();
foreach ( $avatar_sizes as $size ) {
$urls[ $size ] = get_avatar_url( $id_or_email, array( 'size' => $size ) );
}
return $urls;
function rest_is_integer( $maybe_integer ) {
return round( floatval( $maybe_integer ) ) === floatval( $maybe_integer );
}
/**
* Retrieves the pixel sizes for avatars.
* Determines if a given value is array-like.
*
* @since 4.7.0
* @since 5.5.0
*
* @return int[] List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
* @param mixed $maybe_array The value being evaluated.
* @return bool
*/
function rest_get_avatar_sizes() {
/**
* Filters the REST avatar sizes.
*
* Use this filter to adjust the array of sizes returned by the
* `rest_get_avatar_sizes` function.
*
* @since 4.4.0
*
* @param int[] $sizes An array of int values that are the pixel sizes for avatars.
* Default `[ 24, 48, 96 ]`.
*/
return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
function rest_is_array( $maybe_array ) {
if ( is_scalar( $maybe_array ) ) {
$maybe_array = wp_parse_list( $maybe_array );
}
return wp_is_numeric_array( $maybe_array );
}
/**
* Converts an array-like value to an array.
*
* @since 5.5.0
*
* @param mixed $maybe_array The value being evaluated.
* @return array Returns the array extracted from the value.
*/
function rest_sanitize_array( $maybe_array ) {
if ( is_scalar( $maybe_array ) ) {
return wp_parse_list( $maybe_array );
}
if ( ! is_array( $maybe_array ) ) {
return array();
}
// Normalize to numeric array so nothing unexpected is in the keys.
return array_values( $maybe_array );
}
/**
* Determines if a given value is object-like.
*
* @since 5.5.0
*
* @param mixed $maybe_object The value being evaluated.
* @return bool True if object like, otherwise false.
*/
function rest_is_object( $maybe_object ) {
if ( '' === $maybe_object ) {
return true;
}
if ( $maybe_object instanceof stdClass ) {
return true;
}
if ( $maybe_object instanceof JsonSerializable ) {
$maybe_object = $maybe_object->jsonSerialize();
}
return is_array( $maybe_object );
}
/**
* Converts an object-like value to an object.
*
* @since 5.5.0
*
* @param mixed $maybe_object The value being evaluated.
* @return array Returns the object extracted from the value.
*/
function rest_sanitize_object( $maybe_object ) {
if ( '' === $maybe_object ) {
return array();
}
if ( $maybe_object instanceof stdClass ) {
return (array) $maybe_object;
}
if ( $maybe_object instanceof JsonSerializable ) {
$maybe_object = $maybe_object->jsonSerialize();
}
if ( ! is_array( $maybe_object ) ) {
return array();
}
return $maybe_object;
}
/**
* Gets the best type for a value.
*
* @since 5.5.0
*
* @param mixed $value The value to check.
* @param array $types The list of possible types.
* @return string The best matching type, an empty string if no types match.
*/
function rest_get_best_type_for_value( $value, $types ) {
static $checks = array(
'array' => 'rest_is_array',
'object' => 'rest_is_object',
'integer' => 'rest_is_integer',
'number' => 'is_numeric',
'boolean' => 'rest_is_boolean',
'string' => 'is_string',
'null' => 'is_null',
);
// Both arrays and objects allow empty strings to be converted to their types.
// But the best answer for this type is a string.
if ( '' === $value && in_array( 'string', $types, true ) ) {
return 'string';
}
foreach ( $types as $type ) {
if ( isset( $checks[ $type ] ) && $checks[ $type ]( $value ) ) {
return $type;
}
}
return '';
}
/**
* Handles getting the best type for a multi-type schema.
*
* This is a wrapper for {@see rest_get_best_type_for_value()} that handles
* backward compatibility for schemas that use invalid types.
*
* @since 5.5.0
*
* @param mixed $value The value to check.
* @param array $args The schema array to use.
* @param string $param The parameter name, used in error messages.
* @return string
*/
function rest_handle_multi_type_schema( $value, $args, $param = '' ) {
$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
$invalid_types = array_diff( $args['type'], $allowed_types );
if ( $invalid_types ) {
_doing_it_wrong(
__FUNCTION__,
/* translators: 1. Parameter. 2. List of allowed types. */
wp_sprintf( __( 'The "type" schema keyword for %1$s can only contain the built-in types: %2$l.' ), $param, $allowed_types ),
'5.5.0'
);
}
$best_type = rest_get_best_type_for_value( $value, $args['type'] );
if ( ! $best_type ) {
if ( ! $invalid_types ) {
return '';
}
// Backward compatibility for previous behavior which allowed the value if there was an invalid type used.
$best_type = reset( $invalid_types );
}
return $best_type;
}
/**
@ -1284,42 +1460,38 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) {
$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
if ( ! isset( $args['type'] ) ) {
_doing_it_wrong( __FUNCTION__, __( 'The "type" schema keyword is required.' ), '5.5.0' );
/* translators: 1. Parameter */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
}
if ( is_array( $args['type'] ) ) {
foreach ( $args['type'] as $type ) {
$type_args = $args;
$type_args['type'] = $type;
$best_type = rest_handle_multi_type_schema( $value, $args, $param );
if ( true === rest_validate_value_from_schema( $value, $type_args, $param ) ) {
return true;
}
if ( ! $best_type ) {
/* translators: 1: Parameter, 2: List of types. */
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) );
}
/* translators: 1: Parameter, 2: List of types. */
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ) );
$args['type'] = $best_type;
}
if ( ! in_array( $args['type'], $allowed_types, true ) ) {
_doing_it_wrong(
__FUNCTION__,
/* translators: 1. The list of allowed types. */
wp_sprintf( __( 'The "type" schema keyword can only be on of the built-in types: %l.' ), $allowed_types ),
/* translators: 1. Parameter 2. The list of allowed types. */
wp_sprintf( __( 'The "type" schema keyword for %1$s can only be on of the built-in types: %2$l.' ), $param, $allowed_types ),
'5.5.0'
);
}
if ( 'array' === $args['type'] ) {
if ( ! is_null( $value ) ) {
$value = wp_parse_list( $value );
}
if ( ! wp_is_numeric_array( $value ) ) {
if ( ! rest_is_array( $value ) ) {
/* translators: 1: Parameter, 2: Type name. */
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
}
$value = rest_sanitize_array( $value );
foreach ( $value as $index => $v ) {
$is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
if ( is_wp_error( $is_valid ) ) {
@ -1339,23 +1511,13 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) {
}
if ( 'object' === $args['type'] ) {
if ( '' === $value ) {
$value = array();
}
if ( $value instanceof stdClass ) {
$value = (array) $value;
}
if ( $value instanceof JsonSerializable ) {
$value = $value->jsonSerialize();
}
if ( ! is_array( $value ) ) {
if ( ! rest_is_object( $value ) ) {
/* translators: 1: Parameter, 2: Type name. */
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
}
$value = rest_sanitize_object( $value );
if ( isset( $args['required'] ) && is_array( $args['required'] ) ) { // schema version 4
foreach ( $args['required'] as $name ) {
if ( ! array_key_exists( $name, $value ) ) {
@ -1415,7 +1577,7 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
}
if ( 'integer' === $args['type'] && round( floatval( $value ) ) !== floatval( $value ) ) {
if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) {
/* translators: 1: Parameter, 2: Type name. */
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
}
@ -1551,85 +1713,65 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) {
* Sanitize a value based on a schema.
*
* @since 4.7.0
* @since 5.5.0 Added the `$param` parameter.
*
* @param mixed $value The value to sanitize.
* @param array $args Schema array to use for sanitization.
* @param mixed $value The value to sanitize.
* @param array $args Schema array to use for sanitization.
* @param string $param The parameter name, used in error messages.
* @return true|WP_Error
*/
function rest_sanitize_value_from_schema( $value, $args ) {
function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
$allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
if ( ! isset( $args['type'] ) ) {
_doing_it_wrong( __FUNCTION__, __( 'The "type" schema keyword is required.' ), '5.5.0' );
/* translators: 1. Parameter */
_doing_it_wrong( __FUNCTION__, sprintf( __( 'The "type" schema keyword for %s is required.' ), $param ), '5.5.0' );
}
if ( is_array( $args['type'] ) ) {
// Determine which type the value was validated against,
// and use that type when performing sanitization.
$validated_type = '';
$best_type = rest_handle_multi_type_schema( $value, $args, $param );
foreach ( $args['type'] as $type ) {
$type_args = $args;
$type_args['type'] = $type;
if ( ! is_wp_error( rest_validate_value_from_schema( $value, $type_args ) ) ) {
$validated_type = $type;
break;
}
}
if ( ! $validated_type ) {
if ( ! $best_type ) {
return null;
}
$args['type'] = $validated_type;
$args['type'] = $best_type;
}
if ( ! in_array( $args['type'], $allowed_types, true ) ) {
_doing_it_wrong(
__FUNCTION__,
/* translators: 1. The list of allowed types. */
wp_sprintf( __( 'The "type" schema keyword can only be on of the built-in types: %l.' ), $allowed_types ),
/* translators: 1. Parameter. 2. The list of allowed types. */
wp_sprintf( __( 'The "type" schema keyword for %1$s can only be on of the built-in types: %2$l.' ), $param, $allowed_types ),
'5.5.0'
);
}
if ( 'array' === $args['type'] ) {
$value = rest_sanitize_array( $value );
if ( empty( $args['items'] ) ) {
return (array) $value;
return $value;
}
$value = wp_parse_list( $value );
foreach ( $value as $index => $v ) {
$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
}
// Normalize to numeric array so nothing unexpected is in the keys.
$value = array_values( $value );
return $value;
}
if ( 'object' === $args['type'] ) {
if ( $value instanceof stdClass ) {
$value = (array) $value;
}
if ( $value instanceof JsonSerializable ) {
$value = $value->jsonSerialize();
}
if ( ! is_array( $value ) ) {
return array();
}
$value = rest_sanitize_object( $value );
foreach ( $value as $property => $v ) {
if ( isset( $args['properties'][ $property ] ) ) {
$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] );
$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' );
} elseif ( isset( $args['additionalProperties'] ) ) {
if ( false === $args['additionalProperties'] ) {
unset( $value[ $property ] );
} elseif ( is_array( $args['additionalProperties'] ) ) {
$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'] );
$value[ $property ] = rest_sanitize_value_from_schema( $v, $args['additionalProperties'], $param . '[' . $property . ']' );
}
}
}

View File

@ -8,6 +8,7 @@
require_once ABSPATH . 'wp-admin/includes/admin.php';
require_once ABSPATH . WPINC . '/rest-api.php';
require_once __DIR__ . '/../includes/class-jsonserializable-object.php';
/**
* @group restapi
@ -1457,4 +1458,359 @@ class Tests_REST_API extends WP_UnitTestCase {
$term = self::factory()->term->create_and_get();
$this->assertEquals( '/wp/v2/tags/' . $term->term_id, rest_get_route_for_term( $term->term_id ) );
}
/**
* @dataProvider _dp_rest_is_object
* @ticket 50300
* @param bool $expected Expected result of the check.
* @param mixed $value The value to check.
*/
public function test_rest_is_object( $expected, $value ) {
$is_object = rest_is_object( $value );
if ( $expected ) {
$this->assertTrue( $is_object );
} else {
$this->assertFalse( $is_object );
}
}
public function _dp_rest_is_object() {
return array(
array(
true,
'',
),
array(
true,
new stdClass(),
),
array(
true,
new JsonSerializable_Object( array( 'hi' => 'there' ) ),
),
array(
true,
array( 'hi' => 'there' ),
),
array(
true,
array(),
),
array(
true,
array( 'a', 'b' ),
),
array(
false,
new Basic_Object(),
),
array(
false,
new JsonSerializable_Object( 'str' ),
),
array(
false,
'str',
),
array(
false,
5,
),
);
}
/**
* @dataProvider _dp_rest_sanitize_object
* @ticket 50300
* @param array $expected Expected sanitized version.
* @param mixed $value The value to sanitize.
*/
public function test_rest_sanitize_object( $expected, $value ) {
$sanitized = rest_sanitize_object( $value );
$this->assertEquals( $expected, $sanitized );
}
public function _dp_rest_sanitize_object() {
return array(
array(
array(),
'',
),
array(
array( 'a' => '1' ),
(object) array( 'a' => '1' ),
),
array(
array( 'hi' => 'there' ),
new JsonSerializable_Object( array( 'hi' => 'there' ) ),
),
array(
array( 'hi' => 'there' ),
array( 'hi' => 'there' ),
),
array(
array(),
array(),
),
array(
array( 'a', 'b' ),
array( 'a', 'b' ),
),
array(
array(),
new Basic_Object(),
),
array(
array(),
new JsonSerializable_Object( 'str' ),
),
array(
array(),
'str',
),
array(
array(),
5,
),
);
}
/**
* @dataProvider _dp_rest_is_array
* @ticket 50300
* @param bool $expected Expected result of the check.
* @param mixed $value The value to check.
*/
public function test_rest_is_array( $expected, $value ) {
$is_array = rest_is_array( $value );
if ( $expected ) {
$this->assertTrue( $is_array );
} else {
$this->assertFalse( $is_array );
}
}
public function _dp_rest_is_array() {
return array(
array(
true,
'',
),
array(
true,
array( 'a', 'b' ),
),
array(
true,
array(),
),
array(
true,
'a,b,c',
),
array(
true,
'a',
),
array(
true,
5,
),
array(
false,
new stdClass(),
),
array(
false,
new JsonSerializable_Object( array( 'hi' => 'there' ) ),
),
array(
false,
array( 'hi' => 'there' ),
),
array(
false,
new Basic_Object(),
),
array(
false,
new JsonSerializable_Object( 'str' ),
),
array(
false,
null,
),
);
}
/**
* @dataProvider _dp_rest_sanitize_array
* @ticket 50300
* @param array $expected Expected sanitized version.
* @param mixed $value The value to sanitize.
*/
public function test_rest_sanitize_array( $expected, $value ) {
$sanitized = rest_sanitize_array( $value );
$this->assertEquals( $expected, $sanitized );
}
public function _dp_rest_sanitize_array() {
return array(
array(
array(),
'',
),
array(
array( 'a', 'b' ),
array( 'a', 'b' ),
),
array(
array(),
array(),
),
array(
array( 'a', 'b', 'c' ),
'a,b,c',
),
array(
array( 'a' ),
'a',
),
array(
array( 'a', 'b' ),
'a,b,',
),
array(
array( '5' ),
5,
),
array(
array(),
new stdClass(),
),
array(
array(),
new JsonSerializable_Object( array( 'hi' => 'there' ) ),
),
array(
array( 'there' ),
array( 'hi' => 'there' ),
),
array(
array(),
new Basic_Object(),
),
array(
array(),
new JsonSerializable_Object( 'str' ),
),
array(
array(),
null,
),
);
}
/**
* @dataProvider _dp_get_best_type_for_value
* @ticket 50300
* @param string $expected The expected best type.
* @param mixed $value The value to test.
* @param array $types The list of available types.
*/
public function test_get_best_type_for_value( $expected, $value, $types ) {
$this->assertEquals( $expected, rest_get_best_type_for_value( $value, $types ) );
}
public function _dp_get_best_type_for_value() {
return array(
array(
'array',
array( 'hi' ),
array( 'array' ),
),
array(
'object',
array( 'hi' => 'there' ),
array( 'object' ),
),
array(
'integer',
5,
array( 'integer' ),
),
array(
'number',
4.0,
array( 'number' ),
),
array(
'boolean',
true,
array( 'boolean' ),
),
array(
'string',
'str',
array( 'string' ),
),
array(
'null',
null,
array( 'null' ),
),
array(
'string',
'',
array( 'array', 'string' ),
),
array(
'string',
'',
array( 'object', 'string' ),
),
array(
'string',
'Hello',
array( 'object', 'string' ),
),
array(
'object',
array( 'hello' => 'world' ),
array( 'object', 'string' ),
),
array(
'number',
'5.0',
array( 'number', 'string' ),
),
array(
'string',
'5.0',
array( 'string', 'number' ),
),
array(
'boolean',
'false',
array( 'boolean', 'string' ),
),
array(
'string',
'false',
array( 'string', 'boolean' ),
),
array(
'string',
'a,b',
array( 'string', 'array' ),
),
array(
'array',
'a,b',
array( 'array', 'string' ),
),
);
}
}

View File

@ -341,7 +341,7 @@ class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase {
$this->assertNull( rest_sanitize_value_from_schema( null, $schema ) );
$this->assertEquals( '2019-09-19T18:00:00', rest_sanitize_value_from_schema( '2019-09-19T18:00:00', $schema ) );
$this->assertNull( rest_sanitize_value_from_schema( 'lalala', $schema ) );
$this->assertEquals( 'lalala', rest_sanitize_value_from_schema( 'lalala', $schema ) );
}
/**
@ -394,7 +394,7 @@ class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase {
$this->assertEquals( 'My Value', rest_sanitize_value_from_schema( 'My Value', $schema ) );
$this->assertEquals( array( 'raw' => 'My Value' ), rest_sanitize_value_from_schema( array( 'raw' => 'My Value' ), $schema ) );
$this->assertNull( rest_sanitize_value_from_schema( array( 'raw' => 1 ), $schema ) );
$this->assertEquals( array( 'raw' => '1' ), rest_sanitize_value_from_schema( array( 'raw' => 1 ), $schema ) );
}
public function test_object_or_bool() {
@ -423,6 +423,45 @@ class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase {
$this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => '0' ), $schema ) );
$this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => 0 ), $schema ) );
$this->assertNull( rest_sanitize_value_from_schema( array( 'raw' => 'something non boolean' ), $schema ) );
$this->assertEquals( array( 'raw' => true ), rest_sanitize_value_from_schema( array( 'raw' => 'something non boolean' ), $schema ) );
}
/**
* @ticket 50300
*/
public function test_multi_type_with_no_known_types() {
$this->setExpectedIncorrectUsage( 'rest_handle_multi_type_schema' );
$this->setExpectedIncorrectUsage( 'rest_sanitize_value_from_schema' );
$schema = array(
'type' => array( 'invalid', 'type' ),
);
$this->assertEquals( 'My Value', rest_sanitize_value_from_schema( 'My Value', $schema ) );
}
/**
* @ticket 50300
*/
public function test_multi_type_with_some_unknown_types() {
$this->setExpectedIncorrectUsage( 'rest_handle_multi_type_schema' );
$this->setExpectedIncorrectUsage( 'rest_sanitize_value_from_schema' );
$schema = array(
'type' => array( 'object', 'type' ),
);
$this->assertEquals( 'My Value', rest_sanitize_value_from_schema( 'My Value', $schema ) );
}
/**
* @ticket 50300
*/
public function test_multi_type_returns_null_if_no_valid_type() {
$schema = array(
'type' => array( 'number', 'string' ),
);
$this->assertNull( rest_sanitize_value_from_schema( array( 'Hello!' ), $schema ) );
}
}

View File

@ -387,7 +387,10 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
$this->assertTrue( rest_validate_value_from_schema( null, $schema ) );
$this->assertTrue( rest_validate_value_from_schema( '2019-09-19T18:00:00', $schema ) );
$this->assertWPError( rest_validate_value_from_schema( 'some random string', $schema ) );
$error = rest_validate_value_from_schema( 'some random string', $schema );
$this->assertWPError( $error );
$this->assertEquals( 'Invalid date.', $error->get_error_message() );
}
public function test_object_or_string() {
@ -402,7 +405,57 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
$this->assertTrue( rest_validate_value_from_schema( 'My Value', $schema ) );
$this->assertTrue( rest_validate_value_from_schema( array( 'raw' => 'My Value' ), $schema ) );
$this->assertWPError( rest_validate_value_from_schema( array( 'raw' => array( 'a list' ) ), $schema ) );
$error = rest_validate_value_from_schema( array( 'raw' => array( 'a list' ) ), $schema );
$this->assertWPError( $error );
$this->assertEquals( '[raw] is not of type string.', $error->get_error_message() );
}
/**
* @ticket 50300
*/
public function test_null_or_integer() {
$schema = array(
'type' => array( 'null', 'integer' ),
'minimum' => 10,
'maximum' => 20,
);
$this->assertTrue( rest_validate_value_from_schema( null, $schema ) );
$this->assertTrue( rest_validate_value_from_schema( 15, $schema ) );
$this->assertTrue( rest_validate_value_from_schema( '15', $schema ) );
$error = rest_validate_value_from_schema( 30, $schema, 'param' );
$this->assertWPError( $error );
$this->assertEquals( 'param must be between 10 (inclusive) and 20 (inclusive)', $error->get_error_message() );
}
/**
* @ticket 50300
*/
public function test_multi_type_with_no_known_types() {
$this->setExpectedIncorrectUsage( 'rest_handle_multi_type_schema' );
$this->setExpectedIncorrectUsage( 'rest_validate_value_from_schema' );
$schema = array(
'type' => array( 'invalid', 'type' ),
);
$this->assertTrue( rest_validate_value_from_schema( 'My Value', $schema ) );
}
/**
* @ticket 50300
*/
public function test_multi_type_with_some_unknown_types() {
$this->setExpectedIncorrectUsage( 'rest_handle_multi_type_schema' );
$this->setExpectedIncorrectUsage( 'rest_validate_value_from_schema' );
$schema = array(
'type' => array( 'object', 'type' ),
);
$this->assertTrue( rest_validate_value_from_schema( 'My Value', $schema ) );
}
/**