class ResourceResponseValidator

Same name in this branch
  1. 11.x core/modules/jsonapi/src/EventSubscriber/ResourceResponseValidator.php \Drupal\jsonapi\EventSubscriber\ResourceResponseValidator
Same name and namespace in other branches
  1. 10 core/modules/jsonapi/src/EventSubscriber/ResourceResponseValidator.php \Drupal\jsonapi\EventSubscriber\ResourceResponseValidator
  2. 9 core/modules/jsonapi/src/EventSubscriber/ResourceResponseValidator.php \Drupal\jsonapi\EventSubscriber\ResourceResponseValidator
  3. 8.9.x core/modules/jsonapi/src/EventSubscriber/ResourceResponseValidator.php \Drupal\jsonapi\EventSubscriber\ResourceResponseValidator

Response subscriber that validates a JSON:API response.

This must run after ResourceResponseSubscriber.

@internal JSON:API maintains no PHP API. The API is the HTTP API. This class may change at any time and could break any dependencies on it.

Hierarchy

  • class \Drupal\jsonapi_response_validator\EventSubscriber\ResourceResponseValidator implements \Symfony\Component\EventDispatcher\EventSubscriberInterface

Expanded class hierarchy of ResourceResponseValidator

See also

https://www.drupal.org/project/drupal/issues/3032787

jsonapi.api.php

\Drupal\rest\EventSubscriber\ResourceResponseSubscriber

1 file declares its use of ResourceResponseValidator
ResourceResponseValidatorTest.php in core/modules/jsonapi/tests/modules/jsonapi_response_validator/tests/src/Unit/EventSubscriber/ResourceResponseValidatorTest.php

File

core/modules/jsonapi/tests/modules/jsonapi_response_validator/src/EventSubscriber/ResourceResponseValidator.php, line 30

Namespace

Drupal\jsonapi_response_validator\EventSubscriber
View source
class ResourceResponseValidator implements EventSubscriberInterface {
  
  /**
   * The schema validator.
   *
   * @var \JsonSchema\Validator
   */
  protected Validator $validator;
  
  /**
   * Constructs a ResourceResponseValidator object.
   *
   * @param \Psr\Log\LoggerInterface $logger
   *   The JSON:API logger channel.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler
   *   The module handler.
   * @param string $appRoot
   *   The application's root file path.
   */
  public function __construct(protected LoggerInterface $logger, protected ModuleHandlerInterface $moduleHandler, protected string $appRoot) {
    $this->validator = new Validator();
  }
  
  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() : array {
    $events[KernelEvents::RESPONSE][] = [
      'onResponse',
    ];
    return $events;
  }
  
  /**
   * Validates JSON:API responses.
   *
   * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
   *   The event to process.
   */
  public function onResponse(ResponseEvent $event) : void {
    $response = $event->getResponse();
    if (!str_contains($response->headers
      ->get('Content-Type', ''), 'application/vnd.api+json')) {
      return;
    }
    // Wraps validation in an assert to prevent execution in production.
    assert($this->validateResponse($response, $event->getRequest()), 'A JSON:API response failed validation (see the logs for details). Report this in the Drupal issue queue at https://www.drupal.org/project/issues/drupal');
  }
  
  /**
   * Validates a response against the JSON:API specification.
   *
   * @param \Symfony\Component\HttpFoundation\Response $response
   *   The response to validate.
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request containing info about what to validate.
   *
   * @return bool
   *   FALSE if the response failed validation, otherwise TRUE.
   */
  protected function validateResponse(Response $response, Request $request) : bool {
    // Do not use Json::decode here since it coerces the response into an
    // associative array, which creates validation errors.
    $response_data = json_decode($response->getContent());
    if (empty($response_data)) {
      return TRUE;
    }
    $schema_ref = sprintf('file://%s/schema.json', implode('/', [
      $this->appRoot,
      $this->moduleHandler
        ->getModule('jsonapi')
        ->getPath(),
    ]));
    $generic_jsonapi_schema = (object) [
      '$ref' => $schema_ref,
    ];
    return $this->validateSchema($generic_jsonapi_schema, $response_data);
  }
  
  /**
   * Validates a string against a JSON Schema. It logs any possible errors.
   *
   * @param object $schema
   *   The JSON Schema object.
   * @param mixed $response_data
   *   The JSON string to validate.
   *
   * @return bool
   *   TRUE if the string is a valid instance of the schema. FALSE otherwise.
   */
  protected function validateSchema(object $schema, mixed $response_data) : bool {
    // @phpstan-ignore method.deprecated
    $this->validator
      ->check($response_data, $schema);
    $is_valid = $this->validator
      ->isValid();
    if (!$is_valid) {
      $this->logger
        ->debug("Response failed validation.\nResponse:\n@data\n\nErrors:\n@errors", [
        '@data' => Json::encode($response_data),
        '@errors' => Json::encode($this->validator
          ->getErrors()),
      ]);
    }
    return $is_valid;
  }

}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.