<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[49246] trunk: REST API: Add support for the oneOf and anyOf keywords.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/49246">49246</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/49246","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>TimothyBlynJacobs</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2020-10-20 18:22:39 +0000 (Tue, 20 Oct 2020)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>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 <a href="https://core.trac.wordpress.org/ticket/51025">#51025</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludesrestapiphp">trunk/src/wp-includes/rest-api.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestcontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestpostmetafieldsphp">trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestschemasanitizationphp">trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestschemavalidationphp">trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php</a></li>
<li><a href="#trunktestsphpunittestsrestapiresttermmetafieldsphp">trunk/tests/phpunit/tests/rest-api/rest-term-meta-fields.php</a></li>
<li><a href="#trunktestsphpunittestsrestapiresttestcontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-test-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapiphp">trunk/tests/phpunit/tests/rest-api.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestsrestapijson_schema_test_suiteanyofjson">trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/anyof.json</a></li>
<li><a href="#trunktestsphpunittestsrestapijson_schema_test_suiteoneofjson">trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/oneof.json</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludesrestapiphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/rest-api.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api.php        2020-10-20 17:54:50 UTC (rev 49245)
+++ trunk/src/wp-includes/rest-api.php  2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1665,6 +1665,216 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Formats a combining operation error into a WP_Error object.
+ *
+ * @since 5.6.0
+ *
+ * @param string $param The parameter name.
+ * @param array $error  The error details.
+ * @return WP_Error
+ */
+function rest_format_combining_operation_error( $param, $error ) {
+       $position = $error['index'];
+       $reason   = $error['error_object']->get_error_message();
+
+       if ( isset( $error['schema']['title'] ) ) {
+               $title = $error['schema']['title'];
+
+               return new WP_Error(
+                       'rest_invalid_param',
+                       /* translators: 1: Parameter, 2: Schema title, 3: Reason. */
+                       sprintf( __( '%1$s is not a valid %2$s. Reason: %3$s' ), $param, $title, $reason ),
+                       array( 'position' => $position )
+               );
+       }
+
+       return new WP_Error(
+               'rest_invalid_param',
+               /* translators: 1: Parameter, 2: Reason. */
+               sprintf( __( '%1$s does not match the expected format. Reason: %2$s' ), $param, $reason ),
+               array( 'position' => $position )
+       );
+}
+
+/**
+ * Gets the error of combining operation.
+ *
+ * @since 5.6.0
+ *
+ * @param array  $value  The value to validate.
+ * @param string $param  The parameter name, used in error messages.
+ * @param array  $errors The errors array, to search for possible error.
+ * @return WP_Error      The combining operation error.
+ */
+function rest_get_combining_operation_error( $value, $param, $errors ) {
+       // If there is only one error, simply return it.
+       if ( 1 === count( $errors ) ) {
+               return rest_format_combining_operation_error( $param, $errors[0] );
+       }
+
+       // Filter out all errors related to type validation.
+       $filtered_errors = array();
+       foreach ( $errors as $error ) {
+               $error_code = $error['error_object']->get_error_code();
+               $error_data = $error['error_object']->get_error_data();
+
+               if ( 'rest_invalid_type' !== $error_code || ( isset( $error_data['param'] ) && $param !== $error_data['param'] ) ) {
+                       $filtered_errors[] = $error;
+               }
+       }
+
+       // If there is only one error left, simply return it.
+       if ( 1 === count( $filtered_errors ) ) {
+               return rest_format_combining_operation_error( $param, $filtered_errors[0] );
+       }
+
+       // If there are only errors related to object validation, try choosing the most appropriate one.
+       if ( count( $filtered_errors ) > 1 && 'object' === $filtered_errors[0]['schema']['type'] ) {
+               $result = null;
+               $number = 0;
+
+               foreach ( $filtered_errors as $error ) {
+                       if ( isset( $error['schema']['properties'] ) ) {
+                               $n = count( array_intersect_key( $error['schema']['properties'], $value ) );
+                               if ( $n > $number ) {
+                                       $result = $error;
+                                       $number = $n;
+                               }
+                       }
+               }
+
+               if ( null !== $result ) {
+                       return rest_format_combining_operation_error( $param, $result );
+               }
+       }
+
+       // If each schema has a title, include those titles in the error message.
+       $schema_titles = array();
+       foreach ( $errors as $error ) {
+               if ( isset( $error['schema']['title'] ) ) {
+                       $schema_titles[] = $error['schema']['title'];
+               }
+       }
+
+       if ( count( $schema_titles ) === count( $errors ) ) {
+               /* translators: 1: Parameter, 2: Schema titles. */
+               return new WP_Error( 'rest_invalid_param', wp_sprintf( __( '%1$s is not a valid %2$l.' ), $param, $schema_titles ) );
+       }
+
+       /* translators: 1: Parameter. */
+       return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s does not match any of the expected formats.' ), $param ) );
+}
+
+/**
+ * Finds the matching schema among the "anyOf" schemas.
+ *
+ * @since 5.6.0
+ *
+ * @param mixed  $value   The value to validate.
+ * @param array  $args    The schema array to use.
+ * @param string $param   The parameter name, used in error messages.
+ * @return array|WP_Error The matching schema or WP_Error instance if all schemas do not match.
+ */
+function rest_find_any_matching_schema( $value, $args, $param ) {
+       $errors = array();
+
+       foreach ( $args['anyOf'] as $index => $schema ) {
+               if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
+                       $schema['type'] = $args['type'];
+               }
+
+               $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
+               if ( ! is_wp_error( $is_valid ) ) {
+                       return $schema;
+               }
+
+               $errors[] = array(
+                       'error_object' => $is_valid,
+                       'schema'       => $schema,
+                       'index'        => $index,
+               );
+       }
+
+       return rest_get_combining_operation_error( $value, $param, $errors );
+}
+
+/**
+ * Finds the matching schema among the "oneOf" schemas.
+ *
+ * @since 5.6.0
+ *
+ * @param mixed  $value                  The value to validate.
+ * @param array  $args                   The schema array to use.
+ * @param string $param                  The parameter name, used in error messages.
+ * @param bool   $stop_after_first_match Optional. Whether the process should stop after the first successful match.
+ * @return array|WP_Error                The matching schema or WP_Error instance if the number of matching schemas is not equal to one.
+ */
+function rest_find_one_matching_schema( $value, $args, $param, $stop_after_first_match = false ) {
+       $matching_schemas = array();
+       $errors           = array();
+
+       foreach ( $args['oneOf'] as $index => $schema ) {
+               if ( ! isset( $schema['type'] ) && isset( $args['type'] ) ) {
+                       $schema['type'] = $args['type'];
+               }
+
+               $is_valid = rest_validate_value_from_schema( $value, $schema, $param );
+               if ( ! is_wp_error( $is_valid ) ) {
+                       if ( $stop_after_first_match ) {
+                               return $schema;
+                       }
+
+                       $matching_schemas[] = array(
+                               'schema_object' => $schema,
+                               'index'         => $index,
+                       );
+               } else {
+                       $errors[] = array(
+                               'error_object' => $is_valid,
+                               'schema'       => $schema,
+                               'index'        => $index,
+                       );
+               }
+       }
+
+       if ( ! $matching_schemas ) {
+               return rest_get_combining_operation_error( $value, $param, $errors );
+       }
+
+       if ( count( $matching_schemas ) > 1 ) {
+               $schema_positions = array();
+               $schema_titles    = array();
+
+               foreach ( $matching_schemas as $schema ) {
+                       $schema_positions[] = $schema['index'];
+
+                       if ( isset( $schema['schema_object']['title'] ) ) {
+                               $schema_titles[] = $schema['schema_object']['title'];
+                       }
+               }
+
+               // If each schema has a title, include those titles in the error message.
+               if ( count( $schema_titles ) === count( $matching_schemas ) ) {
+                       return new WP_Error(
+                               'rest_invalid_param',
+                               /* translators: 1: Parameter, 2: Schema titles. */
+                               wp_sprintf( __( '%1$s matches %2$l, but should match only one.' ), $param, $schema_titles ),
+                               array( 'positions' => $schema_positions )
+                       );
+               }
+
+               return new WP_Error(
+                       'rest_invalid_param',
+                       /* translators: 1: Parameter. */
+                       sprintf( __( '%1$s matches more than one of the expected formats.' ), $param ),
+                       array( 'positions' => $schema_positions )
+               );
+       }
+
+       return $matching_schemas[0]['schema_object'];
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Validate a value based on a schema.
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 4.7.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1679,6 +1889,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.6.0 Support the "minProperties" and "maxProperties" keywords for objects.
</span><span class="cx" style="display: block; padding: 0 10px">  *              Support the "multipleOf" keyword for numbers and integers.
</span><span class="cx" style="display: block; padding: 0 10px">  *              Support the "patternProperties" keyword for objects.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *              Support the "anyOf" and "oneOf" keywords.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param mixed  $value The value to validate.
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array  $args  Schema array to use for validation.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1686,6 +1897,28 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @return true|WP_Error
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function rest_validate_value_from_schema( $value, $args, $param = '' ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        if ( isset( $args['anyOf'] ) ) {
+               $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
+               if ( is_wp_error( $matching_schema ) ) {
+                       return $matching_schema;
+               }
+
+               if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
+                       $args['type'] = $matching_schema['type'];
+               }
+       }
+
+       if ( isset( $args['oneOf'] ) ) {
+               $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
+               if ( is_wp_error( $matching_schema ) ) {
+                       return $matching_schema;
+               }
+
+               if ( ! isset( $args['type'] ) && isset( $matching_schema['type'] ) ) {
+                       $args['type'] = $matching_schema['type'];
+               }
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! isset( $args['type'] ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1697,8 +1930,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $best_type = rest_handle_multi_type_schema( $value, $args, $param );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $best_type ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /* 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'] ) ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 return new WP_Error(
+                               'rest_invalid_type',
+                               /* translators: 1: Parameter, 2: List of types. */
+                               sprintf( __( '%1$s is not of type %2$s.' ), $param, implode( ',', $args['type'] ) ),
+                               array( 'param' => $param )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $args['type'] = $best_type;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1715,8 +1952,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'array' === $args['type'] ) {
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! rest_is_array( $value ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /* translators: 1: Parameter, 2: Type name. */
-                       return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 return new WP_Error(
+                               'rest_invalid_type',
+                               /* translators: 1: Parameter, 2: Type name. */
+                               sprintf( __( '%1$s is not of type %2$s.' ), $param, 'array' ),
+                               array( 'param' => $param )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $value = rest_sanitize_array( $value );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1748,8 +1989,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'object' === $args['type'] ) {
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! rest_is_object( $value ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /* translators: 1: Parameter, 2: Type name. */
-                       return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 return new WP_Error(
+                               'rest_invalid_type',
+                               /* translators: 1: Parameter, 2: Type name. */
+                               sprintf( __( '%1$s is not of type %2$s.' ), $param, 'object' ),
+                               array( 'param' => $param )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $value = rest_sanitize_object( $value );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1816,8 +2061,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'null' === $args['type'] ) {
</span><span class="cx" style="display: block; padding: 0 10px">                if ( null !== $value ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /* translators: 1: Parameter, 2: Type name. */
-                       return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 return new WP_Error(
+                               'rest_invalid_type',
+                               /* translators: 1: Parameter, 2: Type name. */
+                               sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ),
+                               array( 'param' => $param )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                return true;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1832,8 +2081,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( in_array( $args['type'], array( 'integer', 'number' ), true ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! is_numeric( $value ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /* translators: 1: Parameter, 2: Type name. */
-                       return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 return new WP_Error(
+                               'rest_invalid_type',
+                               /* translators: 1: Parameter, 2: Type name. */
+                               sprintf( __( '%1$s is not of type %2$s.' ), $param, $args['type'] ),
+                               array( 'param' => $param )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( isset( $args['multipleOf'] ) && fmod( $value, $args['multipleOf'] ) !== 0.0 ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1843,19 +2096,31 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'integer' === $args['type'] && ! rest_is_integer( $value ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /* translators: 1: Parameter, 2: Type name. */
-               return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return new WP_Error(
+                       'rest_invalid_type',
+                       /* translators: 1: Parameter, 2: Type name. */
+                       sprintf( __( '%1$s is not of type %2$s.' ), $param, 'integer' ),
+                       array( 'param' => $param )
+               );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                /* translators: 1: Parameter, 2: Type name. */
-               return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return new WP_Error(
+                       'rest_invalid_type',
+                       /* translators: 1: Parameter, 2: Type name. */
+                       sprintf( __( '%1$s is not of type %2$s.' ), $param, 'boolean' ),
+                       array( 'param' => $param )
+               );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( 'string' === $args['type'] ) {
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! is_string( $value ) ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        /* translators: 1: Parameter, 2: Type name. */
-                       return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 return new WP_Error(
+                               'rest_invalid_type',
+                               /* translators: 1: Parameter, 2: Type name. */
+                               sprintf( __( '%1$s is not of type %2$s.' ), $param, 'string' ),
+                               array( 'param' => $param )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( isset( $args['minLength'] ) && mb_strlen( $value ) < $args['minLength'] ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1976,6 +2241,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 4.7.0
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.5.0 Added the `$param` parameter.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 5.6.0 Support the "anyOf" and "oneOf" keywords.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param mixed  $value The value to sanitize.
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array  $args  Schema array to use for sanitization.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1983,6 +2249,32 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @return mixed|WP_Error The sanitized value or a WP_Error instance if the value cannot be safely sanitized.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function rest_sanitize_value_from_schema( $value, $args, $param = '' ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        if ( isset( $args['anyOf'] ) ) {
+               $matching_schema = rest_find_any_matching_schema( $value, $args, $param );
+               if ( is_wp_error( $matching_schema ) ) {
+                       return $matching_schema;
+               }
+
+               if ( ! isset( $args['type'] ) ) {
+                       $args['type'] = $matching_schema['type'];
+               }
+
+               $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
+       }
+
+       if ( isset( $args['oneOf'] ) ) {
+               $matching_schema = rest_find_one_matching_schema( $value, $args, $param );
+               if ( is_wp_error( $matching_schema ) ) {
+                       return $matching_schema;
+               }
+
+               if ( ! isset( $args['type'] ) ) {
+                       $args['type'] = $matching_schema['type'];
+               }
+
+               $value = rest_sanitize_value_from_schema( $value, $matching_schema, $param );
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         $allowed_types = array( 'array', 'object', 'string', 'number', 'integer', 'boolean', 'null' );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        if ( ! isset( $args['type'] ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2198,6 +2490,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.5.0
</span><span class="cx" style="display: block; padding: 0 10px">  * @since 5.6.0 Support the "patternProperties" keyword for objects.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ *              Support the "anyOf" and "oneOf" keywords.
</ins><span class="cx" style="display: block; padding: 0 10px">  *
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array|object $data    The response data to modify.
</span><span class="cx" style="display: block; padding: 0 10px">  * @param array        $schema  The schema for the endpoint used to filter the response.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2205,6 +2498,28 @@
</span><span class="cx" style="display: block; padding: 0 10px">  * @return array|object The filtered response data.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><span class="cx" style="display: block; padding: 0 10px"> function rest_filter_response_by_context( $data, $schema, $context ) {
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        if ( isset( $schema['anyOf'] ) ) {
+               $matching_schema = rest_find_any_matching_schema( $data, $schema, '' );
+               if ( ! is_wp_error( $matching_schema ) ) {
+                       if ( ! isset( $schema['type'] ) ) {
+                               $schema['type'] = $matching_schema['type'];
+                       }
+
+                       $data = rest_filter_response_by_context( $data, $matching_schema, $context );
+               }
+       }
+
+       if ( isset( $schema['oneOf'] ) ) {
+               $matching_schema = rest_find_one_matching_schema( $data, $schema, '', true );
+               if ( ! is_wp_error( $matching_schema ) ) {
+                       if ( ! isset( $schema['type'] ) ) {
+                               $schema['type'] = $matching_schema['type'];
+                       }
+
+                       $data = rest_filter_response_by_context( $data, $matching_schema, $context );
+               }
+       }
+
</ins><span class="cx" style="display: block; padding: 0 10px">         if ( ! is_array( $data ) && ! is_object( $data ) ) {
</span><span class="cx" style="display: block; padding: 0 10px">                return $data;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -2471,6 +2786,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                'minItems',
</span><span class="cx" style="display: block; padding: 0 10px">                'maxItems',
</span><span class="cx" style="display: block; padding: 0 10px">                'uniqueItems',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                'anyOf',
+               'oneOf',
</ins><span class="cx" style="display: block; padding: 0 10px">         );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        foreach ( $schema_properties as $field_id => $params ) {
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapijson_schema_test_suiteanyofjson"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/anyof.json</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/anyof.json                              (rev 0)
+++ trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/anyof.json        2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,229 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+[
+  {
+       "description": "anyOf",
+       "schema": {
+         "anyOf": [
+               {
+                 "type": "integer"
+               },
+               {
+                 "type": [
+                       "integer",
+                       "number"
+                 ],
+                 "minimum": 2
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "first anyOf valid",
+               "data": 1,
+               "valid": true
+         },
+         {
+               "description": "second anyOf valid",
+               "data": 2.5,
+               "valid": true
+         },
+         {
+               "description": "both anyOf valid",
+               "data": 3,
+               "valid": true
+         },
+         {
+               "description": "neither anyOf valid",
+               "data": 1.5,
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "anyOf with base schema",
+       "schema": {
+         "type": "string",
+         "anyOf": [
+               {
+                 "maxLength": 2
+               },
+               {
+                 "minLength": 4
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "mismatch base schema",
+               "data": 3,
+               "valid": false
+         },
+         {
+               "description": "one anyOf valid",
+               "data": "foobar",
+               "valid": true
+         },
+         {
+               "description": "both anyOf invalid",
+               "data": "foo",
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "anyOf with boolean schemas, all true",
+       "schema": {
+         "anyOf": [
+               true,
+               true
+         ]
+       },
+       "tests": [
+         {
+               "description": "any value is valid",
+               "data": "foo",
+               "valid": true
+         }
+       ]
+  },
+  {
+       "description": "anyOf with boolean schemas, some true",
+       "schema": {
+         "anyOf": [
+               true,
+               false
+         ]
+       },
+       "tests": [
+         {
+               "description": "any value is valid",
+               "data": "foo",
+               "valid": true
+         }
+       ]
+  },
+  {
+       "description": "anyOf with boolean schemas, all false",
+       "schema": {
+         "anyOf": [
+               false,
+               false
+         ]
+       },
+       "tests": [
+         {
+               "description": "any value is invalid",
+               "data": "foo",
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "anyOf complex types",
+       "schema": {
+         "type": "object",
+         "anyOf": [
+               {
+                 "properties": {
+                       "bar": {
+                         "type": "integer"
+                       }
+                 },
+                 "required": [
+                       "bar"
+                 ]
+               },
+               {
+                 "properties": {
+                       "foo": {
+                         "type": "string"
+                       }
+                 },
+                 "required": [
+                       "foo"
+                 ]
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "first anyOf valid (complex)",
+               "data": {
+                 "bar": 2
+               },
+               "valid": true
+         },
+         {
+               "description": "second anyOf valid (complex)",
+               "data": {
+                 "foo": "baz"
+               },
+               "valid": true
+         },
+         {
+               "description": "both anyOf valid (complex)",
+               "data": {
+                 "foo": "baz",
+                 "bar": 2
+               },
+               "valid": true
+         },
+         {
+               "description": "neither anyOf valid (complex)",
+               "data": {
+                 "foo": 2,
+                 "bar": "quux"
+               },
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "anyOf with one empty schema",
+       "schema": {
+         "anyOf": [
+               {
+                 "type": "number"
+               },
+               {}
+         ]
+       },
+       "tests": [
+         {
+               "description": "string is valid",
+               "data": "foo",
+               "valid": true
+         },
+         {
+               "description": "number is valid",
+               "data": 123,
+               "valid": true
+         }
+       ]
+  },
+  {
+       "description": "nested anyOf, to check validation semantics",
+       "schema": {
+         "anyOf": [
+               {
+                 "anyOf": [
+                       {
+                         "type": "null"
+                       }
+                 ]
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "null is valid",
+               "data": null,
+               "valid": true
+         },
+         {
+               "description": "anything non-null is invalid",
+               "data": 123,
+               "valid": false
+         }
+       ]
+  }
+]
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/anyof.json
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsrestapijson_schema_test_suiteoneofjson"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/oneof.json</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/oneof.json                              (rev 0)
+++ trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/oneof.json        2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,365 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+[
+  {
+       "description": "oneOf",
+       "schema": {
+         "oneOf": [
+               {
+                 "type": "integer"
+               },
+               {
+                 "type": [
+                       "number",
+                       "integer"
+                 ],
+                 "minimum": 2
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "first oneOf valid",
+               "data": 1,
+               "valid": true
+         },
+         {
+               "description": "second oneOf valid",
+               "data": 2.5,
+               "valid": true
+         },
+         {
+               "description": "both oneOf valid",
+               "data": 3,
+               "valid": false
+         },
+         {
+               "description": "neither oneOf valid",
+               "data": 1.5,
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "oneOf with base schema",
+       "schema": {
+         "type": "string",
+         "oneOf": [
+               {
+                 "minLength": 2
+               },
+               {
+                 "maxLength": 4
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "mismatch base schema",
+               "data": 3,
+               "valid": false
+         },
+         {
+               "description": "one oneOf valid",
+               "data": "foobar",
+               "valid": true
+         },
+         {
+               "description": "both oneOf valid",
+               "data": "foo",
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "oneOf with boolean schemas, all true",
+       "schema": {
+         "oneOf": [
+               true,
+               true,
+               true
+         ]
+       },
+       "tests": [
+         {
+               "description": "any value is invalid",
+               "data": "foo",
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "oneOf with boolean schemas, one true",
+       "schema": {
+         "oneOf": [
+               true,
+               false,
+               false
+         ]
+       },
+       "tests": [
+         {
+               "description": "any value is valid",
+               "data": "foo",
+               "valid": true
+         }
+       ]
+  },
+  {
+       "description": "oneOf with boolean schemas, more than one true",
+       "schema": {
+         "oneOf": [
+               true,
+               true,
+               false
+         ]
+       },
+       "tests": [
+         {
+               "description": "any value is invalid",
+               "data": "foo",
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "oneOf with boolean schemas, all false",
+       "schema": {
+         "oneOf": [
+               false,
+               false,
+               false
+         ]
+       },
+       "tests": [
+         {
+               "description": "any value is invalid",
+               "data": "foo",
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "oneOf complex types",
+       "schema": {
+         "type": "object",
+         "oneOf": [
+               {
+                 "properties": {
+                       "bar": {
+                         "type": "integer"
+                       }
+                 },
+                 "required": [
+                       "bar"
+                 ]
+               },
+               {
+                 "properties": {
+                       "foo": {
+                         "type": "string"
+                       }
+                 },
+                 "required": [
+                       "foo"
+                 ]
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "first oneOf valid (complex)",
+               "data": {
+                 "bar": 2
+               },
+               "valid": true
+         },
+         {
+               "description": "second oneOf valid (complex)",
+               "data": {
+                 "foo": "baz"
+               },
+               "valid": true
+         },
+         {
+               "description": "both oneOf valid (complex)",
+               "data": {
+                 "foo": "baz",
+                 "bar": 2
+               },
+               "valid": false
+         },
+         {
+               "description": "neither oneOf valid (complex)",
+               "data": {
+                 "foo": 2,
+                 "bar": "quux"
+               },
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "oneOf with empty schema",
+       "schema": {
+         "oneOf": [
+               {
+                 "type": "number"
+               },
+               {}
+         ]
+       },
+       "tests": [
+         {
+               "description": "one valid - valid",
+               "data": "foo",
+               "valid": true
+         },
+         {
+               "description": "both valid - invalid",
+               "data": 123,
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "oneOf with required",
+       "schema": {
+         "type": "object",
+         "oneOf": [
+               {
+                 "required": [
+                       "foo",
+                       "bar"
+                 ]
+               },
+               {
+                 "required": [
+                       "foo",
+                       "baz"
+                 ]
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "both invalid - invalid",
+               "data": {
+                 "bar": 2
+               },
+               "valid": false
+         },
+         {
+               "description": "first valid - valid",
+               "data": {
+                 "foo": 1,
+                 "bar": 2
+               },
+               "valid": true
+         },
+         {
+               "description": "second valid - valid",
+               "data": {
+                 "foo": 1,
+                 "baz": 3
+               },
+               "valid": true
+         },
+         {
+               "description": "both valid - invalid",
+               "data": {
+                 "foo": 1,
+                 "bar": 2,
+                 "baz": 3
+               },
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "oneOf with missing optional property",
+       "schema": {
+         "type": "object",
+         "oneOf": [
+               {
+                 "properties": {
+                       "bar": {
+                         "type": "integer"
+                       },
+                       "baz": {
+                         "type": "string"
+                       }
+                 },
+                 "required": [
+                       "bar"
+                 ]
+               },
+               {
+                 "properties": {
+                       "foo": {
+                         "type": "string"
+                       }
+                 },
+                 "required": [
+                       "foo"
+                 ]
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "first oneOf valid",
+               "data": {
+                 "bar": 8
+               },
+               "valid": true
+         },
+         {
+               "description": "second oneOf valid",
+               "data": {
+                 "foo": "foo"
+               },
+               "valid": true
+         },
+         {
+               "description": "both oneOf valid",
+               "data": {
+                 "foo": "foo",
+                 "bar": 8
+               },
+               "valid": false
+         },
+         {
+               "description": "neither oneOf valid",
+               "data": {
+                 "baz": "quux"
+               },
+               "valid": false
+         }
+       ]
+  },
+  {
+       "description": "nested oneOf, to check validation semantics",
+       "schema": {
+         "oneOf": [
+               {
+                 "oneOf": [
+                       {
+                         "type": "null"
+                       }
+                 ]
+               }
+         ]
+       },
+       "tests": [
+         {
+               "description": "null is valid",
+               "data": null,
+               "valid": true
+         },
+         {
+               "description": "anything non-null is invalid",
+               "data": 123,
+               "valid": false
+         }
+       ]
+  }
+]
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/rest-api/json_schema_test_suite/oneof.json
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsrestapirestcontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-controller.php    2020-10-20 17:54:50 UTC (rev 49245)
+++ trunk/tests/phpunit/tests/rest-api/rest-controller.php      2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -66,7 +66,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertErrorResponse(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'rest_invalid_param',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'rest_invalid_type',
</ins><span class="cx" style="display: block; padding: 0 10px">                         rest_validate_request_arg( 'abc', $this->request, 'someinteger' )
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -140,7 +140,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertErrorResponse(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'rest_invalid_param',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'rest_invalid_type',
</ins><span class="cx" style="display: block; padding: 0 10px">                         rest_validate_request_arg( '123', $this->request, 'someboolean' )
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -152,7 +152,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertErrorResponse(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'rest_invalid_param',
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'rest_invalid_type',
</ins><span class="cx" style="display: block; padding: 0 10px">                         rest_validate_request_arg( array( 'foo' => 'bar' ), $this->request, 'somestring' )
</span><span class="cx" style="display: block; padding: 0 10px">                );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -297,6 +297,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        'additionalProperties',
</span><span class="cx" style="display: block; padding: 0 10px">                        'minProperties',
</span><span class="cx" style="display: block; padding: 0 10px">                        'maxProperties',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'anyOf',
+                       'oneOf',
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">                foreach ( $object_properties as $property ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $this->assertArrayHasKey( $property, $args['someobject'] );
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestpostmetafieldsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php      2020-10-20 17:54:50 UTC (rev 49245)
+++ trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php        2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -692,7 +692,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $request->set_body_params( $data );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $response = rest_get_server()->dispatch( $request );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertErrorResponse( 'rest_invalid_type', $response, 400 );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_set_value_invalid_value_multiple() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -717,7 +717,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $request->set_body_params( $data );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $response = rest_get_server()->dispatch( $request );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertErrorResponse( 'rest_invalid_type', $response, 400 );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_set_value_sanitized() {
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestschemasanitizationphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php   2020-10-20 17:54:50 UTC (rev 49245)
+++ trunk/tests/phpunit/tests/rest-api/rest-schema-sanitization.php     2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -587,4 +587,51 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertTrue( rest_validate_value_from_schema( $data, $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertWPError( rest_sanitize_value_from_schema( $data, $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @ticket 51025
+        */
+       public function test_any_of() {
+               $schema = array(
+                       'anyOf' => array(
+                               array(
+                                       'type'       => 'integer',
+                                       'multipleOf' => 2,
+                               ),
+                               array(
+                                       'type'      => 'string',
+                                       'maxLength' => 1,
+                               ),
+                       ),
+               );
+
+               $this->assertSame( 4, rest_sanitize_value_from_schema( '4', $schema ) );
+               $this->assertSame( '5', rest_sanitize_value_from_schema( '5', $schema ) );
+               $this->assertWPError( rest_sanitize_value_from_schema( true, $schema ) );
+               $this->assertWPError( rest_sanitize_value_from_schema( '11', $schema ) );
+       }
+
+       /**
+        * @ticket 51025
+        */
+       public function test_one_of() {
+               $schema = array(
+                       'oneOf' => array(
+                               array(
+                                       'type'       => 'integer',
+                                       'multipleOf' => 2,
+                               ),
+                               array(
+                                       'type'      => 'string',
+                                       'maxLength' => 1,
+                               ),
+                       ),
+               );
+
+               $this->assertSame( 10, rest_sanitize_value_from_schema( '10', $schema ) );
+               $this->assertSame( '5', rest_sanitize_value_from_schema( '5', $schema ) );
+               $this->assertWPError( rest_sanitize_value_from_schema( true, $schema ) );
+               $this->assertWPError( rest_sanitize_value_from_schema( '11', $schema ) );
+               $this->assertWPError( rest_sanitize_value_from_schema( '4', $schema ) );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestschemavalidationphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php     2020-10-20 17:54:50 UTC (rev 49245)
+++ trunk/tests/phpunit/tests/rest-api/rest-schema-validation.php       2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1253,4 +1253,312 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertWPError( rest_validate_value_from_schema( 15.5, $schema ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+        /**
+        * @ticket 51025
+        *
+        * @dataProvider data_any_of
+        *
+        * @param array $data
+        * @param array $schema
+        * @param bool $valid
+        */
+       public function test_any_of( $data, $schema, $valid ) {
+               $is_valid = rest_validate_value_from_schema( $data, $schema );
+
+               if ( $valid ) {
+                       $this->assertTrue( $is_valid );
+               } else {
+                       $this->assertWPError( $is_valid );
+               }
+       }
+
+       /**
+        * @return array
+        */
+       public function data_any_of() {
+               $suites = json_decode( file_get_contents( __DIR__ . '/json_schema_test_suite/anyof.json' ), true );
+               $skip   = array(
+                       'anyOf with boolean schemas, all true',
+                       'anyOf with boolean schemas, some true',
+                       'anyOf with boolean schemas, all false',
+                       'anyOf with one empty schema',
+                       'nested anyOf, to check validation semantics',
+               );
+
+               $tests = array();
+
+               foreach ( $suites as $suite ) {
+                       if ( in_array( $suite['description'], $skip, true ) ) {
+                               continue;
+                       }
+
+                       foreach ( $suite['tests'] as $test ) {
+                               $tests[ $suite['description'] . ': ' . $test['description'] ] = array(
+                                       $test['data'],
+                                       $suite['schema'],
+                                       $test['valid'],
+                               );
+                       }
+               }
+
+               return $tests;
+       }
+
+       /**
+        * @ticket 51025
+        *
+        * @dataProvider data_one_of
+        *
+        * @param array $data
+        * @param array $schema
+        * @param bool $valid
+        */
+       public function test_one_of( $data, $schema, $valid ) {
+               $is_valid = rest_validate_value_from_schema( $data, $schema );
+
+               if ( $valid ) {
+                       $this->assertTrue( $is_valid );
+               } else {
+                       $this->assertWPError( $is_valid );
+               }
+       }
+
+       /**
+        * @return array
+        */
+       public function data_one_of() {
+               $suites = json_decode( file_get_contents( __DIR__ . '/json_schema_test_suite/oneof.json' ), true );
+               $skip   = array(
+                       'oneOf with boolean schemas, all true',
+                       'oneOf with boolean schemas, one true',
+                       'oneOf with boolean schemas, more than one true',
+                       'oneOf with boolean schemas, all false',
+                       'oneOf with empty schema',
+                       'nested oneOf, to check validation semantics',
+               );
+
+               $tests = array();
+
+               foreach ( $suites as $suite ) {
+                       if ( in_array( $suite['description'], $skip, true ) ) {
+                               continue;
+                       }
+
+                       foreach ( $suite['tests'] as $test ) {
+                               $tests[ $suite['description'] . ': ' . $test['description'] ] = array(
+                                       $test['data'],
+                                       $suite['schema'],
+                                       $test['valid'],
+                               );
+                       }
+               }
+
+               return $tests;
+       }
+
+       /**
+        * @ticket 51025
+        *
+        * @dataProvider data_combining_operation_error_message
+        *
+        * @param $data
+        * @param $schema
+        * @param $expected
+        */
+       public function test_combining_operation_error_message( $data, $schema, $expected ) {
+               $is_valid = rest_validate_value_from_schema( $data, $schema, 'foo' );
+
+               $this->assertWPError( $is_valid );
+               $this->assertSame( $expected, $is_valid->get_error_message() );
+       }
+
+       /**
+        * @return array
+        */
+       public function data_combining_operation_error_message() {
+               return array(
+                       array(
+                               10,
+                               array(
+                                       'anyOf' => array(
+                                               array(
+                                                       'title'   => 'circle',
+                                                       'type'    => 'integer',
+                                                       'maximum' => 5,
+                                               ),
+                                       ),
+                               ),
+                               'foo is not a valid circle. Reason: foo must be less than or equal to 5',
+                       ),
+                       array(
+                               10,
+                               array(
+                                       'anyOf' => array(
+                                               array(
+                                                       'type'    => 'integer',
+                                                       'maximum' => 5,
+                                               ),
+                                       ),
+                               ),
+                               'foo does not match the expected format. Reason: foo must be less than or equal to 5',
+                       ),
+                       array(
+                               array( 'a' => 1 ),
+                               array(
+                                       'anyOf' => array(
+                                               array( 'type' => 'boolean' ),
+                                               array(
+                                                       'title'      => 'circle',
+                                                       'type'       => 'object',
+                                                       'properties' => array(
+                                                               'a' => array( 'type' => 'string' ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               'foo is not a valid circle. Reason: foo[a] is not of type string.',
+                       ),
+                       array(
+                               array( 'a' => 1 ),
+                               array(
+                                       'anyOf' => array(
+                                               array( 'type' => 'boolean' ),
+                                               array(
+                                                       'type'       => 'object',
+                                                       'properties' => array(
+                                                               'a' => array( 'type' => 'string' ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               'foo does not match the expected format. Reason: foo[a] is not of type string.',
+                       ),
+                       array(
+                               array(
+                                       'a' => 1,
+                                       'b' => 2,
+                                       'c' => 3,
+                               ),
+                               array(
+                                       'anyOf' => array(
+                                               array( 'type' => 'boolean' ),
+                                               array(
+                                                       'type'       => 'object',
+                                                       'properties' => array(
+                                                               'a' => array( 'type' => 'string' ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'title'      => 'square',
+                                                       'type'       => 'object',
+                                                       'properties' => array(
+                                                               'b' => array( 'type' => 'string' ),
+                                                               'c' => array( 'type' => 'string' ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'type'       => 'object',
+                                                       'properties' => array(
+                                                               'b' => array( 'type' => 'boolean' ),
+                                                               'x' => array( 'type' => 'boolean' ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               'foo is not a valid square. Reason: foo[b] is not of type string.',
+                       ),
+                       array(
+                               array(
+                                       'a' => 1,
+                                       'b' => 2,
+                                       'c' => 3,
+                               ),
+                               array(
+                                       'anyOf' => array(
+                                               array( 'type' => 'boolean' ),
+                                               array(
+                                                       'type'       => 'object',
+                                                       'properties' => array(
+                                                               'a' => array( 'type' => 'string' ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'type'       => 'object',
+                                                       'properties' => array(
+                                                               'b' => array( 'type' => 'string' ),
+                                                               'c' => array( 'type' => 'string' ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'type'       => 'object',
+                                                       'properties' => array(
+                                                               'b' => array( 'type' => 'boolean' ),
+                                                               'x' => array( 'type' => 'boolean' ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               'foo does not match the expected format. Reason: foo[b] is not of type string.',
+                       ),
+                       array(
+                               'test',
+                               array(
+                                       'anyOf' => array(
+                                               array(
+                                                       'title' => 'circle',
+                                                       'type'  => 'boolean',
+                                               ),
+                                               array(
+                                                       'title' => 'square',
+                                                       'type'  => 'integer',
+                                               ),
+                                               array(
+                                                       'title' => 'triangle',
+                                                       'type'  => 'null',
+                                               ),
+                                       ),
+                               ),
+                               'foo is not a valid circle, square, and triangle.',
+                       ),
+                       array(
+                               'test',
+                               array(
+                                       'anyOf' => array(
+                                               array( 'type' => 'boolean' ),
+                                               array( 'type' => 'integer' ),
+                                               array( 'type' => 'null' ),
+                                       ),
+                               ),
+                               'foo does not match any of the expected formats.',
+                       ),
+                       array(
+                               'test',
+                               array(
+                                       'oneOf' => array(
+                                               array(
+                                                       'title' => 'circle',
+                                                       'type'  => 'string',
+                                               ),
+                                               array( 'type' => 'integer' ),
+                                               array(
+                                                       'title' => 'triangle',
+                                                       'type'  => 'string',
+                                               ),
+                                       ),
+                               ),
+                               'foo matches circle and triangle, but should match only one.',
+                       ),
+                       array(
+                               'test',
+                               array(
+                                       'oneOf' => array(
+                                               array( 'type' => 'string' ),
+                                               array( 'type' => 'integer' ),
+                                               array( 'type' => 'string' ),
+                                       ),
+                               ),
+                               'foo matches more than one of the expected formats.',
+                       ),
+               );
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapiresttermmetafieldsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-term-meta-fields.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-term-meta-fields.php      2020-10-20 17:54:50 UTC (rev 49245)
+++ trunk/tests/phpunit/tests/rest-api/rest-term-meta-fields.php        2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -639,7 +639,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $request->set_body_params( $data );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $response = rest_get_server()->dispatch( $request );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertErrorResponse( 'rest_invalid_type', $response, 400 );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_set_value_invalid_value_multiple() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -664,7 +664,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                $request->set_body_params( $data );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $response = rest_get_server()->dispatch( $request );
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         $this->assertErrorResponse( 'rest_invalid_type', $response, 400 );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public function test_set_value_sanitized() {
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapiresttestcontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-test-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-test-controller.php       2020-10-20 17:54:50 UTC (rev 49245)
+++ trunk/tests/phpunit/tests/rest-api/rest-test-controller.php 2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -128,6 +128,42 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                                        'minProperties'        => 1,
</span><span class="cx" style="display: block; padding: 0 10px">                                        'maxProperties'        => 10,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                        'anyOf'                => array(
+                                               array(
+                                                       'properties' => array(
+                                                               'object_id' => array(
+                                                                       'type'    => 'integer',
+                                                                       'minimum' => 100,
+                                                               ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'properties' => array(
+                                                               'object_id' => array(
+                                                                       'type'    => 'integer',
+                                                                       'maximum' => 100,
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                                       'oneOf'                => array(
+                                               array(
+                                                       'properties' => array(
+                                                               'object_id' => array(
+                                                                       'type'    => 'integer',
+                                                                       'minimum' => 100,
+                                                               ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'properties' => array(
+                                                               'object_id' => array(
+                                                                       'type'    => 'integer',
+                                                                       'maximum' => 100,
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                                         'ignored_prop'         => 'ignored_prop',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'context'              => array( 'view' ),
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapiphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api.php    2020-10-20 17:54:50 UTC (rev 49245)
+++ trunk/tests/phpunit/tests/rest-api.php      2020-10-20 18:22:39 UTC (rev 49246)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1085,7 +1085,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        public function _dp_rest_filter_response_by_context() {
</span><span class="cx" style="display: block; padding: 0 10px">                return array(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'default'                             => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'default'                                      => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1106,7 +1106,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array( 'first' => 'a' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'keeps missing context'               => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'keeps missing context'                        => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1129,7 +1129,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'second' => 'b',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'removes empty context'               => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'removes empty context'                        => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1150,7 +1150,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array( 'first' => 'a' ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'nested properties'                   => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'nested properties'                            => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1179,7 +1179,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array( 'parent' => array( 'child' => 'hi' ) ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'grand child properties'              => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'grand child properties'                       => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1215,7 +1215,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array( 'parent' => array( 'child' => array( 'grand' => 'hi' ) ) ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'array'                               => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'array'                                        => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1250,7 +1250,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array( 'arr' => array( array( 'visible' => 'hi' ) ) ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'additional properties'               => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'additional properties'                        => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1284,7 +1284,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array( 'additional' => array( 'a' => '1' ) ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'pattern properties'                  => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'pattern properties'                           => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'              => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'                 => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1320,7 +1320,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        '0' => '3',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'multiple types object'               => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'multiple types object'                        => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1349,7 +1349,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array( 'multi' => array( 'a' => '1' ) ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'multiple types array'                => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'multiple types array'                         => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1384,7 +1384,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array( 'multi' => array( array( 'visible' => '1' ) ) ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'does not traverse missing context'   => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'does not traverse missing context'            => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1427,7 +1427,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        ),
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'object with no matching properties'  => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'object with no matching properties'           => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1448,7 +1448,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array(),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'array whose type does not match'     => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'array whose type does not match'              => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => 'object',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1468,7 +1468,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array( 'arr' => array() ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'array and object type passed object' => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'array and object type passed object'          => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => array( 'array', 'object' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1506,7 +1506,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                        'b' => 'bar',
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        'array and object type passed array'  => array(
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 'array and object type passed array'           => array(
</ins><span class="cx" style="display: block; padding: 0 10px">                                 array(
</span><span class="cx" style="display: block; padding: 0 10px">                                        '$schema'    => 'http://json-schema.org/draft-04/schema#',
</span><span class="cx" style="display: block; padding: 0 10px">                                        'type'       => array( 'array', 'object' ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1547,6 +1547,210 @@
</span><span class="cx" style="display: block; padding: 0 10px">                                ),
</span><span class="cx" style="display: block; padding: 0 10px">                                array(),
</span><span class="cx" style="display: block; padding: 0 10px">                        ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                        'anyOf applies the correct schema'             => array(
+                               array(
+                                       '$schema' => 'http://json-schema.org/draft-04/schema#',
+                                       'type'    => 'object',
+                                       'anyOf'   => array(
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'a' => 1,
+                                       'b' => 2,
+                               ),
+                               array(
+                                       'b' => 2,
+                               ),
+                       ),
+                       'anyOf is ignored if no valid schema is found' => array(
+                               array(
+                                       '$schema' => 'http://json-schema.org/draft-04/schema#',
+                                       'type'    => 'object',
+                                       'anyOf'   => array(
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'a' => true,
+                                       'b' => false,
+                               ),
+                               array(
+                                       'a' => true,
+                                       'b' => false,
+                               ),
+                       ),
+                       'oneOf applies the correct schema'             => array(
+                               array(
+                                       '$schema' => 'http://json-schema.org/draft-04/schema#',
+                                       'type'    => 'object',
+                                       'oneOf'   => array(
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'a' => 1,
+                                       'b' => 2,
+                               ),
+                               array(
+                                       'b' => 2,
+                               ),
+                       ),
+                       'oneOf ignored if no valid schema was found'   => array(
+                               array(
+                                       '$schema' => 'http://json-schema.org/draft-04/schema#',
+                                       'type'    => 'object',
+                                       'anyOf'   => array(
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'a' => true,
+                                       'b' => false,
+                               ),
+                               array(
+                                       'a' => true,
+                                       'b' => false,
+                               ),
+                       ),
+                       'oneOf combined with base'                     => array(
+                               array(
+                                       '$schema'    => 'http://json-schema.org/draft-04/schema#',
+                                       'type'       => 'object',
+                                       'properties' => array(
+                                               'c' => array(
+                                                       'type'    => 'integer',
+                                                       'context' => array( 'edit' ),
+                                               ),
+                                       ),
+                                       'oneOf'      => array(
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'string',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                               array(
+                                                       'properties' => array(
+                                                               'a' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'edit' ),
+                                                               ),
+                                                               'b' => array(
+                                                                       'type'    => 'integer',
+                                                                       'context' => array( 'view' ),
+                                                               ),
+                                                       ),
+                                               ),
+                                       ),
+                               ),
+                               array(
+                                       'a' => 1,
+                                       'b' => 2,
+                                       'c' => 3,
+                               ),
+                               array(
+                                       'b' => 2,
+                               ),
+                       ),
</ins><span class="cx" style="display: block; padding: 0 10px">                 );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre>
</div>
</div>

</body>
</html>