logger = $logger; $this->moduleHandler = $module_handler; $this->appRoot = $app_root; } /** * {@inheritdoc} */ public static function getSubscribedEvents(): array { $events[KernelEvents::RESPONSE][] = ['onResponse']; return $events; } /** * Sets the validator service if available. */ public function setValidator(?Validator $validator = NULL) { if ($validator) { $this->validator = $validator; } elseif (class_exists(Validator::class)) { $this->validator = new Validator(); } } /** * Validates JSON:API responses. * * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event * The event to process. */ public function onResponse(ResponseEvent $event) { $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) { // If the validator isn't set, then the validation library is not installed. if (!$this->validator) { return TRUE; } // 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 string $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($schema, $response_data) { $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; } }