Skip to content

Commit 1d65512

Browse files
feat: catch error during delegate execution
1 parent f069828 commit 1d65512

File tree

3 files changed

+114
-23
lines changed

3 files changed

+114
-23
lines changed

src/RootFieldsResolver.php

+48-23
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use GraphQL\Type\Definition\Type;
1616
use GraphQL\Type\Definition\WrappingType;
1717
use GraphQL\Type\Introspection;
18+
use GraphQL\Type\Schema;
1819
use XGraphQL\DelegateExecution\Exception\LogicException;
1920
use XGraphQL\Utils\SelectionSet;
2021

@@ -35,33 +36,57 @@ public function __construct(
3536
public function __invoke(mixed $value, array $arguments, mixed $context, ResolveInfo $info): Promise
3637
{
3738
if (!isset($this->delegatedPromises[$info->operation])) {
39+
$this->delegatedPromises[$info->operation] = $this->delegateToExecute(
40+
$info->schema,
41+
$info->operation,
42+
$info->fragments,
43+
$info->variableValues
44+
);
45+
}
46+
47+
return $this->resolve($info);
48+
}
49+
50+
51+
/**
52+
* @param Schema $schema
53+
* @param OperationDefinitionNode $operation
54+
* @param array<string, FragmentDefinitionNode> $fragments
55+
* @param array<string, mixed> $variables
56+
* @return Promise
57+
*/
58+
private function delegateToExecute(Schema $schema, OperationDefinitionNode $operation, array $fragments, array $variables): Promise
59+
{
60+
try {
3861
/// We need to clone all fragments and operation to make sure it can not be mutated by delegator.
39-
$operation = $info->operation->cloneDeep();
40-
$fragments = array_map(fn (FragmentDefinitionNode $fragment) => $fragment->cloneDeep(), $info->fragments);
62+
$delegateOperation = $operation->cloneDeep();
63+
$delegateFragments = array_map(fn(FragmentDefinitionNode $fragment) => $fragment->cloneDeep(), $fragments);
4164

4265
/// Add typename for detecting object type of interface or union
43-
SelectionSet::addTypename($operation->getSelectionSet());
44-
SelectionSet::addTypenameToFragments($fragments);
45-
46-
$this->delegatedPromises[$info->operation] = $this
47-
->delegator
48-
->delegate(
49-
$info->schema,
50-
$operation,
51-
$fragments,
52-
$info->variableValues
53-
)->then(
54-
function (ExecutionResult $result): ExecutionResult {
55-
if ([] !== $result->errors) {
56-
$this->delegatedErrorsReporter?->reportErrors($result->errors);
57-
}
58-
59-
return $result;
60-
}
61-
);
66+
SelectionSet::addTypename($delegateOperation->getSelectionSet());
67+
SelectionSet::addTypenameToFragments($delegateFragments);
68+
69+
$promise = $this->delegator->delegate($schema, $delegateOperation, $delegateFragments, $variables);
70+
} catch (\Throwable $exception) {
71+
$result = new ExecutionResult(
72+
null,
73+
[
74+
new Error('Error during delegate execution', [$operation], previous: $exception)
75+
],
76+
);
77+
78+
$promise = $this->delegator->getPromiseAdapter()->createFulfilled($result);
6279
}
6380

64-
return $this->resolve($info);
81+
return $promise->then(
82+
function (ExecutionResult $result): ExecutionResult {
83+
if ([] !== $result->errors) {
84+
$this->delegatedErrorsReporter?->reportErrors($result->errors);
85+
}
86+
87+
return $result;
88+
}
89+
);
6590
}
6691

6792
private function resolve(ResolveInfo $info): Promise
@@ -72,7 +97,7 @@ private function resolve(ResolveInfo $info): Promise
7297

7398
$promise = $this->delegatedPromises[$info->operation];
7499

75-
return $promise->then(fn (ExecutionResult $result) => $this->accessResultByPath($info->path, $result));
100+
return $promise->then(fn(ExecutionResult $result) => $this->accessResultByPath($info->path, $result));
76101
}
77102

78103
private function prepareTypeResolver(Type $type): void

tests/BadExecutionDelegator.php

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace XGraphQL\DelegateExecution\Test;
6+
7+
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
8+
use GraphQL\Executor\Promise\Promise;
9+
use GraphQL\Executor\Promise\PromiseAdapter;
10+
use GraphQL\Language\AST\OperationDefinitionNode;
11+
use GraphQL\Type\Schema;
12+
use XGraphQL\DelegateExecution\ExecutionDelegatorInterface;
13+
14+
final readonly class BadExecutionDelegator implements ExecutionDelegatorInterface
15+
{
16+
17+
public function delegate(Schema $executionSchema, OperationDefinitionNode $operation, array $fragments = [], array $variables = []): Promise
18+
{
19+
throw new \RuntimeException('Bad execution delegator');
20+
}
21+
22+
public function getPromiseAdapter(): PromiseAdapter
23+
{
24+
return new SyncPromiseAdapter();
25+
}
26+
}

tests/ExecutionTest.php

+40
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
namespace XGraphQL\DelegateExecution\Test;
66

7+
use GraphQL\Error\Error;
78
use GraphQL\GraphQL;
89
use GraphQL\Type\Definition\ObjectType;
910
use GraphQL\Utils\BuildSchema;
1011
use PHPUnit\Framework\TestCase;
12+
use XGraphQL\DelegateExecution\DelegatedErrorsReporterInterface;
1113
use XGraphQL\DelegateExecution\Execution;
1214
use XGraphQL\DelegateExecution\ExecutionDelegatorInterface;
1315
use XGraphQL\DelegateExecution\RootFieldsResolver;
@@ -172,4 +174,42 @@ interface Unknown {
172174

173175
$this->assertEquals('Expect type: `DummyObject` implementing `Unknown` should be exist in schema', $result->errors[0]->getMessage());
174176
}
177+
178+
public function testErrorDuringDelegateExecution(): void
179+
{
180+
/** @var Error[] $delegatedErrors */
181+
$delegatedErrors = null;
182+
$reporter = $this->createMock(DelegatedErrorsReporterInterface::class);
183+
184+
$reporter->expects($this->once())->method('reportErrors')->willReturnCallback(
185+
function (array $errors) use (&$delegatedErrors): void {
186+
$delegatedErrors = $errors;
187+
}
188+
);
189+
190+
$delegator = new BadExecutionDelegator();
191+
$schema = BuildSchema::build(
192+
<<<'SDL'
193+
type Query {
194+
dummy: String!
195+
}
196+
SDL
197+
);
198+
Execution::delegate($schema, $delegator, $reporter);
199+
200+
$result = GraphQL::executeQuery(
201+
$schema,
202+
<<<'GQL'
203+
query {
204+
dummy
205+
}
206+
GQL
207+
);
208+
209+
$this->assertNotNull($delegatedErrors);
210+
$this->assertEquals('Error during delegate execution', $delegatedErrors[0]->getMessage());
211+
$this->assertNotNull($delegatedErrors[0]->getPrevious());
212+
$this->assertEquals('Bad execution delegator', $delegatedErrors[0]->getPrevious()->getMessage());
213+
$this->assertEquals('Delegated execution result is missing field value at path: `dummy`', $result->errors[0]->getMessage());
214+
}
175215
}

0 commit comments

Comments
 (0)