diff --git a/core/modules/rest/lib/Drupal/rest/RequestHandler.php b/core/modules/rest/lib/Drupal/rest/RequestHandler.php index 30156de..8a23b50 100644 --- a/core/modules/rest/lib/Drupal/rest/RequestHandler.php +++ b/core/modules/rest/lib/Drupal/rest/RequestHandler.php @@ -70,12 +70,15 @@ public function handle(Request $request, $id = NULL) { } // Invoke the operation on the resource plugin. + // All REST routes are restricted to exactly one format, so instead of + // parsing it out of the Accept headers again, we can simply retrieve the + // format requirement. If there is no format associated, just pick HAL. + $format = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getRequirement('_format') ?: 'hal_json'; try { $response = $resource->{$method}($id, $unserialized, $request); } catch (HttpException $e) { $error['error'] = $e->getMessage(); - $format = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getRequirement('_format') ?: 'drupal_jsonld'; $content = $serializer->serialize($error, $format); // Add the default content type, but only if the headers from the // exception have not specified it already. @@ -86,12 +89,6 @@ public function handle(Request $request, $id = NULL) { // Serialize the outgoing data for the response, if available. $data = $response->getResponseData(); if ($data != NULL) { - // All REST routes are restricted to exactly one format, so instead of - // parsing it out of the Accept headers again we can simply retrieve the - // format requirement. If there is no format associated just pick Drupal - // JSON-LD. - $format = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)->getRequirement('_format') ?: 'drupal_jsonld'; - $output = $serializer->serialize($data, $format); $response->setContent($output); $response->headers->set('Content-Type', $request->getMimeType($format)); diff --git a/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php index 32784a8..42c2950 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/CreateTest.php @@ -19,7 +19,7 @@ class CreateTest extends RESTTestBase { * * @var array */ - public static $modules = array('rest', 'entity_test'); + public static $modules = array('hal', 'rest', 'entity_test'); public static function getInfo() { return array( @@ -48,9 +48,9 @@ public function testCreate() { $entity_values = $this->entityValues($entity_type); $entity = entity_create($entity_type, $entity_values); - $serialized = $serializer->serialize($entity, 'drupal_jsonld'); + $serialized = $serializer->serialize($entity, $this->defaultFormat); // Create the entity over the REST API. - $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType); $this->assertResponse(201); // Get the new entity ID from the location header and try to read it from @@ -75,17 +75,17 @@ public function testCreate() { // Try to create an entity with an access protected field. // @see entity_test_entity_field_access() $entity->field_test_text->value = 'no access value'; - $serialized = $serializer->serialize($entity, 'drupal_jsonld'); - $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json'); + $serialized = $serializer->serialize($entity, $this->defaultFormat); + $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType); $this->assertResponse(403); $this->assertFalse(entity_load_multiple($entity_type, NULL, TRUE), 'No entity has been created in the database.'); // Restore the valid test value. $entity->field_test_text->value = $entity_values['field_test_text'][0]['value']; - $serialized = $serializer->serialize($entity, 'drupal_jsonld'); + $serialized = $serializer->serialize($entity, $this->defaultFormat); // Try to send invalid data that cannot be correctly deserialized. - $this->httpRequest('entity/' . $entity_type, 'POST', 'kaboom!', 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/' . $entity_type, 'POST', 'kaboom!', $this->defaultMimeType); $this->assertResponse(400); // Try to create an entity without the CSRF token. @@ -96,21 +96,21 @@ public function testCreate() { CURLOPT_POSTFIELDS => $serialized, CURLOPT_URL => url('entity/' . $entity_type, array('absolute' => TRUE)), CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => array('Content-Type: application/vnd.drupal.ld+json'), + CURLOPT_HTTPHEADER => array('Content-Type: ' . $this->defaultMimeType), )); $this->assertResponse(403); $this->assertFalse(entity_load_multiple($entity_type, NULL, TRUE), 'No entity has been created in the database.'); // Try to create an entity without proper permissions. $this->drupalLogout(); - $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/' . $entity_type, 'POST', $serialized, $this->defaultMimeType); $this->assertResponse(403); $this->assertFalse(entity_load_multiple($entity_type, NULL, TRUE), 'No entity has been created in the database.'); // Try to create a resource which is not REST API enabled. $this->enableService(FALSE); $this->drupalLogin($account); - $this->httpRequest('entity/entity_test', 'POST', $serialized, 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/entity_test', 'POST', $serialized, $this->defaultMimeType); $this->assertResponse(404); $this->assertFalse(entity_load_multiple($entity_type, NULL, TRUE), 'No entity has been created in the database.'); diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php index 9be9896..f3e0eda 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php @@ -19,7 +19,7 @@ class DBLogTest extends RESTTestBase { * * @var array */ - public static $modules = array('jsonld', 'rest', 'dblog'); + public static $modules = array('hal', 'rest', 'dblog'); public static function getInfo() { return array( @@ -50,16 +50,16 @@ public function testWatchdog() { $account = $this->drupalCreateUser(array('restful get dblog')); $this->drupalLogin($account); - $response = $this->httpRequest("dblog/$id", 'GET', NULL, 'application/vnd.drupal.ld+json'); + $response = $this->httpRequest("dblog/$id", 'GET', NULL, $this->defaultMimeType); $this->assertResponse(200); - $this->assertHeader('content-type', 'application/vnd.drupal.ld+json'); + $this->assertHeader('content-type', $this->defaultMimeType); $log = drupal_json_decode($response); $this->assertEqual($log['wid'], $id, 'Log ID is correct.'); $this->assertEqual($log['type'], 'rest_test', 'Type of log message is correct.'); $this->assertEqual($log['message'], 'Test message', 'Log message text is correct.'); // Request an unknown log entry. - $response = $this->httpRequest("dblog/9999", 'GET', NULL, 'application/vnd.drupal.ld+json'); + $response = $this->httpRequest("dblog/9999", 'GET', NULL, $this->defaultMimeType); $this->assertResponse(404); $decoded = drupal_json_decode($response); $this->assertEqual($decoded['error'], 'Log entry with ID 9999 was not found', 'Response message is correct.'); diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php index 7f72ee7..d85cf71 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php @@ -19,7 +19,7 @@ class DeleteTest extends RESTTestBase { * * @var array */ - public static $modules = array('rest', 'entity_test'); + public static $modules = array('hal', 'rest', 'entity_test'); public static function getInfo() { return array( diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php index cd9bc22..4bad46b 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php @@ -15,6 +15,26 @@ abstract class RESTTestBase extends WebTestBase { /** + * The default serialization format to use for testing REST operations. + * + * @var string + */ + protected $defaultFormat; + + /** + * The default MIME type to use for testing REST operations. + * + * @var string + */ + protected $defaultMimeType; + + protected function setUp() { + parent::setUp(); + $this->defaultFormat = 'hal_json'; + $this->defaultMimeType = 'application/hal+json'; + } + + /** * Helper function to issue a HTTP request with simpletest's cURL. * * @param string $url @@ -23,10 +43,13 @@ * HTTP method, one of GET, POST, PUT or DELETE. * @param array $body * Either the body for POST and PUT or additional URL parameters for GET. - * @param string $format + * @param string $mime_type * The MIME type of the transmitted content. */ - protected function httpRequest($url, $method, $body = NULL, $format = 'application/ld+json') { + protected function httpRequest($url, $method, $body = NULL, $mime_type = NULL) { + if (!isset($mime_type)) { + $mime_type = $this->defaultMimeType; + } if (!in_array($method, array('GET', 'HEAD', 'OPTIONS', 'TRACE'))) { // GET the CSRF token first for writing requests. $token = $this->drupalGet('rest/session/token'); @@ -39,7 +62,7 @@ protected function httpRequest($url, $method, $body = NULL, $format = 'applicati CURLOPT_HTTPGET => TRUE, CURLOPT_URL => url($url, $options), CURLOPT_NOBODY => FALSE, - CURLOPT_HTTPHEADER => array('Accept: ' . $format), + CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type), ); break; @@ -51,7 +74,7 @@ protected function httpRequest($url, $method, $body = NULL, $format = 'applicati CURLOPT_URL => url($url, array('absolute' => TRUE)), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => array( - 'Content-Type: ' . $format, + 'Content-Type: ' . $mime_type, 'X-CSRF-Token: ' . $token, ), ); @@ -65,7 +88,7 @@ protected function httpRequest($url, $method, $body = NULL, $format = 'applicati CURLOPT_URL => url($url, array('absolute' => TRUE)), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => array( - 'Content-Type: ' . $format, + 'Content-Type: ' . $mime_type, 'X-CSRF-Token: ' . $token, ), ); @@ -79,7 +102,7 @@ protected function httpRequest($url, $method, $body = NULL, $format = 'applicati CURLOPT_URL => url($url, array('absolute' => TRUE)), CURLOPT_NOBODY => FALSE, CURLOPT_HTTPHEADER => array( - 'Content-Type: ' . $format, + 'Content-Type: ' . $mime_type, 'X-CSRF-Token: ' . $token, ), ); @@ -159,7 +182,7 @@ protected function entityValues($entity_type) { * @param string $method * The HTTP method to enable, e.g. GET, POST etc. * @param string $format - * (Optional) The serialization format, e.g. jsonld. + * (Optional) The serialization format, e.g. hal_json. */ protected function enableService($resource_type, $method = 'GET', $format = NULL) { // Enable REST API for this entity type. diff --git a/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php index f3ccca7..cb7fb6e 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/ReadTest.php @@ -19,7 +19,7 @@ class ReadTest extends RESTTestBase { * * @var array */ - public static $modules = array('jsonld', 'rest', 'entity_test'); + public static $modules = array('hal', 'rest', 'entity_test'); public static function getInfo() { return array( @@ -50,20 +50,20 @@ public function testRead() { $entity = $this->entityCreate($entity_type); $entity->save(); // Read it over the REST API. - $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/vnd.drupal.ld+json'); + $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, $this->defaultMimeType); $this->assertResponse('200', 'HTTP response code is correct.'); - $this->assertHeader('content-type', 'application/vnd.drupal.ld+json'); + $this->assertHeader('content-type', $this->defaultMimeType); $data = drupal_json_decode($response); // Only assert one example property here, other properties should be // checked in serialization tests. - $this->assertEqual($data['uuid'][LANGUAGE_DEFAULT][0]['value'], $entity->uuid(), 'Entity UUID is correct'); + $this->assertEqual($data['uuid'][0]['value'], $entity->uuid(), 'Entity UUID is correct'); // Try to read the entity with an unsupported mime format. $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/wrongformat'); $this->assertResponse(415); // Try to read an entity that does not exist. - $response = $this->httpRequest('entity/' . $entity_type . '/9999', 'GET', NULL, 'application/vnd.drupal.ld+json'); + $response = $this->httpRequest('entity/' . $entity_type . '/9999', 'GET', NULL, $this->defaultMimeType); $this->assertResponse(404); $decoded = drupal_json_decode($response); $this->assertEqual($decoded['error'], 'Entity with ID 9999 not found', 'Response message is correct.'); @@ -73,22 +73,22 @@ public function testRead() { // @see entity_test_entity_field_access() $entity->field_test_text->value = 'no access value'; $entity->save(); - $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/vnd.drupal.ld+json'); + $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, $this->defaultMimeType); $this->assertResponse(200); - $this->assertHeader('content-type', 'application/vnd.drupal.ld+json'); + $this->assertHeader('content-type', $this->defaultMimeType); $data = drupal_json_decode($response); $this->assertFalse(isset($data['field_test_text']), 'Field access protexted field is not visible in the response.'); // Try to read an entity without proper permissions. $this->drupalLogout(); - $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, 'application/vnd.drupal.ld+json'); + $response = $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'GET', NULL, $this->defaultMimeType); $this->assertResponse(403); $this->assertNull(drupal_json_decode($response), 'No valid JSON found.'); } // Try to read a resource which is not REST API enabled. $account = $this->drupalCreateUser(); $this->drupalLogin($account); - $response = $this->httpRequest('entity/user/' . $account->id(), 'GET', NULL, 'application/vnd.drupal.ld+json'); + $response = $this->httpRequest('entity/user/' . $account->id(), 'GET', NULL, $this->defaultMimeType); $this->assertResponse(404); $this->assertNull(drupal_json_decode($response), 'No valid JSON found.'); } diff --git a/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php b/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php index 44ea063..25d56c9 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/UpdateTest.php @@ -19,7 +19,7 @@ class UpdateTest extends RESTTestBase { * * @var array */ - public static $modules = array('rest', 'entity_test'); + public static $modules = array('hal', 'rest', 'entity_test'); public static function getInfo() { return array( @@ -55,10 +55,10 @@ public function testPatchUpdate() { $patch_entity = entity_create($entity_type, $patch_values); // We don't want to overwrite the UUID. unset($patch_entity->uuid); - $serialized = $serializer->serialize($patch_entity, 'drupal_jsonld'); + $serialized = $serializer->serialize($patch_entity, $this->defaultFormat); // Update the entity over the REST API. - $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, $this->defaultMimeType); $this->assertResponse(204); // Re-load updated entity from the database. @@ -66,12 +66,12 @@ public function testPatchUpdate() { $this->assertEqual($entity->field_test_text->value, $patch_entity->field_test_text->value, 'Field was successfully updated.'); // Try to empty a field. - $normalized = $serializer->normalize($patch_entity, 'drupal_jsonld'); + $normalized = $serializer->normalize($patch_entity, $this->defaultFormat); $normalized['field_test_text'] = array(); - $serialized = $serializer->encode($normalized, 'jsonld'); + $serialized = $serializer->encode($normalized, $this->defaultFormat); // Update the entity over the REST API. - $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, $this->defaultMimeType); $this->assertResponse(204); // Re-load updated entity from the database. @@ -84,7 +84,7 @@ public function testPatchUpdate() { $entity->save(); // Try to empty a field that is access protected. - $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, $this->defaultMimeType); $this->assertResponse(403); // Re-load the entity from the database. @@ -92,8 +92,8 @@ public function testPatchUpdate() { $this->assertEqual($entity->field_test_text->value, 'no access value', 'Text field was not updated.'); // Try to update an access protected field. - $serialized = $serializer->serialize($patch_entity, 'drupal_jsonld'); - $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json'); + $serialized = $serializer->serialize($patch_entity, $this->defaultFormat); + $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, $this->defaultMimeType); $this->assertResponse(403); // Re-load the entity from the database. @@ -105,20 +105,20 @@ public function testPatchUpdate() { $entity->save(); // Try to update a non-existing entity with ID 9999. - $this->httpRequest('entity/' . $entity_type . '/9999', 'PATCH', $serialized, 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/' . $entity_type . '/9999', 'PATCH', $serialized, $this->defaultMimeType); $this->assertResponse(404); $loaded_entity = entity_load($entity_type, 9999, TRUE); $this->assertFalse($loaded_entity, 'Entity 9999 was not created.'); // Try to update an entity without proper permissions. $this->drupalLogout(); - $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, $this->defaultMimeType); $this->assertResponse(403); // Try to update a resource which is not REST API enabled. $this->enableService(FALSE); $this->drupalLogin($account); - $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, 'application/vnd.drupal.ld+json'); + $this->httpRequest('entity/' . $entity_type . '/' . $entity->id(), 'PATCH', $serialized, $this->defaultMimeType); $this->assertResponse(404); } } diff --git a/core/modules/rest/lib/Drupal/rest/Tests/Views/StyleSerializerTest.php b/core/modules/rest/lib/Drupal/rest/Tests/Views/StyleSerializerTest.php index 4aeaaa7..c38db82 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/Views/StyleSerializerTest.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/Views/StyleSerializerTest.php @@ -25,7 +25,7 @@ class StyleSerializerTest extends PluginTestBase { * * @var array */ - public static $modules = array('views_ui', 'entity_test', 'jsonld', 'rest_test_views'); + public static $modules = array('views_ui', 'entity_test', 'hal', 'rest_test_views'); /** * Views used by this test. @@ -127,13 +127,9 @@ public function testSerializerResponses() { $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.'); - $expected = $serializer->serialize($entities, 'jsonld'); - $actual_json = $this->drupalGet('test/serialize/entity', array(), array('Accept: application/ld+json')); - $this->assertIdentical($actual_json, $expected, 'The expected JSONLD output was found.'); - - $expected = $serializer->serialize($entities, 'drupal_jsonld'); - $actual_json = $this->drupalGet('test/serialize/entity', array(), array('Accept: application/vnd.drupal.ld+json')); - $this->assertIdentical($actual_json, $expected, 'The expected JSONLD output was found.'); + $expected = $serializer->serialize($entities, 'hal_json'); + $actual_json = $this->drupalGet('test/serialize/entity', array(), array('Accept: application/hal+json')); + $this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.'); } /** diff --git a/core/modules/rest/rest.info.yml b/core/modules/rest/rest.info.yml index e67ea21..1ac5015 100644 --- a/core/modules/rest/rest.info.yml +++ b/core/modules/rest/rest.info.yml @@ -4,7 +4,5 @@ package: Core version: VERSION core: 8.x dependencies: - # @todo Remove this dependency once hard coding to JSON-LD is gone. - - jsonld - serialization configure: admin/config/services/rest diff --git a/core/modules/rest/rest.module b/core/modules/rest/rest.module index 233db32..43b32f4 100644 --- a/core/modules/rest/rest.module +++ b/core/modules/rest/rest.module @@ -69,7 +69,7 @@ function rest_help($path, $arg) { $output .= '

' . t('Example uses') . '

'; $output .= '
'; $output .= '
' . t('An HTTP GET request can be used to get a node') . '
'; - $output .= '
curl -H "Accept: application/vnd.drupal.ld+json" --include --request GET --cookie ' . session_name() . '=' . session_id() . ' ' . url('entity/node/5', array('absolute' => TRUE)) . '
'; + $output .= '
curl -H "Accept: application/hal+json" --include --request GET --cookie ' . session_name() . '=' . session_id() . ' ' . url('entity/node/5', array('absolute' => TRUE)) . '
'; $output .= '
' . t('An HTTP DELETE request can be used to delete a node') . '
'; $output .= '
curl --include --request DELETE --cookie ' . session_name() . '=' . session_id() . ' ' . url('entity/node/5', array('absolute' => TRUE)) . '
'; $output .= '
';