Commit e0784b1d authored by webchick's avatar webchick
Browse files

Issue #1935538 by linclark, effulgentsia: Switch REST to default to HAL.

parent f277d1dc
......@@ -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));
......
......@@ -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.');
......
......@@ -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.');
......
......@@ -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(
......
......@@ -14,6 +14,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.
*
......@@ -23,10 +43,13 @@ abstract class RESTTestBase extends WebTestBase {
* 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.
......
......@@ -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.');
}
......
......@@ -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);
}
}
......@@ -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.');
}
/**
......
......@@ -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
......@@ -69,7 +69,7 @@ function rest_help($path, $arg) {
$output .= '<h3>' . t('Example uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('An HTTP GET request can be used to get a node') . '</dt>';
$output .= '<dd><code>curl -H "Accept: application/vnd.drupal.ld+json" --include --request GET --cookie ' . session_name() . '=' . session_id() . ' ' . url('entity/node/5', array('absolute' => TRUE)) . '</code></dd>';
$output .= '<dd><code>curl -H "Accept: application/hal+json" --include --request GET --cookie ' . session_name() . '=' . session_id() . ' ' . url('entity/node/5', array('absolute' => TRUE)) . '</code></dd>';
$output .= '<dt>' . t('An HTTP DELETE request can be used to delete a node') . '</dt>';
$output .= '<dd><code>curl --include --request DELETE --cookie ' . session_name() . '=' . session_id() . ' ' . url('entity/node/5', array('absolute' => TRUE)) . '</code></dd>';
$output .= '</dl>';
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment