summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2018-06-02 15:40:15 (GMT)
committerAlex Pott2018-06-02 15:40:15 (GMT)
commit8ce2a3ec3a67a6b8a9cc5b74e1ec47da484ccf3a (patch)
tree868566bd19cddc96bc7078e0919490ec7314fbb8
parentf54efcf21dcff92a3bfbb9a85b800cb4c10bb3cf (diff)
Issue #2955383 by Wim Leers, borisson_, dawehner: List available representations in 406 responses
-rw-r--r--core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php40
-rw-r--r--core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php2
-rw-r--r--core/tests/Drupal/Tests/Core/Routing/RequestFormatRouteFilterTest.php21
3 files changed, 55 insertions, 8 deletions
diff --git a/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php b/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php
index 8d14dd5..fad7bc0 100644
--- a/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php
+++ b/core/lib/Drupal/Core/Routing/RequestFormatRouteFilter.php
@@ -2,6 +2,7 @@
namespace Drupal\Core\Routing;
+use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\Routing\Route;
@@ -60,7 +61,18 @@ class RequestFormatRouteFilter implements FilterInterface {
// We do not throw a
// \Symfony\Component\Routing\Exception\ResourceNotFoundException here
// because we don't want to return a 404 status code, but rather a 406.
- throw new NotAcceptableHttpException("No route found for the specified format $format.");
+ $available_formats = static::getAvailableFormats($collection);
+ $not_acceptable = new NotAcceptableHttpException("No route found for the specified format $format. Supported formats: " . implode(', ', $available_formats) . '.');
+ if ($available_formats) {
+ $links = [];
+ foreach ($available_formats as $available_format) {
+ $url = Url::fromUri($request->getUri(), ['query' => ['_format' => $available_format]])->toString(TRUE)->getGeneratedUrl();
+ $content_type = $request->getMimeType($available_format);
+ $links[] = "<$url>; rel=\"alternate\"; type=\"$content_type\"";
+ }
+ $not_acceptable->setHeaders(['Link' => implode(', ', $links)]);
+ }
+ throw $not_acceptable;
}
/**
@@ -79,7 +91,24 @@ class RequestFormatRouteFilter implements FilterInterface {
* The default format.
*/
protected static function getDefaultFormat(RouteCollection $collection) {
- // Get the set of formats across all routes in the collection.
+ $formats = static::getAvailableFormats($collection);
+
+ // The default format is 'html' unless ALL routes require the same format.
+ return count($formats) === 1
+ ? reset($formats)
+ : 'html';
+ }
+
+ /**
+ * Gets the set of formats across all routes in the collection.
+ *
+ * @param \Symfony\Component\Routing\RouteCollection $collection
+ * The route collection to filter.
+ *
+ * @return string[]
+ * All available formats.
+ */
+ protected static function getAvailableFormats(RouteCollection $collection) {
$all_formats = array_reduce($collection->all(), function (array $carry, Route $route) {
// Routes without a '_format' requirement are assumed to require HTML.
$route_formats = !$route->hasRequirement('_format')
@@ -87,12 +116,7 @@ class RequestFormatRouteFilter implements FilterInterface {
: explode('|', $route->getRequirement('_format'));
return array_merge($carry, $route_formats);
}, []);
- $formats = array_unique(array_filter($all_formats));
-
- // The default format is 'html' unless ALL routes require the same format.
- return count($formats) === 1
- ? reset($formats)
- : 'html';
+ return array_unique(array_filter($all_formats));
}
}
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
index 76f0139..6f84d7e 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
@@ -1492,6 +1492,8 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
else {
// This is the desired response.
$this->assertSame(406, $response->getStatusCode());
+ $this->stringContains('?_format=' . static::$format . '>; rel="alternate"; type="' . static::$mimeType . '"', $response->getHeader('Link'));
+ $this->stringContains('?_format=foobar>; rel="alternate"', $response->getHeader('Link'));
}
}
diff --git a/core/tests/Drupal/Tests/Core/Routing/RequestFormatRouteFilterTest.php b/core/tests/Drupal/Tests/Core/Routing/RequestFormatRouteFilterTest.php
index 3574ee1..83d0505 100644
--- a/core/tests/Drupal/Tests/Core/Routing/RequestFormatRouteFilterTest.php
+++ b/core/tests/Drupal/Tests/Core/Routing/RequestFormatRouteFilterTest.php
@@ -2,7 +2,10 @@
namespace Drupal\Tests\Core\Routing;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\GeneratedUrl;
use Drupal\Core\Routing\RequestFormatRouteFilter;
+use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
@@ -59,6 +62,14 @@ class RequestFormatRouteFilterTest extends UnitTestCase {
* @covers ::filter
*/
public function testNoRouteFound() {
+ $url = $this->prophesize(GeneratedUrl::class);
+ $url_assembler = $this->prophesize(UnroutedUrlAssemblerInterface::class);
+ $url_assembler->assemble('http://localhost/test?_format=xml', ['query' => ['_format' => 'json'], 'external' => TRUE], TRUE)
+ ->willReturn($url);
+ $container = new ContainerBuilder();
+ $container->set('unrouted_url_assembler', $url_assembler->reveal());
+ \Drupal::setContainer($container);
+
$collection = new RouteCollection();
$route_with_format = $route = new Route('/test');
$route_with_format->setRequirement('_format', 'json');
@@ -78,6 +89,16 @@ class RequestFormatRouteFilterTest extends UnitTestCase {
public function testNoRouteFoundWhenNoRequestFormatAndSingleRouteWithMultipleFormats() {
$this->setExpectedException(NotAcceptableHttpException::class, 'No route found for the specified format html.');
+ $url = $this->prophesize(GeneratedUrl::class);
+ $url_assembler = $this->prophesize(UnroutedUrlAssemblerInterface::class);
+ $url_assembler->assemble('http://localhost/test', ['query' => ['_format' => 'json'], 'external' => TRUE], TRUE)
+ ->willReturn($url);
+ $url_assembler->assemble('http://localhost/test', ['query' => ['_format' => 'xml'], 'external' => TRUE], TRUE)
+ ->willReturn($url);
+ $container = new ContainerBuilder();
+ $container->set('unrouted_url_assembler', $url_assembler->reveal());
+ \Drupal::setContainer($container);
+
$collection = new RouteCollection();
$route_with_format = $route = new Route('/test');
$route_with_format->setRequirement('_format', 'json|xml');