Skip to content

Commit bfdb491

Browse files
committed
Merge remote-tracking branch 'origin/ACP2E-4115' into PR_2025_08_25_muntianu
2 parents 200167b + 7d121f8 commit bfdb491

File tree

9 files changed

+270
-11
lines changed

9 files changed

+270
-11
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\QuoteGraphQl\Model;
9+
10+
use Magento\Framework\GraphQl\Query\QueryResponseFormatterInterface;
11+
12+
class OrderResponseFormatter implements QueryResponseFormatterInterface
13+
{
14+
private const NODE_IDENTIFIER = 'placeOrder';
15+
16+
/**
17+
* @inheritDoc
18+
*/
19+
public function formatResponse(array $executionResult): array
20+
{
21+
if (!$this->isApplicable($executionResult)) {
22+
return $executionResult;
23+
}
24+
25+
$dataErrors = $this->getErrors($executionResult);
26+
if (empty($dataErrors)) {
27+
return $executionResult;
28+
}
29+
30+
$response = $executionResult['data'][self::NODE_IDENTIFIER] ?: [];
31+
$response['errors'] = $dataErrors;
32+
$executionResult['data'][self::NODE_IDENTIFIER] = $response;
33+
34+
return $executionResult;
35+
}
36+
37+
/**
38+
* Check if the formatter is applicable for the given execution result
39+
*
40+
* @param array $executionResult
41+
* @return bool
42+
*/
43+
private function isApplicable(array $executionResult): bool
44+
{
45+
return isset($executionResult['data']) && key_exists(self::NODE_IDENTIFIER, $executionResult['data']);
46+
}
47+
48+
/**
49+
* Extract errors from the execution result
50+
*
51+
* @param array $executionResult
52+
* @return array
53+
*/
54+
private function getErrors(array $executionResult): array
55+
{
56+
$dataErrors = [];
57+
if (!empty($executionResult['errors'])) {
58+
foreach ($executionResult['errors'] as $error) {
59+
if (isset($error['extensions']['error_code'])) {
60+
$dataErrors[] = [
61+
'message' => $error['message'],
62+
'code' => $error['extensions']['error_code']
63+
];
64+
}
65+
}
66+
}
67+
return $dataErrors;
68+
}
69+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\QuoteGraphQl\Test\Unit\Model;
9+
10+
use Magento\QuoteGraphQl\Model\OrderResponseFormatter;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class OrderResponseFormatterTest extends TestCase
14+
{
15+
/**
16+
* @return void
17+
*/
18+
public function testNotApplicable(): void
19+
{
20+
$formatter = new OrderResponseFormatter();
21+
$executionResult = ['data' => []];
22+
23+
$result = $formatter->formatResponse($executionResult);
24+
25+
$this->assertEquals($executionResult, $result);
26+
}
27+
28+
/**
29+
* @return void
30+
*/
31+
public function testFormattingIsApplied(): void
32+
{
33+
$formatter = new OrderResponseFormatter();
34+
$executionResult = [
35+
'errors' => [
36+
[
37+
'message' => 'An error occurred',
38+
'extensions' => [
39+
'error_code' => 'some_error_code',
40+
],
41+
],
42+
],
43+
'data' => [
44+
'placeOrder' => null,
45+
],
46+
];
47+
48+
$result = $formatter->formatResponse($executionResult);
49+
50+
$this->assertArrayHasKey('data', $result);
51+
$this->assertArrayHasKey('placeOrder', $result['data']);
52+
$this->assertArrayHasKey('errors', $result['data']['placeOrder']);
53+
$this->assertCount(1, $result['data']['placeOrder']['errors']);
54+
$this->assertEquals('some_error_code', $result['data']['placeOrder']['errors'][0]['code']);
55+
}
56+
}

app/code/Magento/QuoteGraphQl/etc/graphql/di.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,11 @@
106106
</argument>
107107
</arguments>
108108
</type>
109+
<type name="Magento\Framework\GraphQl\Query\QueryDataFormatter">
110+
<arguments>
111+
<argument name="formatters" xsi:type="array">
112+
<item name="process_order_response_formatter" xsi:type="object">Magento\QuoteGraphQl\Model\OrderResponseFormatter</item>
113+
</argument>
114+
</arguments>
115+
</type>
109116
</config>

app/code/Magento/QuoteGraphQl/etc/schema.graphqls

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,20 @@ type ApplyCouponToCartOutput @doc(description: "Contains details about the cart
262262
type PlaceOrderOutput @doc(description: "Contains the results of the request to place an order.") {
263263
order: Order @deprecated(reason: "Use `orderV2` instead.") @doc(description: "The ID of the order.")
264264
orderV2: CustomerOrder @doc(description: "Full order information.")
265+
errors: [PlaceOrderError!] @doc(description:"An array of place order errors.")
266+
}
267+
268+
type PlaceOrderError @doc(description:"An error encountered while placing an order."){
269+
message: String! @doc(description: "A localized error message.")
270+
code: PlaceOrderErrorCodes! @doc(description: "An error code that is specific to place order.")
271+
}
272+
273+
enum PlaceOrderErrorCodes {
274+
CART_NOT_FOUND
275+
CART_NOT_ACTIVE
276+
GUEST_EMAIL_MISSING
277+
UNABLE_TO_PLACE_ORDER
278+
UNDEFINED
265279
}
266280

267281
type Cart @doc(description: "Contains the contents and other details about a guest or customer cart.") {

dev/tests/api-functional/testsuite/Magento/QuoteGraphQl/Model/Resolver/PlaceOrderTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ public function testPlaceOrderErrorTranslation()
9393
'Sommige adressen kunnen niet worden',
9494
$exceptionData['errors'][0]['message']
9595
);
96+
self::assertEquals(
97+
'UNABLE_TO_PLACE_ORDER',
98+
$exceptionData['data']['placeOrder']['errors'][0]['code']
99+
);
96100
}
97101
}
98102

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\GraphQl\Query;
9+
10+
class QueryDataFormatter
11+
{
12+
/**
13+
* @var array
14+
*/
15+
private array $formatterPool = [];
16+
17+
/**
18+
* @param QueryResponseFormatterInterface[] $formatters
19+
*/
20+
public function __construct(array $formatters = [])
21+
{
22+
foreach ($formatters as $formatter) {
23+
if ($formatter instanceof QueryResponseFormatterInterface) {
24+
$this->formatterPool[] = $formatter;
25+
} else {
26+
throw new \InvalidArgumentException(
27+
sprintf('Formatter must implement %s', QueryResponseFormatterInterface::class)
28+
);
29+
}
30+
}
31+
}
32+
/**
33+
* Format the response using registered formatters
34+
*
35+
* @param array $executionResult
36+
* @return array
37+
*/
38+
public function formatResponse(array $executionResult): array
39+
{
40+
foreach ($this->formatterPool as $formatter) {
41+
$executionResult = $formatter->formatResponse($executionResult);
42+
}
43+
return $executionResult;
44+
}
45+
}

lib/internal/Magento/Framework/GraphQl/Query/QueryProcessor.php

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ class QueryProcessor
3737
*/
3838
private $errorHandler;
3939

40+
/**
41+
* @var QueryDataFormatter
42+
*/
43+
private QueryDataFormatter $formatter;
44+
4045
/**
4146
* @var QueryParser
4247
*/
@@ -46,18 +51,21 @@ class QueryProcessor
4651
* @param ExceptionFormatter $exceptionFormatter
4752
* @param QueryComplexityLimiter $queryComplexityLimiter
4853
* @param ErrorHandlerInterface $errorHandler
54+
* @param QueryDataFormatter $formatter
4955
* @param QueryParser|null $queryParser
5056
* @SuppressWarnings(PHPMD.LongVariable)
5157
*/
5258
public function __construct(
5359
ExceptionFormatter $exceptionFormatter,
5460
QueryComplexityLimiter $queryComplexityLimiter,
5561
ErrorHandlerInterface $errorHandler,
62+
QueryDataFormatter $formatter,
5663
?QueryParser $queryParser = null
5764
) {
5865
$this->exceptionFormatter = $exceptionFormatter;
5966
$this->queryComplexityLimiter = $queryComplexityLimiter;
6067
$this->errorHandler = $errorHandler;
68+
$this->formatter = $formatter;
6169
$this->queryParser = $queryParser ?: ObjectManager::getInstance()->get(QueryParser::class);
6270
}
6371

@@ -101,16 +109,7 @@ public function process(
101109
)->toArray(
102110
(int) ($this->exceptionFormatter->shouldShowDetail() ? DebugFlag::INCLUDE_DEBUG_MESSAGE : false)
103111
);
104-
if (!empty($executionResult['errors'])) {
105-
foreach ($executionResult['errors'] as $error) {
106-
if (isset($error['extensions']['error_code'])) {
107-
$executionResult['data']['errors'][] = [
108-
'message' => $error['message'],
109-
'code' => $error['extensions']['error_code']
110-
];
111-
}
112-
}
113-
}
114-
return $executionResult;
112+
113+
return $this->formatter->formatResponse($executionResult);
115114
}
116115
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\GraphQl\Query;
9+
10+
interface QueryResponseFormatterInterface
11+
{
12+
/**
13+
* Adjust execution result to the format expected by GraphQL response
14+
*
15+
* @param array $executionResult
16+
* @return array
17+
*/
18+
public function formatResponse(array $executionResult): array;
19+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Framework\GraphQl\Test\Unit\Query;
9+
10+
use Magento\Framework\GraphQl\Query\QueryDataFormatter;
11+
use Magento\Framework\GraphQl\Query\QueryResponseFormatterInterface;
12+
use PHPUnit\Framework\MockObject\Exception;
13+
use PHPUnit\Framework\TestCase;
14+
15+
class QueryDataFormatterTest extends TestCase
16+
{
17+
/**
18+
* @return void
19+
*/
20+
public function testNotUsingCorrectInterface(): void
21+
{
22+
$this->expectException(\InvalidArgumentException::class);
23+
$this->expectExceptionMessage(
24+
'Formatter must implement ' . QueryResponseFormatterInterface::class
25+
);
26+
27+
new QueryDataFormatter([new \stdClass()]);
28+
}
29+
30+
/**
31+
* @return void
32+
* @throws Exception
33+
*/
34+
public function testFormatResponse(): void
35+
{
36+
$formatterMock = $this->createMock(QueryResponseFormatterInterface::class);
37+
$formatterMock->expects($this->once())
38+
->method('formatResponse')
39+
->willReturn(['formatted' => true]);
40+
41+
$queryDataFormatter = new QueryDataFormatter([$formatterMock]);
42+
$result = $queryDataFormatter->formatResponse(['data' => 'test']);
43+
44+
$this->assertEquals(['formatted' => true], $result);
45+
}
46+
}

0 commit comments

Comments
 (0)