Abilities API: Normalize required schema shape for REST responses#12013
Abilities API: Normalize required schema shape for REST responses#12013gziolo wants to merge 1 commit into
required schema shape for REST responses#12013Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
6265213 to
03946ca
Compare
When exposing an ability's input and output schemas through the REST API, convert any draft-03 per-property `required` boolean into a draft-04 `required` array of property names on the parent object schema, and drop boolean `required` flags that have no draft-04 equivalent (such as on a scalar root schema). WordPress accepts both forms internally, but only the array form is valid JSON Schema draft-04, so clients consuming `wp-abilities/v1` now receive a conformant schema. The transformation only affects the REST response copy; server-side validation continues to use the stored schema via validate_input(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
03946ca to
508a98f
Compare
There was a problem hiding this comment.
Pull request overview
Normalizes Abilities API REST-exposed JSON Schemas so the required keyword is always represented in JSON Schema draft-04 form (parent required: [] array), even when abilities were registered using WordPress’s draft-03-style per-property required: true/false booleans. This keeps the REST schema contract compatible with standard JSON Schema validators consumed by clients.
Changes:
- Add server-side schema-response normalization to convert per-property
requiredbooleans into a parentrequiredarray (and drop boolean-only cases with no draft-04 equivalent). - Preserve/merge existing draft-04
requiredarrays while de-duplicating entries. - Add PHPUnit coverage for top-level, nested object, array
itemsobject, mixed required forms,required: false, and scalar-root cases.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php |
Adds required normalization logic to prepare_schema_for_response() so REST responses conform to JSON Schema draft-04. |
tests/phpunit/tests/rest-api/wpRestAbilitiesV1ListController.php |
Adds tests asserting correct conversion/stripping/merging behavior for required across multiple schema shapes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $required = ( isset( $schema['required'] ) && is_array( $schema['required'] ) ) ? $schema['required'] : array(); | ||
| foreach ( $schema['properties'] as $property => $property_schema ) { | ||
| if ( $this->is_associative_array( $property_schema ) && isset( $property_schema['required'] ) && is_bool( $property_schema['required'] ) ) { | ||
| if ( true === $property_schema['required'] ) { | ||
| $required[] = $property; | ||
| } | ||
| unset( $schema['properties'][ $property ]['required'] ); | ||
| } | ||
| } | ||
| if ( ! empty( $required ) ) { | ||
| $schema['required'] = array_values( array_unique( $required ) ); | ||
| } |
| unset( $schema['properties'][ $property ]['required'] ); | ||
| } | ||
| } | ||
| if ( ! empty( $required ) ) { |
There was a problem hiding this comment.
Just to avoid empty() in favor of something more specific:
| if ( ! empty( $required ) ) { | |
| if ( count( $required ) > 0 ) { |
There was a problem hiding this comment.
Or alternatively:
| if ( ! empty( $required ) ) { | |
| if ( $required ) { |
|
|
||
| // Collect draft-03 per-property `required: true` flags into a draft-04 | ||
| // `required` array of property names on the parent object schema. | ||
| if ( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) { |
There was a problem hiding this comment.
I wonder if REST is the right place for this transformation or if it is something that should be considered at the ability registration level. The advantage of being on the ability level, is that for things like wp_ai_client use abilities and MCP adapter the abilities would also have the draft-04 shape.
jorgefilipecosta
left a comment
There was a problem hiding this comment.
Left a comment for consideration but other than that it looks good.
| // Collect draft-03 per-property `required: true` flags into a draft-04 | ||
| // `required` array of property names on the parent object schema. | ||
| if ( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) { | ||
| $required = ( isset( $schema['required'] ) && is_array( $schema['required'] ) ) ? $schema['required'] : array(); |
There was a problem hiding this comment.
In case of a mix where where we use required = array(...) and also required = true/false at the property level, in this code were are using both, but on rest_validate_object_value_from_schema https://github.com/gziolo/wordpress-develop/blob/508a98f4daab5dcc277c07059bcea0da24795eb1/src/wp-includes/rest-api.php#L2378-L2390 we use just required = array(...) if it was set and then propertly level if it was not set, so we have a difference of behavior. Should we align both cases?
Summary
Normalizes the
requiredkeyword in an ability's input and output schemas to its JSON Schema draft-04 form when they are exposed through the REST API (wp-abilities/v1).Background
The input and output schemas are a public contract: clients — such as the
@wordpress/abilitiesJS client — consume them as standard JSON Schema and validate ability input and output against them, so they must receive a draft-04-conformant shape that standard validators understand. On the server, WordPress accepts either a draft-04requiredarray of property names on the object schema, or a draft-03 per-propertyrequiredboolean (seerest_validate_value_from_schema()). Both validate server-side, but only the array form is valid JSON Schema draft-04. Ability schemas registered with the per-property boolean form (as shown in thewp_register_ability()examples) were therefore exposed to clients in a shape that standard validators do not recognize and that does not conform to the draft-04 contract the endpoint advertises via$schema.Changes
In
WP_REST_Abilities_V1_List_Controller::prepare_schema_for_response():required: truebooleans and emit them as arequired: [ ... ]array of property names on the parent object schema — applied recursively, so nested objects each get their own array.required: falseand any booleanrequiredthat has no draft-04 equivalent (for example on a scalar root schema).requiredarray, de-duplicating names.The transformation only rewrites the REST response copy; server-side validation continues to use the stored schema via
validate_input(), so behavior is unchanged. Adds test coverage for the conversion: top-level, nested objects, objects nested inside arrayitems,required: false, mixed draft-04 array + draft-03 booleans, and the scalar-root case.Trac ticket: https://core.trac.wordpress.org/ticket/64955
🤖 Generated with Claude Code