Move JSON:API validation to a feature flag or separate module

Created on 4 September 2024, 2 months ago
Updated 20 September 2024, about 2 months ago

Problem/Motivation

JSON:API has a soft dependency on justinrainbow/json-schema; validation is performed automatically if the dependency is enabled via a check in \Drupal\jsonapi\EventSubscriber\ResourceResponseValidator:

use JsonSchema\Validator;
...
    elseif (class_exists(Validator::class)) {
      $this->validator = new Validator();
    }

@larowlan has previously noted that this causes severe performance issues at runtime, and the fix is to uninstall the dependency.

However, Experience Builder uses justinrainbow/json-schema at runtime (although at the time of writing this is not explicitly declared): ๐Ÿ› Declare explicit runtime dependency on `justinrainbow/json-schema` RTBC

In turn this will cause performance issues on sites that want to use both Experience Builder and JSON:API.

Steps to reproduce

Proposed resolution

Move ResourceResponseValidator behind some kind of feature flag instead of magically enabling it when the dependency is present. This could be a development mode flag similar to the Twig debug mode, or a feature flag module.

It may even be preferable to move this out of core to a contrib module, as this doesn't feel like it is necessarily in the core use case. Tagging for product manager review.

Remaining tasks

Discuss the best solution.
Implement.

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

๐Ÿ“Œ Task
Status

Active

Version

11.0 ๐Ÿ”ฅ

Component
JSON APIย  โ†’

Last updated about 16 hours ago

Created by

๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom longwave UK

Live updates comments and jobs are added and updated live.
  • Performance

    It affects performance. It is often combined with the Needs profiling tag.

Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @longwave
  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom longwave UK

    Actually given that this is purely a developer-facing feature I am not even sure this is a product manager decision, removing the tag again.

  • ๐Ÿ‡ฆ๐Ÿ‡บAustralia larowlan ๐Ÿ‡ฆ๐Ÿ‡บ๐Ÿ.au GMT+10
  • ๐Ÿ‡ง๐Ÿ‡ชBelgium wim leers Ghent ๐Ÿ‡ง๐Ÿ‡ช๐Ÿ‡ช๐Ÿ‡บ

    Quoting @larowlan:

    FYI if we make this a production requirement, we should add a hook_requirements warning people with jsonapi module and assertions enabled that their performance will go through the floor because of that module's response validator.

    I've been asked to audit sites for performance and found that combo in production ๐Ÿ˜ฑ

    โ€” #3469516-4: Declare explicit runtime dependency on justinrainbow/json-schema โ†’

    As a JSON:API maintainer: +1 for this.

  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands

    Hmm, automatic discovery is kinda unsettling indeed. I agree that needs to go. I wonder if we will make things less stable if we stop validating. Especially in tests thi has merit. Altough, if performance on this is very bad (have not seem any numbers in the parent) we might want to choose and disable in anyways.

    I do kinda agree on ussing a flag perhaps as twig, seems reasonable. Wonder though if we need to change ci then also to enable that. Something like parameters.jsonapi.validate_schema comes to mind then.

    In order to be able to move forwards relatively quicky, this could first just be a parameter defaulting to false if not passed. Then we can consider (and perhaps implement) developer mode in a follow up.

    In other news; I was also thinking if this would mean problems in โœจ Generate JSON schema for content entity types Needs review but i think that is all fine.

  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands

    Wondering, would this same issue exist for:

    Drupal\Core\Theme\Component\ComponentValidator

    That kinda does the same.

      /**
       * Sets the validator service if available.
       */
      public function setValidator(?Validator $validator = NULL): void {
        if ($validator) {
          $this->validator = $validator;
          return;
        }
        if (class_exists(Validator::class)) {
          $this->validator = new Validator();
        }
      }
    
  • Merge request !9573Resolve #3472008 "Move jsonapi validation" โ†’ (Open) created by bbrala
  • Pipeline finished with Failed
    about 2 months ago
    Total: 426s
    #290064
  • Pipeline finished with Failed
    about 2 months ago
    Total: 525s
    #290071
  • Pipeline finished with Canceled
    about 2 months ago
    Total: 552s
    #290083
  • Pipeline finished with Failed
    about 2 months ago
    Total: 406s
    #290094
  • Pipeline finished with Failed
    about 2 months ago
    Total: 695s
    #290122
  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands

    Test run results. Kinda weary to do a full conclusion since variance is so high.

    Run analytics timeing of step_script	Disable 1	Disable 2	Enable 1	Enable 2
    ๐Ÿ–ฑ๏ธ๏ธ๏ธ PHPUnit Functional Javascript 1/3	03:50		02:49		04:03		03:33
    ๐Ÿ–ฑ๏ธ๏ธ๏ธ PHPUnit Functional Javascript 2/3	03:49		03:29		04:12		03:58
    ๐Ÿ–ฑ๏ธ๏ธ๏ธ PHPUnit Functional Javascript 3/3	02:56		02:38		04:03		03:12
    ๐ŸŒ๏ธ๏ธ PHPUnit Functional 1/8		03:32		02:52		04:29		03:54
    ๐ŸŒ๏ธ๏ธ PHPUnit Functional 2/8		03:41		02:42		04:10		02:36
    ๐ŸŒ๏ธ๏ธ PHPUnit Functional 3/8		03:36		03:21		05:22		02:44
    ๐ŸŒ๏ธ๏ธ PHPUnit Functional 4/8		03:05		03:11		04:20		02:56
    ๐ŸŒ๏ธ๏ธ PHPUnit Functional 5/8		03:38		02:47		03:27		03:37
    ๐ŸŒ๏ธ๏ธ PHPUnit Functional 6/8		02:59		03:20		04:22		03:43
    ๐ŸŒ๏ธ๏ธ PHPUnit Functional 7/8		02:42		02:33		03:59		03:24
    ๐ŸŒ๏ธ๏ธ PHPUnit Functional 8/8		02:45		02:45		04:56		03:06
    Total time taken in functional		36:33		32:27		47:23		36:43
    

    So question then becomes. Do we really want this, or perhaps only enable this in testing for a specific node?

  • Pipeline finished with Failed
    about 2 months ago
    Total: 156s
    #292384
  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands

    Think this should fail since the validation is off by default now. Lets see if that is the case.

  • Pipeline finished with Failed
    about 2 months ago
    Total: 495s
    #292388
  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands

    Ok it seems we only have unittests for the code. That is fine, i did a booboo on the unit test arguments though, so lets fix that. Validation should be disbled by default now at least.

  • Pipeline finished with Failed
    about 2 months ago
    Total: 496s
    #292397
  • Pipeline finished with Failed
    about 2 months ago
    Total: 449s
    #292483
  • Pipeline finished with Failed
    about 2 months ago
    Total: 492s
    #292501
  • Pipeline finished with Success
    about 2 months ago
    Total: 464s
    #293084
  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands

    Think this is fine, first time doing a pattern like this, so not sure if this is the right way.

    Adding the extra contructor argument shoiuld be fine. We could also do this without a default value if preferred, but this way chances of breaking something seem smaller.

  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Code change looks fine but since it's adding a default service probably needs a change record. Maybe release notes? not 100% on the second.

  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands
  • Pipeline finished with Canceled
    about 1 month ago
    Total: 198s
    #300512
  • Pipeline finished with Failed
    about 1 month ago
    Total: 179s
    #300514
  • Pipeline finished with Canceled
    about 1 month ago
    Total: 243s
    #300519
  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands

    Rebased and small tweak to test method order and comment. All ready.

  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands
  • Pipeline finished with Success
    about 1 month ago
    Total: 1085s
    #300528
  • ๐Ÿ‡บ๐Ÿ‡ธUnited States smustgrave

    Thanks!

    CR reads well to me, clearly defines the need and code example makes it look clean.

    Ran test-only feature

    1) Drupal\Tests\jsonapi\Unit\EventSubscriber\ResourceResponseValidatorTest::testValidateResponse with data set #5 (Symfony\Component\HttpFoundation\Request Object (...), Drupal\rest\ResourceResponse Object (...), true, 'Response validation disabled ...valid.', false)
    Response validation disabled doesn't flags empty array as invalid.
    Failed asserting that false is identical to true.
    /builds/issue/drupal-3472008/core/modules/jsonapi/tests/src/Unit/EventSubscriber/ResourceResponseValidatorTest.php:81
    FAILURES!
    Tests: 6, Assertions: 6, Failures: 1.
    Exiting with EXIT_CODE=1
    

    Which shows coverage.

    Rest of the code looks good and believe this is good to go.

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom longwave UK

    Added a question about the new container parameter.

  • ๐Ÿ‡ณ๐Ÿ‡ฑNetherlands bbrala Netherlands

    Fair point. Will do

  • ๐Ÿ‡ฌ๐Ÿ‡งUnited Kingdom longwave UK

    I'm also assuming that this can then be overridden still in a services.local.yml, but haven't checked - we should update the change record to tell people how to reenable the feature.

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia sumit_saini

    sumit saini โ†’ made their first commit to this issueโ€™s fork.

  • ๐Ÿ‡ฎ๐Ÿ‡ณIndia sumit_saini

    Added changes as per #17. Also, fixed flag name mismatch in files.
    For #19, I have tested and confirm that it is possible to override this container parameter in services.local.yml with these changes.

Production build 0.71.5 2024