2

I'm currently setting up my first WP REST API and am attempting to understand how WP implements JSON Schema for the API endpoints, and the docs are imho really poor and confusing; so I wanted to make sure that I understand things properly:

  1. According to this, you can specify the 'schema' parameter within the $args argument when registering a new rest route, to provide a JSON schema informing about the structure of the response of your endpoint. That schema should be delivered to any client making an OPTIONS HTTP request to the according endpoint. That works.

  2. You can then specify any combination of the WP JSON Schema parameters for every parameter of your $args array when using register_rest_route, to inform about the required format of the request's arguments, for that respective endpoint. When I add for example a description key to the argument X of the endpoint Y, and then inspect the general https://example.org/wp-json/ REST schema of the website via GET, that description indeed shows up under the argument X of my API endpoint Y. So that also works.

  3. What I totally don't understand is, how can I use the JSON schema I've specified in 2) for validation and subsequent (if validation is passed) sanitization? The docs mention the built-in rest_validate_value_from_schema and rest_validate_value_from_schema functions, but do not really show any examples / explain how you implement your JSON schemas provided for the request data (step 2)) for validation / sanitization.

How is that actually done using WP's built-in JSON schema ?

8
  • the schema is only for the response, not the request, that's why you are only finding documentation for response schemas. Defining the structure of the request aka expected/optional parameters and data about them is defined differently, and it looks like you've already found that documentation. Perhaps you're expecting that you can write a scheme somewhere for requests and provide it? If so I have never seen that and that's not how the API is built, my expectation would be that WordPress would use the parameters I gave it to build that schema. Unless I've misunderstood your question?
    – Tom J Nowell
    Jul 1, 2022 at 13:18
  • If your question is how to implement the validation aka where you told WP that a parameter is an integer and are now looking to write the code that checks if it's an integer, then the answer is you don't, WordPress does that for you, and if your value is more complex then you do that as a part of the registration process when you define it.
    – Tom J Nowell
    Jul 1, 2022 at 13:19
  • It would be more helpful if your question contained a concrete example and code, rather than being hyper-abstract and generic, use the edit link to make the question more tangible and concrete, it will be much easier to understand and write answers, right now there are ambiguities, unshared assumptions, and implied things
    – Tom J Nowell
    Jul 1, 2022 at 13:21
  • you're expecting that you can write a scheme somewhere for requests and provide it? Exactly, yes, and I feel that you have to do it via the mentioned rest_validate_value_from_schema and rest_validate_value_from_schema functions. To these, you can actually indeed provide a WP JSON Schema (as PHP array, as for the schema parameter when registering a route), and the two callbacks then use that to execute the validation and sanitization as built-in by wordpess. Think I figured it out; will post the resulting code in a while.
    – DevelJoe
    Jul 1, 2022 at 13:42
  • I think those functions are intended for when you construct your own REST controller, they wouldn't be how you provide a schema, but rather if you had already provided a schema it would be the function WP uses to validate things against it, which isn't quite what you're aiming to do
    – Tom J Nowell
    Jul 1, 2022 at 13:50

3 Answers 3

4

You're already providing a schema, the parameters in args are their own schemas, but WordPress does not enforce this schema or perform sanitisation/validation by default.

You can make WordPress enforce it though by setting the validation and sanitisation callbacks to the following:

'sanitize_callback' => 'rest_sanitize_request_arg',
'validate_callback' => 'rest_validate_request_arg',

rest_validate_request_arg will look up the parameter in the args and use that as the schema and pass it to rest_validate_value_from_schema for validation. The format of each parameter in the args section is also the format of the schema for that input because it is a schema.

For example, this is how WordPress defines validates and enforces the per_page parameter in the args section of collection endpoints:

            'per_page' => array(
                'description'       => __( 'Maximum number of items to be returned in result set.' ),
                'type'              => 'integer',
                'default'           => 10,
                'minimum'           => 1,
                'maximum'           => 100,
                'sanitize_callback' => 'absint',
                'validate_callback' => 'rest_validate_request_arg',
            ),

And here is how it does it internally:

function rest_validate_request_arg( $value, $request, $param ) {
    $attributes = $request->get_attributes();
    if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
        return true;
    }
    $args = $attributes['args'][ $param ];
 
    return rest_validate_value_from_schema( $value, $args, $param );
}

There is also a rest_sanitize_request_arg for sanitisation, but this is used in fewer locations in core itself.

1
-1

So the way to do what's mentioned can be reached by the following, coded in your main plugin file or the active theme's functions.php. But as you see, I need to specify the JSON schema used for validation, sanitization, and request argument documentation. I just would like to know if there's a way to provide that schema only once and use it everywhere (of course in a way different from just storing it in a variable).

add_action(
                'rest_api_init',
                function () {
                    
                    // Register Route - START
                    
                    register_rest_route(
                        'api/v1',
                        '/zoo',
                        [
                            [
                                'methods'             => 'POST',
                                'permission_callback' => function ( WP_REST_Request $request ) { // authenticate client + check nonce
                                },
                                'callback'            => 'controller_callback',
                                'args'                => [
                                    'animal' => [
                                        'type' => 'string',
                                        'oneOf' => ['lion','tiger'],
                                        'description' => "string determining the kind of animal you're trying to create",
                                        'required'          => true,
                                        'validate_callback' => function ( $param ) {
                                            return rest_validate_value_from_schema( $param, ['type' => 'string', 'enum' => ['lion','tiger']]);
                                        },
                                        'sanitize_callback' => function ( $param ) {
                                            return rest_sanitize_value_from_schema( $param, ['type' => 'string', 'enum' => ['lion','tiger']]);
                                        }
                                    ],
                                    '_wpnonce' => [
                                        'required' => true
                                    ]
                                ]
                            ],
                            'schema' => function() { return array(
                                // This tells the spec of JSON Schema we are using which is draft 4.
                                '$schema'              => 'http://json-schema.org/draft-04/schema#',
                                // The title property marks the identity of the resource.
                                'title'                => 'animal',
                                'type'                 => 'object',
                                // In JSON Schema you can specify object properties in the properties attribute.
                                'properties'           => array(
                                    'id' => array(
                                        'description'  => 'ID of the created animal',
                                        'type'         => 'integer',
                                        'readonly'     => true,
                                    ),
                                    'animal' => array(
                                        'description'  => 'identifier of the animal type',
                                        'type'         => 'string',
                                    )
                                )); }
                        ]
                    );
    
                    // Register Route - END
                }
            );
5
  • This doesn't make a lot of sense, it looks like you wanted to whitelist the possible parameters of an argument and thought schema were the solution, but you can already use the schema parameter to specify an enum and eliminate your validate and sanitize parameters in the args section. This is a very weird thing to solve this problem, and those are not request schema
    – Tom J Nowell
    Jul 1, 2022 at 14:52
  • The schema is for the structure of the response, the stuff in $args for the request params, do we agree on that? Could you may constructively explain what I'm doing wrong, instead of telling we that I'm doing it wrong? ^^I asked the question because I did not know how to do this with WP, after all
    – DevelJoe
    Jul 1, 2022 at 14:53
  • the problem is you're misusing a WP core function to implement essentially a schema within a schema so that you can workaround a different problem. The problem you're trying to solve by doing this has a much simpler solution
    – Tom J Nowell
    Jul 1, 2022 at 15:03
  • You're talking about custom validation and sanitization callbacks as the simpler solution? It would be really useful if you could provide a concrete constructive answer to my question, instead of constantly bringing in other stuff into this post, telling that there's a simpler solution, that REST is this and that, and so on and so fourth. All I'd like to know is how to solve the problem described in my question.
    – DevelJoe
    Jul 1, 2022 at 15:06
  • And your simpler solution, just to be clear on that, is to provide Argument schema to every parameter and then provide rest_validate_request_arg as the value of the validate_callback ? Am I understanding your answer correctly, that's the solution ?
    – DevelJoe
    Jul 1, 2022 at 15:25
-1

So the finally correct (and tested) way to implement what I was looking for is:

add_action(
                'rest_api_init',
                function () {
                    
                    // Register Route - START
                    
                    register_rest_route(
                        'api/v1',
                        '/zoo',
                        [
                            [
                                'methods'             => 'POST',
                                'permission_callback' => function ( WP_REST_Request $request ) { // authenticate client + check nonce
                                },
                                'callback'            => 'controller_callback',
                                'args'                => [
                                    'animal' => [
                                        'type' => 'string',
                                        'enum' => ['lion','tiger'],
                                        'description' => "string determining the kind of animal you're trying to create",
                                        'required'          => true,
                                        'validate_callback' => 'rest_validate_request_arg',
                                        'sanitize_callback' => 'rest_sanitize_request_arg'
                                    ],
                                    '_wpnonce' => [
                                        'required' => true
                                    ]
                                ]
                            ]
                        ]
                    );
    
                    // Register Route - END
                }
            );

As a side-note, be aware that using JSON schema arguments exposes your argument validation + sanitization criteria through your endpoints, and will cause default responses with clear details about where your validation / sanitization failed.

This is perfect for development environments, but if you do not want this to happen in production environments, you're better off using custom validation and sanitization callbacks, where you can explicitly determine the information returned to your client in case of any failure, as documented here.

Was just a final thought I had on this story.

2
  • @Tom I really really hope that wasn't you who downvoted this answer ^^ The person who downvoted is very welcome to inform what's wrong with this answer.
    – DevelJoe
    Jul 1, 2022 at 16:15
  • It was not me, though your answer doesn't state what it was that you did in the code snippet or how it answers the question, just that the solution was applied to it as an example
    – Tom J Nowell
    Jul 2, 2022 at 22:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.