Commit 235bc9fb authored by Mateu Aguiló Bosch's avatar Mateu Aguiló Bosch

feat(Merger): Allow JSON output

parent 9b599ae5
......@@ -41,25 +41,19 @@ class BlueprintManager {
/**
* @param \Symfony\Component\HttpFoundation\Response[] $responses
* The responses to combine.
* @param string $format
* The format to combine the responses on. Default is multipart/related.
*
* @return \Symfony\Component\HttpFoundation\Response
* The combined response with a 207.
*/
public function combineResponses(array $responses) {
$delimiter = md5(microtime());
// Prepare the root content type header.
$content_type = sprintf(
'multipart/related; boundary="%s", type=%s',
$delimiter,
$this->negotiateSubContentType($responses)
);
$headers = ['Content-Type' => $content_type];
$context = ['delimiter' => $delimiter];
public function combineResponses(array $responses, $format) {
$context = [
'sub-content-type' => $this->negotiateSubContentType($responses),
];
// Set the content.
$content = $this->serializer->normalize($responses, 'multipart-related', $context);
$response = CacheableResponse::create($content, 207, $headers);
$normalized = $this->serializer->normalize($responses, $format, $context);
$response = CacheableResponse::create($normalized['content'], 207, $normalized['headers']);
// Set the cacheability metadata.
$cacheable_responses = array_filter($responses, function ($response) {
return $response instanceof CacheableResponseInterface;
......
......@@ -57,7 +57,14 @@ class FrontController extends ControllerBase {
}
$tree = $this->blueprintManager->parse($data, $request);
$responses = $this->subrequestsManager->request($tree);
return $this->blueprintManager->combineResponses($responses);
$master_request = $tree->getMasterRequest();
$output_format = $master_request->getRequestFormat();
if ($output_format === 'html') {
// Change the default format from html to multipart-related.
$output_format = 'multipart-related';
}
$master_request->getMimeType($output_format);
return $this->blueprintManager->combineResponses($responses, $output_format);
}
}
<?php
namespace Drupal\subrequests\Normalizer;
use Drupal\Component\Serialization\Json;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* Normalizes multiple response objects into a single string.
*/
class MultiresponseJsonNormalizer implements NormalizerInterface {
/**
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
// Prepare the root content type header.
$content_type = sprintf(
'application/json; type=%s',
$context['sub-content-type']
);
$headers = ['Content-Type' => $content_type];
// Join the content responses as a JSON object with the separator.
$output = array_reduce((array) $object, function ($carry, Response $part_response) {
$part_response->headers->set('Status', $part_response->getStatusCode());
$content_id = $part_response->headers->get('Content-ID');
$content_id = substr($content_id, 1, strlen($content_id) - 2);
$carry[$content_id] = [
'headers' => $part_response->headers->all(),
'body' => $part_response->getContent(),
];
return $carry;
}, []);
$content = Json::encode($output);
return [
'content' => $content,
'headers' => $headers,
];
}
/**
* {@inheritdoc}
*/
public function supportsNormalization($data, $format = NULL) {
if ($format !== 'json') {
return FALSE;
}
if (!is_array($data)) {
return FALSE;
}
$responses = array_filter($data, function ($response) {
return $response instanceof Response;
});
if (count($responses) !== count($data)) {
return FALSE;
}
return TRUE;
}
}
......@@ -14,7 +14,16 @@ class MultiresponseNormalizer implements NormalizerInterface {
* {@inheritdoc}
*/
public function normalize($object, $format = NULL, array $context = []) {
$delimiter = $context['delimiter'];
$delimiter = md5(microtime());
// Prepare the root content type header.
$content_type = sprintf(
'multipart/related; boundary="%s", type=%s',
$delimiter,
$context['sub-content-type']
);
$headers = ['Content-Type' => $content_type];
$separator = sprintf("\r\n--%s\r\n", $delimiter);
// Join the content responses with the separator.
$content_items = array_map(function (Response $part_response) {
......@@ -25,7 +34,11 @@ class MultiresponseNormalizer implements NormalizerInterface {
$part_response->getContent()
);
}, (array) $object);
return sprintf("--%s\r\n", $delimiter) . implode($separator, $content_items) . sprintf("\r\n--%s--", $delimiter);
$content = sprintf("--%s\r\n", $delimiter) . implode($separator, $content_items) . sprintf("\r\n--%s--", $delimiter);
return [
'content' => $content,
'headers' => $headers,
];
}
/**
......
......@@ -23,3 +23,7 @@ services:
class: Drupal\subrequests\Normalizer\MultiresponseNormalizer
tags:
- { name: normalizer, priority: 0 }
subrequests.normalizer.multiresponse_json:
class: Drupal\subrequests\Normalizer\MultiresponseJsonNormalizer
tags:
- { name: normalizer, priority: 0 }
......@@ -35,7 +35,7 @@ class BlueprintManagerTest extends UnitTestCase {
$denormalizer->setSerializer(Argument::any())->willReturn(NULL);
$normalizer = $this->prophesize(MultiresponseNormalizer::class);
$normalizer->normalize(Argument::type('array'), 'multipart-related', Argument::type('array'))
->willReturn('Booh!');
->willReturn(['content' => 'Booh!', 'headers' => ['head' => 'Ha!']]);
$normalizer->supportsNormalization(Argument::type('array'), 'multipart-related')
->willReturn([])->willReturn(TRUE);
$serializer = new Serializer(
......@@ -62,11 +62,9 @@ class BlueprintManagerTest extends UnitTestCase {
Response::create('foo', 200, ['lorem' => 'ipsum', 'Content-Type' => 'sparrow']),
Response::create('bar', 201, ['dolor' => 'sid', 'Content-Type' => 'sparrow']),
];
$combined = $this->sut->combineResponses($responses);
$combined = $this->sut->combineResponses($responses, 'multipart-related');
$this->assertInstanceOf(CacheableResponse::class, $combined);
$content_type = $combined->headers->get('Content-Type');
$this->assertStringStartsWith('multipart/related; boundary="', $content_type);
$this->assertStringEndsWith('", type=sparrow', $content_type);
$this->assertSame('Ha!', $combined->headers->get('head'));
$this->assertSame('Booh!', $combined->getContent());
}
......
......@@ -2,6 +2,7 @@
namespace Drupal\Tests\subrequests\Normalizer;
use Drupal\Component\Serialization\Json;
use Drupal\subrequests\Normalizer\JsonSubrequestDenormalizer;
use Drupal\subrequests\Subrequest;
use Drupal\Tests\UnitTestCase;
......@@ -42,7 +43,7 @@ class JsonSubrequestDenormalizerTest extends UnitTestCase {
$request->setSession(new Session());
$actual = $this->sut->denormalize($data, $class, NULL, ['master_request' => $request]);
$this->assertSame('POST', $actual->getMethod());
$this->assertEquals(['bar' => 'foo'], $actual->getContent());
$this->assertEquals(['bar' => 'foo'], Json::decode($actual->getContent()));
$this->assertSame('<oof>', $actual->headers->get('Content-ID'));
$this->assertSame('lorem', $actual->headers->get('PHP_AUTH_USER'));
$this->assertSame('ipsum', $actual->headers->get('PHP_AUTH_PW'));
......
<?php
namespace Drupal\Tests\subrequests\Normalizer;
use Drupal\Component\Serialization\Json;
use Drupal\subrequests\Normalizer\MultiresponseJsonNormalizer;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Response;
/**
* @coversDefaultClass \Drupal\subrequests\Normalizer\MultiresponseJsonNormalizer
* @group subrequests
*/
class MultiresponseJsonNormalizerTest extends UnitTestCase {
/**
* @var \Drupal\subrequests\Normalizer\MultiresponseJsonNormalizer
*/
protected $sut;
protected function setUp() {
parent::setUp();
$this->sut = new MultiresponseJsonNormalizer();
}
/**
* @dataProvider dataProviderSupportsNormalization
* @covers ::supportsNormalization
*/
public function testSupportsNormalization($data, $format, $is_supported) {
$actual = $this->sut->supportsNormalization($data, $format);
$this->assertSame($is_supported, $actual);
}
public function dataProviderSupportsNormalization() {
return [
[[Response::create('')], 'json', TRUE],
[[], 'json', TRUE],
[[Response::create('')], 'fail', FALSE],
[NULL, 'json', FALSE],
[[Response::create(''), NULL], 'json', FALSE],
];
}
/**
* @covers ::normalize
*/
public function testNormalize() {
$sub_content_type = $this->getRandomGenerator()->string();
$data = [
Response::create('Foo!', 200, ['Content-ID' => '<f>']),
Response::create('Bar', 200, ['Content-ID' => '<b>']),
];
$actual = $this->sut->normalize($data, NULL, ['sub-content-type' => $sub_content_type]);
$this->assertSame(
'application/json; type=' . $sub_content_type,
$actual['headers']['Content-Type']
);
$parsed = Json::decode($actual['content']);
$this->assertSame('Foo!', $parsed['f']['body']);
$this->assertSame('Bar', $parsed['b']['body']);
}
}
......@@ -45,13 +45,17 @@ class MultiresponseNormalizerTest extends UnitTestCase {
* @covers ::normalize
*/
public function testNormalize() {
$delimiter = $this->getRandomGenerator()->string();
$sub_content_type = $this->getRandomGenerator()->string();
$data = [Response::create('Foo!'), Response::create('Bar')];
$actual = $this->sut->normalize($data, NULL, ['delimiter' => $delimiter]);
$this->assertStringStartsWith('--' . $delimiter, $actual);
$this->assertStringEndsWith('--' . $delimiter . '--', $actual);
$this->assertRegExp("/\r\nFoo!\r\n/", $actual);
$this->assertRegExp("/\r\nBar\r\n/", $actual);
$actual = $this->sut->normalize($data, NULL, ['sub-content-type' => $sub_content_type]);
$parts = explode(', ', $actual['headers']['Content-Type']);
$parts = explode('; ', $parts[0]);
parse_str($parts[1], $parts);
$delimiter = substr($parts['boundary'], 1, strlen($parts['boundary']) - 2);
$this->assertStringStartsWith('--' . $delimiter, $actual['content']);
$this->assertStringEndsWith('--' . $delimiter . '--', $actual['content']);
$this->assertRegExp("/\r\nFoo!\r\n/", $actual['content']);
$this->assertRegExp("/\r\nBar\r\n/", $actual['content']);
}
}
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