Skip to content

Commit 85b9cf0

Browse files
authored
Merge pull request #2003 from magento-honey-badgers/MAGETWO-87010-Graph-Ql-Error-Handling
[honey] MAGETWO-81668: GraphQL error handling
2 parents 1a81e05 + 328a463 commit 85b9cf0

File tree

17 files changed

+606
-28
lines changed

17 files changed

+606
-28
lines changed

app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ public function resolve(array $args, ResolverContextInterface $context)
5555
{
5656
$searchCriteria = $this->searchCriteriaBuilder->build($args);
5757

58-
if (isset($args['search'])) {
58+
if (!isset($args['search']) && !isset($args['filter'])) {
59+
throw new GraphQlInputException(
60+
__("One of 'search' or 'filter' input arguments needs to be specified in products request.")
61+
);
62+
} elseif (isset($args['search'])) {
5963
$searchResult = $this->searchQuery->getResult($searchCriteria);
6064
} else {
6165
$searchResult = $this->filterQuery->getResult($searchCriteria);
@@ -67,8 +71,10 @@ public function resolve(array $args, ResolverContextInterface $context)
6771
} else {
6872
$maxPages = 0;
6973
}
74+
75+
$currentPage = $searchCriteria->getCurrentPage();
7076
if ($searchCriteria->getCurrentPage() > $maxPages && $searchResult->getTotalCount() > 0) {
71-
throw new GraphQlInputException(
77+
$currentPage = new GraphQlInputException(
7278
__(
7379
'The value specified in the currentPage attribute is greater than the number'
7480
. ' of pages available (%1).',
@@ -82,7 +88,7 @@ public function resolve(array $args, ResolverContextInterface $context)
8288
'items' => $searchResult->getProductsSearchResult(),
8389
'page_info' => [
8490
'page_size' => $searchCriteria->getPageSize(),
85-
'current_page' => $searchCriteria->getCurrentPage()
91+
'current_page' => $currentPage
8692
]
8793
];
8894
}

app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
namespace Magento\CustomerGraphQl\Model\Resolver;
88

9+
use Magento\Framework\Exception\NoSuchEntityException;
10+
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
911
use Magento\GraphQl\Model\ResolverInterface;
1012
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
1113
use Magento\GraphQl\Model\ResolverContextInterface;
@@ -36,10 +38,17 @@ public function resolve(array $args, ResolverContextInterface $context)
3638
{
3739
if ((!$context->getUserId()) || $context->getUserType() == 4) {
3840
throw new GraphQlInputException(
39-
__('Current customer does not have access to the resource "%1"', 'customer')
41+
__(
42+
'Current customer does not have access to the resource "%1"',
43+
[\Magento\Customer\Model\Customer::ENTITY]
44+
)
4045
);
4146
}
4247

43-
return $this->customerResolver->getCustomerById($context->getUserId());
48+
try {
49+
return $this->customerResolver->getCustomerById($context->getUserId());
50+
} catch (NoSuchEntityException $exception) {
51+
return new GraphQlNoSuchEntityException(__('Customer id %1 does not exist.', [$context->getUserId()]));
52+
}
4453
}
4554
}

app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/CustomerDataProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
namespace Magento\CustomerGraphQl\Model\Resolver\Customer;
88

99
use Magento\Customer\Api\CustomerRepositoryInterface;
10+
use Magento\Framework\Exception\LocalizedException;
1011
use Magento\Framework\Exception\NoSuchEntityException;
1112
use Magento\Framework\Serialize\SerializerInterface;
1213
use Magento\Framework\Webapi\ServiceOutputProcessor;
@@ -51,6 +52,7 @@ public function __construct(
5152
*
5253
* @param int $customerId
5354
* @return array|null
55+
* @throws NoSuchEntityException|LocalizedException
5456
*/
5557
public function getCustomerById(int $customerId)
5658
{

app/code/Magento/EavGraphQl/Model/Resolver/CustomAttributeMetadata.php

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66

77
namespace Magento\EavGraphQl\Model\Resolver;
88

9+
use Magento\Framework\Exception\InputException;
10+
use Magento\Framework\GraphQl\Argument\ArgumentValueInterface;
911
use Magento\Framework\GraphQl\ArgumentInterface;
12+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
13+
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
1014
use Magento\GraphQl\Model\ResolverInterface;
11-
use Magento\Framework\Exception\InputException;
1215
use \Magento\GraphQl\Model\ResolverContextInterface;
1316
use Magento\EavGraphQl\Model\Resolver\Query\Type;
1417

@@ -35,25 +38,64 @@ public function __construct(Type $type)
3538
*/
3639
public function resolve(array $args, ResolverContextInterface $context)
3740
{
38-
if (!isset($args['attributes']) || empty($args['attributes'])) {
39-
throw new InputException(__('Missing arguments for correct type resolution.'));
40-
}
41-
4241
$attributes['items'] = null;
4342
/** @var ArgumentInterface $attributeInputs */
4443
$attributeInputs = $args['attributes'];
4544
foreach ($attributeInputs->getValue() as $attribute) {
46-
$type = $this->type->getType($attribute['attribute_code'], $attribute['entity_type']);
47-
48-
if (!empty($type)) {
49-
$attributes['items'][] = [
50-
'attribute_code' => $attribute['attribute_code'],
51-
'entity_type' => $attribute['entity_type'],
52-
'attribute_type' => ucfirst($type)
53-
];
45+
if (!isset($attribute['attribute_code']) || !isset($attribute['entity_type'])) {
46+
$attributes['items'][] = $this->createInputException($attribute);
47+
continue;
5448
}
49+
try {
50+
$type = $this->type->getType($attribute['attribute_code'], $attribute['entity_type']);
51+
} catch (InputException $exception) {
52+
$attributes['items'][] = new GraphQlNoSuchEntityException(
53+
__(
54+
'Attribute code %1 of entity type %2 not configured to have a type.',
55+
[$attribute['attribute_code'], $attribute['entity_type']]
56+
)
57+
);
58+
continue;
59+
}
60+
61+
if (empty($type)) {
62+
continue;
63+
}
64+
65+
$attributes['items'][] = [
66+
'attribute_code' => $attribute['attribute_code'],
67+
'entity_type' => $attribute['entity_type'],
68+
'attribute_type' => ucfirst($type)
69+
];
5570
}
5671

5772
return $attributes;
5873
}
74+
75+
/**
76+
* Create GraphQL input exception for an invalid AttributeInput ArgumentValueInterface
77+
*
78+
* @param array $attribute
79+
* @return GraphQlInputException
80+
*/
81+
private function createInputException(array $attribute)
82+
{
83+
$isCodeSet = isset($attribute['attribute_code']);
84+
$isEntitySet = isset($attribute['entity_type']);
85+
$messagePart = !$isCodeSet ? 'attribute_code' : 'entity_type';
86+
$messagePart .= !$isCodeSet && !$isEntitySet ? '/entity_type' : '';
87+
$identifier = "Empty AttributeInput";
88+
if ($isCodeSet) {
89+
$identifier = 'attribute_code: ' . $attribute['attribute_code'];
90+
} elseif ($isEntitySet) {
91+
$identifier = 'entity_type: ' . $attribute['entity_type'];
92+
}
93+
94+
return new GraphQlInputException(
95+
__(
96+
'Attribute input does not contain %1 for the input %2.',
97+
[$messagePart, $identifier]
98+
)
99+
);
100+
}
59101
}

app/code/Magento/GraphQl/Controller/GraphQl.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public function __construct(
9393
*/
9494
public function dispatch(RequestInterface $request)
9595
{
96+
$statusCode = 200;
9697
try {
9798
/** @var Http $request */
9899
$this->requestProcessor->processHeaders($request);
@@ -106,12 +107,14 @@ public function dispatch(RequestInterface $request)
106107
isset($data['variables']) ? $data['variables'] : []
107108
);
108109
} catch (\Exception $error) {
109-
$result['extensions']['exception'] = $this->graphQlError->create($error);
110+
$result['errors'] = isset($result) && isset($result['errors']) ? $result['errors'] : [];
111+
$result['errors'][] = $this->graphQlError->create($error);
112+
$statusCode = ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS;
110113
}
111114
$this->response->setBody($this->jsonSerializer->serialize($result))->setHeader(
112115
'Content-Type',
113116
'application/json'
114-
);
117+
)->setHttpResponseCode($statusCode);
115118
return $this->response;
116119
}
117120
}

app/code/Magento/GraphQl/etc/graphql.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<type xsi:type="OutputType" name="Query" />
88
<type xsi:type="InputType" name="FilterTypeInput">
99
<field xsi:type="ScalarInputField" name="eq" type="String"/>
10-
<field xsi:type="ObjectArrayInputField" name="finset" itemType="AttributeInput"/>
10+
<field xsi:type="ScalarArrayInputField" name="finset" itemType="String"/>
1111
<field xsi:type="ScalarInputField" name="from" type="String"/>
1212
<field xsi:type="ScalarInputField" name="gt" type="String"/>
1313
<field xsi:type="ScalarInputField" name="gteq" type="String"/>
@@ -30,4 +30,4 @@
3030
<item name="asc">ASC</item>
3131
<item name="desc">DESC</item>
3232
</type>
33-
</config>
33+
</config>

dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Magento\TestFramework\TestCase\HttpClient\CurlClient;
1010
use Magento\TestFramework\Helper\JsonSerializer;
1111
use Magento\TestFramework\Helper\Bootstrap;
12+
use PHPUnit\Framework\TestCase;
1213

1314
/**
1415
* Curl client for GraphQL
@@ -73,7 +74,12 @@ public function postQuery(string $query, array $variables = [], string $operatio
7374
if (isset($error['message'])) {
7475
$errorMessage .= $error['message'] . PHP_EOL;
7576
}
77+
if (isset($error['trace'])) {
78+
$traceString = $error['trace'];
79+
TestCase::assertNotEmpty($traceString, "trace is empty");
80+
}
7681
}
82+
7783
throw new \Exception('GraphQL response contains errors: ' . $errorMessage);
7884
}
7985
throw new \Exception('GraphQL responded with an unknown error: ' . $responseBody);
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
namespace Magento\GraphQl\Catalog;
7+
8+
use Magento\TestFramework\TestCase\GraphQlAbstract;
9+
10+
class ExceptionFormatterDefaultModeTest extends GraphQlAbstract
11+
{
12+
public function testInvalidEntityTypeExceptionInDefaultMode()
13+
{
14+
if (!$this->cleanCache()) {
15+
$this->fail('Cache could not be cleaned properly.');
16+
}
17+
$query
18+
= <<<QUERY
19+
{
20+
customAttributeMetadata(attributes:[
21+
{
22+
attribute_code:"sku"
23+
entity_type:"invalid"
24+
}
25+
])
26+
{
27+
items{
28+
attribute_code
29+
attribute_type
30+
entity_type
31+
}
32+
}
33+
}
34+
QUERY;
35+
$this->expectException(\Exception::class);
36+
37+
$this->expectExceptionMessage('GraphQL response contains errors: Attribute code' . ' ' .
38+
'sku of entity type invalid not configured to have a type.');
39+
40+
$this->graphQlQuery($query);
41+
}
42+
43+
public function testDuplicateEntityTypeException()
44+
{
45+
$query
46+
= <<<QUERY
47+
{
48+
customAttributeMetadata(attributes:[
49+
{
50+
entity_type:"catalog_category"
51+
entity_type:"catalog_product"
52+
}
53+
])
54+
{
55+
items{
56+
attribute_code
57+
attribute_type
58+
entity_type
59+
}
60+
}
61+
}
62+
QUERY;
63+
$this->expectException(\Exception::class);
64+
$this->expectExceptionMessage('GraphQL response contains errors: There' . ' ' .
65+
'can be only one input field named "entity_type"');
66+
$this->graphQlQuery($query);
67+
}
68+
69+
public function testEmptyAttributeInputException()
70+
{
71+
$query
72+
= <<<QUERY
73+
{
74+
customAttributeMetadata(attributes:[
75+
{
76+
77+
}
78+
])
79+
{
80+
items{
81+
attribute_code
82+
attribute_type
83+
entity_type
84+
}
85+
}
86+
}
87+
QUERY;
88+
$this->expectException(\Exception::class);
89+
$this->expectExceptionMessage('GraphQL response contains errors: Attribute' . ' ' .
90+
'input does not contain attribute_code/entity_type for the input Empty AttributeInput.');
91+
92+
$this->graphQlQuery($query);
93+
}
94+
public function testAttributeWithNoEntityTypeInputException()
95+
{
96+
$query
97+
= <<<QUERY
98+
{
99+
customAttributeMetadata(attributes:[
100+
{
101+
attribute_code:"sku"
102+
}
103+
])
104+
{
105+
items{
106+
attribute_code
107+
attribute_type
108+
entity_type
109+
}
110+
}
111+
}
112+
QUERY;
113+
$this->expectException(\Exception::class);
114+
$this->expectExceptionMessage('GraphQL response contains errors: Attribute input' . ' ' .
115+
'does not contain entity_type for the input attribute_code: sku.');
116+
117+
$this->graphQlQuery($query);
118+
}
119+
120+
public function testAttributeWithNoAttributeCodeInputException()
121+
{
122+
$query
123+
= <<<QUERY
124+
{
125+
customAttributeMetadata(attributes:[
126+
{
127+
entity_type:"catalog_category"
128+
}
129+
])
130+
{
131+
items{
132+
attribute_code
133+
attribute_type
134+
entity_type
135+
}
136+
}
137+
}
138+
QUERY;
139+
$this->expectException(\Exception::class);
140+
$this->expectExceptionMessage('GraphQL response contains errors: Attribute input' . ' ' .
141+
'does not contain attribute_code for the input entity_type: catalog_category.');
142+
143+
$this->graphQlQuery($query);
144+
}
145+
}

0 commit comments

Comments
 (0)