| | 337 | |
| | 338 | |
| | 339 | **SIMPLER EXAMPLE** |
| | 340 | |
| | 341 | Here you go with another, much simpler example, this time with a REST route. Let's say you have an endpoint to which you want to submit fruit data. Every distinct fruit (banana, apple, pear) has its own set of attributes: |
| | 342 | |
| | 343 | {{{#!php |
| | 344 | <?php |
| | 345 | $banana_attributes = [ |
| | 346 | "bananas_color" => [ |
| | 347 | 'type' => 'string', |
| | 348 | 'pattern' => '^yellow$', |
| | 349 | 'required' => true |
| | 350 | ], |
| | 351 | "bananas_age" => [ |
| | 352 | 'type' => 'integer', |
| | 353 | 'enum' => [2,3,4,5], |
| | 354 | 'required' => true |
| | 355 | ] |
| | 356 | ]; |
| | 357 | |
| | 358 | $apple_attributes = [ |
| | 359 | "apples_color" => [ |
| | 360 | 'type' => 'string', |
| | 361 | 'pattern' => '^red$', |
| | 362 | 'required' => true |
| | 363 | ], |
| | 364 | "apples_radius" => [ |
| | 365 | 'type' => 'integer', |
| | 366 | 'enum' => [2,3,4,5], |
| | 367 | 'required' => true |
| | 368 | ] |
| | 369 | ]; |
| | 370 | |
| | 371 | $pear_attributes = [ |
| | 372 | "pears_color" => [ |
| | 373 | 'type' => 'string', |
| | 374 | 'pattern' => '^green$', |
| | 375 | 'required' => true |
| | 376 | ], |
| | 377 | "pears_height" => [ |
| | 378 | 'type' => 'integer', |
| | 379 | 'enum' => [2,3,4,5], |
| | 380 | 'required' => true |
| | 381 | ] |
| | 382 | ]; |
| | 383 | }}} |
| | 384 | |
| | 385 | You want to implement JSON Schema logic using `oneOf` that validates an input payload to contain either only banana attributes, banana and apple attributes or banana and apple and pear attributes. A payload like this would for example pass: |
| | 386 | |
| | 387 | {{{ |
| | 388 | { |
| | 389 | "data": { |
| | 390 | "bananas_color": "yellow", |
| | 391 | "bananas_age": 4, |
| | 392 | "apples_color": "red", |
| | 393 | "apples_radius": 3, |
| | 394 | "pears_color": "green", |
| | 395 | "pears_height": 5 |
| | 396 | } |
| | 397 | } |
| | 398 | }}} |
| | 399 | |
| | 400 | So I would do something like: |
| | 401 | |
| | 402 | {{{#!php |
| | 403 | <?php |
| | 404 | register_rest_route( |
| | 405 | 'mynamespace/v1', |
| | 406 | '/fruits', |
| | 407 | [ |
| | 408 | [ |
| | 409 | 'methods' => 'POST', |
| | 410 | 'permission_callback' => '__return_true', |
| | 411 | 'callback' => [ |
| | 412 | FruitsController::class, |
| | 413 | 'process' |
| | 414 | ], |
| | 415 | 'args' => [ |
| | 416 | 'data' => [ |
| | 417 | 'type' => 'object', |
| | 418 | 'additionalProperties' => false, |
| | 419 | 'required' => true, |
| | 420 | 'oneOf' => [ |
| | 421 | [ |
| | 422 | 'title' => 'only_banana', |
| | 423 | 'type' => 'object', |
| | 424 | 'additionalProperties' => false, |
| | 425 | 'properties' => $banana_attributes |
| | 426 | ], |
| | 427 | [ |
| | 428 | 'title' => 'banana_and_apple', |
| | 429 | 'type' => 'object', |
| | 430 | 'additionalProperties' => false, |
| | 431 | 'properties' => array_merge( |
| | 432 | $banana_attributes, |
| | 433 | $apple_attributes |
| | 434 | ) |
| | 435 | ], |
| | 436 | [ |
| | 437 | 'title' => 'banana_and_apple_and_pear', |
| | 438 | 'type' => 'object', |
| | 439 | 'additionalProperties' => false, |
| | 440 | 'properties' => array_merge( |
| | 441 | $banana_attributes, |
| | 442 | $apple_attributes, |
| | 443 | $pear_attributes |
| | 444 | ) |
| | 445 | ] |
| | 446 | ] |
| | 447 | ] |
| | 448 | ] |
| | 449 | ] |
| | 450 | ] |
| | 451 | ); |
| | 452 | }}} |
| | 453 | |
| | 454 | |
| | 455 | Now with this, I have two problems: |
| | 456 | |
| | 457 | |
| | 458 | **A)** As @TimothyBlynJacobs said; the above code will block incoming requests with a `400`. You actually have to set `additionalProperties` on the level of the `oneOf` key to `true`, or simply omit the `additionalProperties` key there (which I believe results in the same, as the default value of that one is `true`). The consequence of this is that I cannot block incoming requests that have more data in the payload than needed, correct? |
| | 459 | |
| | 460 | **B)** If I'm understanding @TimothyBlynJacobs correctly, he mentioned that setting `type => object` on the same level as `oneOf` is not necessary. But you will notice in this example, that if you omit the `type` key at the `oneOf` level, the validation will no longer work. So I figured you have to specify it at both levels; can you confirm that? |
| | 461 | |
| | 462 | Then, I also have a question regarding how WordPress executes validation and sanitization based on JSON Schema: |
| | 463 | |
| | 464 | **C)** When I started with WP REST, I've always specified: |
| | 465 | |
| | 466 | {{{ |
| | 467 | 'validate_callback' => 'rest_validate_request_arg', |
| | 468 | 'sanitize_callback' => 'rest_sanitize_request_arg' |
| | 469 | }}} |
| | 470 | |
| | 471 | For every argument, to ensure that all arguments are both validated and sanitized according to what I specified in the JSON Schema. I now noticed that WP seems to do at least the validation by default; even if I omit the above-mentioned. Does WP now actually automatically trigger the default validation `rest_validate_request_arg` and sanitization `rest_sanitize_request_arg` functions based on what you've provided for your JSON schema? Or do I stell have to provide it explicitly to every argument? |
| | 472 | |
| | 473 | **D)** In my example above, to simplify the JSON Schema related to the usecase, I've grouped the submitted payload into an inner object key `data`. This means that I now have to adapt the way I submit payloads to endpoints using `oneOf` logic; by wrapping the payloads in an inner `{"data":{}}` object. Is that the way to go; or can you in WP JSON Schema maybe directly tell the `args` that the payload is a simply object; basically under no key? |