.../Core/EventSubscriber/ExceptionJsonSubscriber.php | 11 +++++++++++ core/modules/hal/src/Encoder/JsonEncoder.php | 20 +++----------------- .../EntityResource/EntityResourceTestBase.php | 18 +++--------------- .../rest/tests/src/Functional/ResourceTestBase.php | 4 +--- .../ResourceResponseSubscriberTest.php | 2 +- .../serialization/src/Encoder/JsonEncoder.php | 14 ++++++++++++++ 6 files changed, 33 insertions(+), 36 deletions(-) diff --git a/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php index 2853b98..f9688e9 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php @@ -93,4 +93,15 @@ public function on415(GetResponseForExceptionEvent $event) { $event->setResponse($response); } + /** + * Handles a 422 error for JSON. + * + * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event + * The event to process. + */ + public function on422(GetResponseForExceptionEvent $event) { + $response = new JsonResponse(['message' => $event->getException()->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY); + $event->setResponse($response); + } + } diff --git a/core/modules/hal/src/Encoder/JsonEncoder.php b/core/modules/hal/src/Encoder/JsonEncoder.php index b722666..bafc487 100644 --- a/core/modules/hal/src/Encoder/JsonEncoder.php +++ b/core/modules/hal/src/Encoder/JsonEncoder.php @@ -2,34 +2,20 @@ namespace Drupal\hal\Encoder; -use Symfony\Component\Serializer\Encoder\JsonEncoder as SymfonyJsonEncoder; +use Drupal\serialization\Encoder\JsonEncoder as SerializationJsonEncoder; /** * Encodes HAL data in JSON. * * Simply respond to hal_json format requests using the JSON encoder. */ -class JsonEncoder extends SymfonyJsonEncoder { +class JsonEncoder extends SerializationJsonEncoder { /** * The formats that this Encoder supports. * * @var string */ - protected $format = 'hal_json'; - - /** - * {@inheritdoc} - */ - public function supportsEncoding($format) { - return $format == $this->format; - } - - /** - * {@inheritdoc} - */ - public function supportsDecoding($format) { - return $format == $this->format; - } + protected static $format = ['hal_json']; } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index 973f5b9..48ed438 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -565,11 +565,7 @@ public function testPost() { $response = $this->request('POST', $url, $request_options); $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; $label_field_capitalized = ucfirst($label_field); - // @todo Uncomment, remove next 3 in https://www.drupal.org/node/2813755. - // $this->assertErrorResponse(422, "Unprocessable Entity: validation failed.\ntitle: Title: this field cannot hold more than 1 values.\n", $response); - $this->assertSame(422, $response->getStatusCode()); - $this->assertSame([static::$mimeType], $response->getHeader('Content-Type')); - $this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n"], static::$format), (string) $response->getBody()); + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2; @@ -577,11 +573,7 @@ public function testPost() { // DX: 422 when invalid entity: UUID field too long. $response = $this->request('POST', $url, $request_options); - // @todo Uncomment, remove next 3 in https://www.drupal.org/node/2813755. - // $this->assertErrorResponse(422, "Unprocessable Entity: validation failed.\nuuid.0.value: UUID: may not be longer than 128 characters.\n", $response); - $this->assertSame(422, $response->getStatusCode()); - $this->assertSame([static::$mimeType], $response->getHeader('Content-Type')); - $this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\nuuid.0.value: UUID: may not be longer than 128 characters.\n"], static::$format), (string) $response->getBody()); + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\nuuid.0.value: UUID: may not be longer than 128 characters.\n", $response); $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_3; @@ -765,11 +757,7 @@ public function testPatch() { $response = $this->request('PATCH', $url, $request_options); $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName; $label_field_capitalized = ucfirst($label_field); - // @todo Uncomment, remove next 3 in https://www.drupal.org/node/2813755. - // $this->assertErrorResponse(422, "Unprocessable Entity: validation failed.\ntitle: Title: this field cannot hold more than 1 values.\n", $response); - // $this->assertSame(422, $response->getStatusCode()); - // $this->assertSame([static::$mimeType], $response->getHeader('Content-Type')); - $this->assertSame($this->serializer->encode(['message' => "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n"], static::$format), (string) $response->getBody()); + $this->assertResourceErrorResponse(422, "Unprocessable Entity: validation failed.\n$label_field: $label_field_capitalized: this field cannot hold more than 1 values.\n", $response); $request_options[RequestOptions::BODY] = $parseable_invalid_request_body_2; diff --git a/core/modules/rest/tests/src/Functional/ResourceTestBase.php b/core/modules/rest/tests/src/Functional/ResourceTestBase.php index f1f0458..49ef8e5 100644 --- a/core/modules/rest/tests/src/Functional/ResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/ResourceTestBase.php @@ -340,9 +340,7 @@ protected function assertResourceResponse($expected_status_code, $expected_body, * The error response to assert. */ protected function assertResourceErrorResponse($expected_status_code, $expected_message, ResponseInterface $response) { - // @todo Fix this in https://www.drupal.org/node/2813755. - $encode_options = ['json_encode_options' => JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT]; - $expected_body = $this->serializer->encode(['message' => $expected_message], static::$format, $encode_options); + $expected_body = $this->serializer->encode(['message' => $expected_message], static::$format); $this->assertResourceResponse($expected_status_code, $expected_body, $response); } diff --git a/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php b/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php index 87c9059..89770fb 100644 --- a/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php +++ b/core/modules/rest/tests/src/Unit/EventSubscriber/ResourceResponseSubscriberTest.php @@ -48,7 +48,7 @@ public function testSerialization($data, $expected_response = FALSE) { $resource_response_subscriber->onResponse($event); // Content is a serialized version of the data we provided. - $this->assertEquals($expected_response !== FALSE ? $expected_response : json_encode($data), $event->getResponse()->getContent()); + $this->assertEquals($expected_response !== FALSE ? $expected_response : json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT), $event->getResponse()->getContent()); } public function providerTestSerialization() { diff --git a/core/modules/serialization/src/Encoder/JsonEncoder.php b/core/modules/serialization/src/Encoder/JsonEncoder.php index 63e1ddb..18c6066 100644 --- a/core/modules/serialization/src/Encoder/JsonEncoder.php +++ b/core/modules/serialization/src/Encoder/JsonEncoder.php @@ -4,6 +4,8 @@ use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; +use Symfony\Component\Serializer\Encoder\JsonDecode; +use Symfony\Component\Serializer\Encoder\JsonEncode; use Symfony\Component\Serializer\Encoder\JsonEncoder as BaseJsonEncoder; /** @@ -21,6 +23,18 @@ class JsonEncoder extends BaseJsonEncoder implements EncoderInterface, DecoderIn /** * {@inheritdoc} */ + public function __construct(JsonEncode $encodingImpl = null, JsonDecode $decodingImpl = null) { + // Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be + // embedded into HTML. + // @see \Symfony\Component\HttpFoundation\JsonResponse + $json_encoding_options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT; + $this->encodingImpl = $encodingImpl ?: new JsonEncode($json_encoding_options); + $this->decodingImpl = $decodingImpl ?: new JsonDecode(true); + } + + /** + * {@inheritdoc} + */ public function supportsEncoding($format) { return in_array($format, static::$format); }