Skip to content

Commit 409d03b

Browse files
committed
Abilities API: Normalize input from schema
Without this patch REST API would require a weird empty `?input` field for optional input given how the current controller works with input schema when it defines the expected shape. This patch normalizes the input for the ability, applying the default value from the input schema when needed. Developed in #10395. Follow-up [61032], [61045]. Props gziolo, jorgefilipecosta, mukesh27. Fixes #64139. git-svn-id: https://develop.svn.wordpress.org/trunk@61047 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 1c85052 commit 409d03b

File tree

5 files changed

+80
-11
lines changed

5 files changed

+80
-11
lines changed

src/wp-includes/abilities-api/class-wp-ability.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,31 @@ public function get_meta_item( string $key, $default_value = null ) {
400400
return array_key_exists( $key, $this->meta ) ? $this->meta[ $key ] : $default_value;
401401
}
402402

403+
/**
404+
* Normalizes the input for the ability, applying the default value from the input schema when needed.
405+
*
406+
* When no input is provided and the input schema is defined with a top-level `default` key, this method returns
407+
* the value of that key. If the input schema does not define a `default`, or if the input schema is empty,
408+
* this method returns null. If input is provided, it is returned as-is.
409+
*
410+
* @since 6.9.0
411+
*
412+
* @param mixed $input Optional. The raw input provided for the ability. Default `null`.
413+
* @return mixed The same input, or the default from schema, or `null` if default not set.
414+
*/
415+
public function normalize_input( $input = null ) {
416+
if ( null !== $input ) {
417+
return $input;
418+
}
419+
420+
$input_schema = $this->get_input_schema();
421+
if ( ! empty( $input_schema ) && array_key_exists( 'default', $input_schema ) ) {
422+
return $input_schema['default'];
423+
}
424+
425+
return null;
426+
}
427+
403428
/**
404429
* Validates input data against the input schema.
405430
*
@@ -536,6 +561,7 @@ protected function validate_output( $output ) {
536561
* @return mixed|WP_Error The result of the ability execution, or WP_Error on failure.
537562
*/
538563
public function execute( $input = null ) {
564+
$input = $this->normalize_input( $input );
539565
$is_valid = $this->validate_input( $input );
540566
if ( is_wp_error( $is_valid ) ) {
541567
return $is_valid;

src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public function get_items( $request ) {
9393
$offset = ( $page - 1 ) * $per_page;
9494

9595
$total_categories = count( $categories );
96-
$max_pages = ceil( $total_categories / $per_page );
96+
$max_pages = (int) ceil( $total_categories / $per_page );
9797

9898
if ( $request->get_method() === 'HEAD' ) {
9999
$response = new WP_REST_Response( array() );

src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ static function ( $ability ) use ( $category ) {
111111
$offset = ( $page - 1 ) * $per_page;
112112

113113
$total_abilities = count( $abilities );
114-
$max_pages = ceil( $total_abilities / $per_page );
114+
$max_pages = (int) ceil( $total_abilities / $per_page );
115115

116116
if ( $request->get_method() === 'HEAD' ) {
117117
$response = new WP_REST_Response( array() );

src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ public function check_ability_permissions( $request ) {
159159
}
160160

161161
$input = $this->get_input_from_request( $request );
162+
$input = $ability->normalize_input( $input );
162163
$is_valid = $ability->validate_input( $input );
163164
if ( is_wp_error( $is_valid ) ) {
164165
$is_valid->add_data( array( 'status' => 400 ) );

tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -921,12 +921,11 @@ public function test_ability_without_annotations_defaults_to_post_method(): void
921921
}
922922

923923
/**
924-
* Test edge case with empty input for both GET and POST methods.
924+
* Test edge case with empty input for GET method.
925925
*
926926
* @ticket 64098
927927
*/
928-
public function test_empty_input_handling(): void {
929-
// Registers abilities for empty input testing.
928+
public function test_empty_input_handling_get_method(): void {
930929
wp_register_ability(
931930
'test/read-only-empty',
932931
array(
@@ -946,6 +945,55 @@ public function test_empty_input_handling(): void {
946945
)
947946
);
948947

948+
// Tests GET with no input parameter.
949+
$get_request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/test/read-only-empty/run' );
950+
$get_response = $this->server->dispatch( $get_request );
951+
$this->assertEquals( 200, $get_response->get_status() );
952+
$this->assertTrue( $get_response->get_data()['input_was_empty'] );
953+
}
954+
955+
/**
956+
* Test edge case with empty input for GET method, and normalized input using schema.
957+
*
958+
* @ticket 64098
959+
*/
960+
public function test_empty_input_handling_get_method_with_normalized_input(): void {
961+
wp_register_ability(
962+
'test/read-only-empty-array',
963+
array(
964+
'label' => 'Read-only Empty Array',
965+
'description' => 'Read-only with inferred empty array input from schema.',
966+
'category' => 'general',
967+
'input_schema' => array(
968+
'type' => 'array',
969+
'default' => array(),
970+
),
971+
'execute_callback' => static function ( $input ) {
972+
return is_array( $input ) && empty( $input );
973+
},
974+
'permission_callback' => '__return_true',
975+
'meta' => array(
976+
'annotations' => array(
977+
'readonly' => true,
978+
),
979+
'show_in_rest' => true,
980+
),
981+
)
982+
);
983+
984+
// Tests GET with no input parameter.
985+
$get_request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/test/read-only-empty-array/run' );
986+
$get_response = $this->server->dispatch( $get_request );
987+
$this->assertEquals( 200, $get_response->get_status() );
988+
$this->assertTrue( $get_response->get_data() );
989+
}
990+
991+
/**
992+
* Test edge case with empty input for POST method.
993+
*
994+
* @ticket 64098
995+
*/
996+
public function test_empty_input_handling_post_method(): void {
949997
wp_register_ability(
950998
'test/regular-empty',
951999
array(
@@ -962,12 +1010,6 @@ public function test_empty_input_handling(): void {
9621010
)
9631011
);
9641012

965-
// Tests GET with no input parameter.
966-
$get_request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/test/read-only-empty/run' );
967-
$get_response = $this->server->dispatch( $get_request );
968-
$this->assertEquals( 200, $get_response->get_status() );
969-
$this->assertTrue( $get_response->get_data()['input_was_empty'] );
970-
9711013
// Tests POST with no body.
9721014
$post_request = new WP_REST_Request( 'POST', '/wp-abilities/v1/abilities/test/regular-empty/run' );
9731015
$post_request->set_header( 'Content-Type', 'application/json' );

0 commit comments

Comments
 (0)