To ensure the responses sent by the JSON API module are standards-compliant, we should validate JSON API responses against the official specification. This might be done only in tests, or as a PHP assert() that verifies all JSON API responses.

Test speed impacts of adding assert()'s for all responses on both PHP 5.6 and PHP 7, in Production configuration. If those impacts are minimal, proceed with a patch along that line.

Comments

hampercm created an issue. See original summary.

hampercm’s picture

Issue summary: View changes
wim leers’s picture

Woot! Thanks Chris :) This is possibly the single most important issue in the JSON API issue queue right now!

hampercm’s picture

Priority: Normal » Major
Status: Active » Needs review
StatusFileSize
new13.15 KB

Here's a preliminary patch for performing validation against the official JSON API schema. I'd like to test it a bit more before it is committed, but it's already discovered a couple issues with responses that didn't meet spec, so that's a ++

Bumping Priority to Major based on @Wim Leers's comment. Automated tests will fail until child issues are resolved.

Status: Needs review » Needs work

The last submitted patch, 4: validate_responses-2840677-4.patch, failed testing.

hampercm’s picture

StatusFileSize
new13.31 KB
new365 bytes

Add some elements to the composer.json file that are required for Drupal modules.

hampercm’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 6: validate_responses-2840677-6.patch, failed testing.

e0ipso’s picture

Title: Validate responses against the JSON API specification » [PP-2] Validate responses against the JSON API specification
e0ipso’s picture

We have the ability to create JSON Schemas for the resources themselves based on the Typed Data (using https://github.com/e0ipso/data_model). That means that we could test the output against the specialized schema per resource, instead of the generic schema. This would have the benefit of asserting required values, cardinalities, etc. However the generated schema may have bugs and cause false alerts.

Thoughts?

e0ipso’s picture

Title: [PP-2] Validate responses against the JSON API specification » [PP-1] Validate responses against the JSON API specification
hampercm’s picture

Status: Needs work » Needs review
StatusFileSize
new14.34 KB
new2.65 KB

This patch fixes a couple of issues with #4
- Don't flag empty responses as invalid (DELETE responses for example)
- Use JSON::encode() to ensure web-safe characters are logged

It also adds a debugging "printout" to the first functional test, to help figure out why it's failing. This will need to be removed later, as it will cause the test to fail.

Status: Needs review » Needs work

The last submitted patch, 12: pp_1_validate-2840677-12.patch, failed testing.

hampercm’s picture

Status: Needs work » Needs review
StatusFileSize
new14.33 KB

Hmmm, looks like the 500 response has no content, so no help there. Checking to see if the validator class exists...

Status: Needs review » Needs work

The last submitted patch, 14: pp_1_validate-2840677-14.patch, failed testing.

wim leers’s picture

It looks like DrupalCI is not installing the PHP lib. Described the problem at #2597778-78: Install composer dependencies for contrib projects before running tests, and pinged Mixologic on IRC.

wim leers’s picture

@e0ipso: I think that can be a next step. Regardless of that, I think it's critical that we comply with the generic/basic/barebones schema for JSON API responses. If we don't comply with that, it's pointless to generate our own more specific schemas.
In other words: we must ALWAYS comply with the "basic JSON API JSON schema". As a next step, we should have a JSON schema for any JSON API extensions we support. And as a final step, we can have JSON schemas for our specific resources.


This is looking great, and wonderfully simple :)

  1. +++ b/src/RequestHandler.php
    @@ -158,6 +159,8 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +    assert($this->validateResponse($response), 'A JSON API response failed validation. Please report this at https://www.drupal.org/project/issues/jsonapi');
    

    This is currently running 100% of the time on PHP 5.

    On PHP 5, you must wrap the assertion logic in quotes, i.e. make it a string. PHP 5 eval()s these statements.

  2. +++ b/src/RequestHandler.php
    @@ -280,4 +283,42 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +  protected function validateResponse(ResourceResponse $response) {
    

    Should be static, because this doesn't use any data in the RequestHandler class (and shouldn't).

  3. +++ b/src/RequestHandler.php
    @@ -280,4 +283,42 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +    $responseData = json_decode($response->getContent());
    

    s/$responseData/$response_data/

    Or $response_body.

  4. +++ b/src/RequestHandler.php
    @@ -280,4 +283,42 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +    if ($response->headers->get('Content-Type') != 'application/vnd.api+json') {
    +      return TRUE;
    +    }
    

    Is this really necessary?

  5. +++ b/src/RequestHandler.php
    @@ -280,4 +283,42 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +    $schema_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'jsonapi')
    +      . '/schema.json';
    

    It's okay for this all to be put on a single line.

  6. +++ b/src/RequestHandler.php
    --- a/tests/src/Functional/JsonApiFunctionalTest.php
    +++ b/tests/src/Functional/JsonApiFunctionalTest.php
    

    I'd like to see an additional test here where this newly added assertion is explicitly failing.

    Because that would allow us to ensure that the DX of failed JSON API JSON schema validation is solid.

hampercm’s picture

Status: Needs work » Postponed
Issue tags: +blocked
StatusFileSize
new15.62 KB
new4.2 KB

Thanks for the review!

1) Oops. I had originally done this as a string, but changed it to debug some weirdness. Forgot to change it back.
2) Done
3) Done
4) Not really necessary, I suppose. Removed.
5) Done
6) Test added.

@e0ipso: Regarding creating custom schemas, I agree with @Wim Leers. That does seem like a nice thing to explore, but I'd say create a separate issue for it.

This issue is blocked on child issues being resolved, and on a bug in the Drupal CI infrastructure, though the latter can be worked around.

e0ipso’s picture

That does seem like a nice thing to explore, but I'd say create a separate issue for it.

Yup, that's what I meant. I was sharing a random thought in there.

Mixologic’s picture

I fixed the drupalci issue, and jsonschema is being installed now. Looks like its still failing #14 has different errors now.

wim leers’s picture

Thanks, Mixologic!

hampercm’s picture

Assigned: hampercm » Unassigned

Yes, there will be test failures until the child issues are resolved. Glad to hear about the Drupal CI fix. @Mixologic++

wim leers’s picture

hampercm’s picture

@Wim Leers yes, that's correct. Tests won't pass until those are resolved.

wim leers’s picture

Title: [PP-1] Validate responses against the JSON API specification » [PP-2] Validate responses against the JSON API specification

Alright, updating issue title to reflect that then. :)

wim leers’s picture

+++ b/composer.json
@@ -0,0 +1,9 @@
+    "require": {

When the blockers are in and we resume work on this issue/patch, we should change this to require-dev.

hampercm’s picture

If that's done, we should also add a check for the existence of the validator class, in case the dependency isn't installed.

e0ipso’s picture

If that's done, we should also add a check for the existence of the validator class, in case the dependency isn't installed.

I think that's acceptable.

wim leers’s picture

Looking at #26 again, I think it could be argued either way actually.

hampercm’s picture

I had originally considered making it a require-dev, but it seemed like there would be an advantage to having validation on all installs. The performance impacts from validation aren't too bad:

PHP 7.0: 19.8 req/sec vs. 22.0
PHP 5.6: 15.9 req/sec vs. 20.2

and are statistically insignificant with assertions turned off (<1% performance hit). The code size of the dependency is about 128k.

My preference would be to leave as a require'd dependency. Perhaps we could plan to move it to a require-dev when the module stabilizes?

e0ipso’s picture

My preference would be to leave as a require'd dependency. Perhaps we could plan to move it to a require-dev when the module stabilizes?

I don't really care as long as we have the same outcome, which is there is a conscious decision to turn this on (regardless of the composer command needed).

wim leers’s picture

Perhaps we could plan to move it to a require-dev when the module stabilizes?

Sounds good to me. Let's get it stable first.

e0ipso’s picture

Title: [PP-2] Validate responses against the JSON API specification » Validate responses against the JSON API specification
Status: Postponed » Needs review

This is unblocked now.

Status: Needs review » Needs work

The last submitted patch, 18: pp_1_validate-2840677-18.patch, failed testing.

wim leers’s picture

WOOT!

e0ipso’s picture

Issue tags: -blocked +Needs reroll
hampercm’s picture

Assigned: Unassigned » hampercm

:) Thanks for your help with that, e0ipso!

working on this...

hampercm’s picture

Status: Needs work » Needs review
StatusFileSize
new15.68 KB
new1.03 KB

Patch rerolled.

I also changed the validation library to a require-dev dependency in the composer file. Verifying that automated tests still pass...

This isn't quite ready to commit yet, as I'll need to add a check to the validation code for the case where the validation library isn't installed.

hampercm’s picture

Assigned: hampercm » Unassigned
StatusFileSize
new15.76 KB
new636 bytes

Check added. This should be good to go!

wim leers’s picture

Issue tags: -Needs reroll
  1. +++ b/src/Controller/RequestHandler.php
    @@ -165,6 +166,8 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +    assert('$this->validateResponse($response)', 'A JSON API response failed validation. Please report this at https://www.drupal.org/project/issues/jsonapi');
    

    Wouldn't it be better to show the validation errors here, instead of this generic "please report"? Only developers will see this, and they'll be smart enough to report it?

  2. +++ b/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -746,4 +747,53 @@ class JsonApiFunctionalTest extends BrowserTestBase {
    +    // Test validation failure: no "type" in "data".
    ...
    +    // Test validation success.
    ...
    +    // Test validation of an empty response passes.
    

    Looks sensible :)

hampercm’s picture

Assigned: Unassigned » hampercm
Status: Needs review » Needs work

The validation errors do get watchdog logged. I'll add a bit of text to the assert message explaining that.

hampercm’s picture

Assigned: hampercm » Unassigned
Status: Needs work » Needs review
StatusFileSize
new15.8 KB
new806 bytes

Refer to recent log messages in assert() message

e0ipso’s picture

Sorry I'm late to this, but I'd rather remove references to https://www.drupal.org/project/issues/jsonapi since we're proposing this for inclusion into core.

Maybe: A JSON API response failed validation (see /admin/reports/dblog for details). Please report this at the issue queue.'

hampercm’s picture

StatusFileSize
new16.93 KB
new829 bytes

Good point! Fixed.

dawehner’s picture

  1. +++ b/composer.json
    @@ -0,0 +1,9 @@
    +    "require-dev": {
    +        "justinrainbow/json-schema": "^4.1"
    +    }
    
    +++ b/src/Controller/RequestHandler.php
    @@ -286,4 +289,41 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +    if (!class_exists("\\JsonSchema\\Validator")) {
    +      return TRUE;
    +    }
    

    This is a nice trick!

  2. +++ b/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -746,4 +747,53 @@ class JsonApiFunctionalTest extends BrowserTestBase {
     
    +  public function testResponseValidation() {
    +    // Expose the protected RequestHandler::validateResponse() method.
    

    Couldn't this be a kernel test?

  3. +++ b/tests/src/Functional/JsonApiFunctionalTest.php
    @@ -746,4 +747,53 @@ class JsonApiFunctionalTest extends BrowserTestBase {
    +    if (!$validate_response->invoke(NULL, $response)) {
    +      $this->fail('Response validation flagged a valid response.');
    +    }
    ...
    +    if (!$validate_response->invoke(NULL, $response)) {
    +      $this->fail('Response validation flagged a valid empty response.');
    +    }
    

    Is there a reason we cannot use assertFalse here?

hampercm’s picture

StatusFileSize
new21.74 KB
new3.97 KB

Addressed #45:

1. :)

2. Yes, it can be a kernel test. I had originally thought to use a more complex scenario that utilized a full functional test environment, but went a different direction. Changed.

3. Changed.

Thanks!

e0ipso’s picture

I'm cool to commit this, but it seems that the patch in #46 got some extra unintended stuff attached to it,

Status: Needs review » Needs work

The last submitted patch, 46: validate_responses-2840677-46.patch, failed testing.

hampercm’s picture

Assigned: Unassigned » hampercm

The patch in #46 looks correct to me (except for the missing newline at EOF).
Ugh, guess it did get some changes from the core patch review mixed in.

Rerolling...

hampercm’s picture

Status: Needs work » Needs review
StatusFileSize
new15.86 KB

Fixed. Sorry for the lack of an interdiff.

hampercm’s picture

Assigned: hampercm » Unassigned
wim leers’s picture

I have only nits:

  1. +++ b/composer.json
    @@ -0,0 +1,9 @@
    +    "description": "Provides a JSON API format for the REST resources.",
    

    I was going to say "this description is wrong", but it's wrong in the .info.yml too. So, no fault of this patch.

  2. +++ b/composer.json
    --- /dev/null
    +++ b/schema.json
    

    Does this file have a particular version? How will we keep this up to date?

  3. +++ b/src/Controller/RequestHandler.php
    @@ -2,6 +2,7 @@
    +use Drupal\Component\Serialization\Json;
    

    Unused use.

  4. +++ b/src/Controller/RequestHandler.php
    @@ -289,4 +292,41 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +   * Only responses that are in the api_json format will be validated.
    

    This sentence is unnecessary, and misleading. This function is simply called, it has no knowledge whatsoever about which responses are going through the system. It simply validates the response it is given.

hampercm’s picture

Assigned: Unassigned » hampercm
Status: Needs review » Needs work
hampercm’s picture

Assigned: hampercm » Unassigned
Status: Needs work » Needs review
StatusFileSize
new18.12 KB
new1.39 KB

Addresses #52:
1. Yes, that description is out-of-date. Taking the liberty of making it more accurate.

2. There isn't really any version on the schema file, it's part of the JSON API standard's GitHub repo at https://raw.githubusercontent.com/json-api/json-api/gh-pages/schema . Not sure how to plan keeping it up-to-date. Any ideas?

3. This class IS used.

4. Removed. This was a holdover from when I was actually checking the format, or at least planning to.

wim leers’s picture

Status: Needs review » Reviewed & tested by the community

#54.2: let's at least document in this issue which commit (hash) it uses.
#54.3: oops, sorry!

hampercm’s picture

The schema file was last updated September 19, 2016 in commit https://github.com/json-api/json-api/commit/ff222faee41260d80cb8054e75fb...

wim leers’s picture

There we go :)

e0ipso’s picture

I'm working on resolving conflicts after #2846857: Automated tests broken got merged and some minor follow ups.

e0ipso’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new17.12 KB

No intediff due to reroll. However these are the most important changes I made:

  1. +++ b/src/Controller/RequestHandler.php
    @@ -165,6 +166,8 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +    assert('$this->validateResponse($response)', 'A JSON API response failed validation (see /admin/reports/dblog for details). Please report this in the issue queue on drupal.org');
    

    (see /admin/reports/dblog for details) to (see the logs for details)

  2. +++ b/src/Controller/RequestHandler.php
    @@ -289,4 +292,39 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter
    +    $response_data = json_decode($response->getContent());
    

    Added:
    <?
    // Do not use Json::decode here since it coerces the response into an
    // associative array, which creates validation errors.

  3. +++ b/tests/src/Kernel/Controller/RequestHandlerTest.php
    @@ -0,0 +1,76 @@
    +  public function testResponseValidation() {
    

    Added:

        // Check that the validation class is enabled.
        $this->assertTrue(
          class_exists("\\JsonSchema\\Validator"),
          'The JSON Schema validator is not present. Please make sure to install it using composer.'
        );
    
  4. +++ b/tests/src/Kernel/Controller/RequestHandlerTest.php
    @@ -0,0 +1,76 @@
    +    if ($validate_response->invoke(NULL, $response)) {
    +      $this->assertFalse(TRUE, 'Response validation failed to flag an invalid response.');
    +    }
    

    Changed to:

        $this->assertFalse(
          $validate_response->invoke(NULL, $response),
          'Response validation failed to flag an invalid response.'
        );
    

    (This applies to all the asserts in this class).

Merging if green.

  • e0ipso committed e8821d3 on 8.x-1.x authored by hampercm
    test(Controller) Validate responses against the JSON API specification...
e0ipso’s picture

Status: Needs review » Fixed
hampercm’s picture

@e0ipso ++

wim leers’s picture

Woooooot! This is a huge milestone!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.