Unverified Commit 8ce2a3ec authored by alexpott's avatar alexpott

Issue #2955383 by Wim Leers, borisson_, dawehner: List available representations in 406 responses

parent f54efcf2
......@@ -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 @@ public function filter(RouteCollection $collection, Request $request) {
// 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 @@ public function filter(RouteCollection $collection, Request $request) {
* 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 @@ protected static function getDefaultFormat(RouteCollection $collection) {
: 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));
}
}
......@@ -1492,6 +1492,8 @@ protected function assert406Response(ResponseInterface $response) {
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'));
}
}
......
......@@ -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 @@ public function filterProvider() {
* @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 @@ public function testNoRouteFound() {
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');
......
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