Skip to content

Commit 9e46ce6

Browse files
Merge directives from Scalar/Enum/Type/Input/Interface extension node into target node
1 parent a66df10 commit 9e46ce6

File tree

3 files changed

+194
-2
lines changed

3 files changed

+194
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co
99

1010
## Unreleased
1111

12+
### Added
13+
14+
- Merge directives from Scalar/Enum/Type/Input/Interface extension node into target node https://github.com/nuwave/lighthouse/pull/2512
15+
1216
## v6.33.4
1317

1418
### Fixed

src/Schema/AST/ASTBuilder.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
1111
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
1212
use GraphQL\Language\AST\ObjectTypeExtensionNode;
13+
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
14+
use GraphQL\Language\AST\ScalarTypeExtensionNode;
1315
use GraphQL\Language\AST\UnionTypeDefinitionNode;
1416
use GraphQL\Language\AST\UnionTypeExtensionNode;
1517
use GraphQL\Language\Parser;
@@ -33,6 +35,7 @@ class ASTBuilder
3335
ObjectTypeExtensionNode::class => ObjectTypeDefinitionNode::class,
3436
InputObjectTypeExtensionNode::class => InputObjectTypeDefinitionNode::class,
3537
InterfaceTypeExtensionNode::class => InterfaceTypeDefinitionNode::class,
38+
ScalarTypeExtensionNode::class => ScalarTypeDefinitionNode::class,
3639
EnumTypeExtensionNode::class => EnumTypeDefinitionNode::class,
3740
UnionTypeExtensionNode::class => UnionTypeDefinitionNode::class,
3841
];
@@ -122,6 +125,8 @@ protected function applyTypeExtensionManipulators(): void
122125
|| $typeExtension instanceof InterfaceTypeExtensionNode
123126
) {
124127
$this->extendObjectLikeType($typeName, $typeExtension);
128+
} elseif ($typeExtension instanceof ScalarTypeExtensionNode) {
129+
$this->extendScalarType($typeName, $typeExtension);
125130
} elseif ($typeExtension instanceof EnumTypeExtensionNode) {
126131
$this->extendEnumType($typeName, $typeExtension);
127132
} elseif ($typeExtension instanceof UnionTypeExtensionNode) {
@@ -156,6 +161,7 @@ protected function extendObjectLikeType(string $typeName, ObjectTypeExtensionNod
156161
// @phpstan-ignore-next-line
157162
$typeExtension->fields,
158163
);
164+
$extendedObjectLikeType->directives = $extendedObjectLikeType->directives->merge($typeExtension->directives);
159165

160166
if ($extendedObjectLikeType instanceof ObjectTypeDefinitionNode) {
161167
assert($typeExtension instanceof ObjectTypeExtensionNode, 'We know this because we passed assertExtensionMatchesDefinition().');
@@ -166,6 +172,17 @@ protected function extendObjectLikeType(string $typeName, ObjectTypeExtensionNod
166172
}
167173
}
168174

175+
protected function extendScalarType(string $typeName, ScalarTypeExtensionNode $typeExtension): void
176+
{
177+
$extendedScalar = $this->documentAST->types[$typeName]
178+
?? throw new DefinitionException($this->missingBaseDefinition($typeName, $typeExtension));
179+
assert($extendedScalar instanceof ScalarTypeDefinitionNode);
180+
181+
$this->assertExtensionMatchesDefinition($typeExtension, $extendedScalar);
182+
183+
$extendedScalar->directives = $extendedScalar->directives->merge($typeExtension->directives);
184+
}
185+
169186
protected function extendEnumType(string $typeName, EnumTypeExtensionNode $typeExtension): void
170187
{
171188
$extendedEnum = $this->documentAST->types[$typeName]
@@ -174,6 +191,7 @@ protected function extendEnumType(string $typeName, EnumTypeExtensionNode $typeE
174191

175192
$this->assertExtensionMatchesDefinition($typeExtension, $extendedEnum);
176193

194+
$extendedEnum->directives = $extendedEnum->directives->merge($typeExtension->directives);
177195
$extendedEnum->values = ASTHelper::mergeUniqueNodeList(
178196
$extendedEnum->values,
179197
$typeExtension->values,
@@ -194,12 +212,15 @@ protected function extendUnionType(string $typeName, UnionTypeExtensionNode $typ
194212
);
195213
}
196214

197-
protected function missingBaseDefinition(string $typeName, ObjectTypeExtensionNode|InputObjectTypeExtensionNode|InterfaceTypeExtensionNode|EnumTypeExtensionNode|UnionTypeExtensionNode $typeExtension): string
215+
protected function missingBaseDefinition(string $typeName, ObjectTypeExtensionNode|InputObjectTypeExtensionNode|InterfaceTypeExtensionNode|ScalarTypeExtensionNode|EnumTypeExtensionNode|UnionTypeExtensionNode $typeExtension): string
198216
{
199217
return "Could not find a base definition {$typeName} of kind {$typeExtension->kind} to extend.";
200218
}
201219

202-
protected function assertExtensionMatchesDefinition(ObjectTypeExtensionNode|InputObjectTypeExtensionNode|InterfaceTypeExtensionNode|EnumTypeExtensionNode|UnionTypeExtensionNode $extension, ObjectTypeDefinitionNode|InputObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeDefinitionNode|UnionTypeDefinitionNode $definition): void
220+
protected function assertExtensionMatchesDefinition(
221+
ObjectTypeExtensionNode|InputObjectTypeExtensionNode|InterfaceTypeExtensionNode|ScalarTypeExtensionNode|EnumTypeExtensionNode|UnionTypeExtensionNode $extension,
222+
ObjectTypeDefinitionNode|InputObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|ScalarTypeDefinitionNode|EnumTypeDefinitionNode|UnionTypeDefinitionNode $definition,
223+
): void
203224
{
204225
if (static::EXTENSION_TO_DEFINITION_CLASS[$extension::class] !== $definition::class) {
205226
throw new DefinitionException("The type extension {$extension->name->value} of kind {$extension->kind} can not extend a definition of kind {$definition->kind}.");

tests/Unit/Schema/AST/ASTBuilderTest.php

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
88
use GraphQL\Language\AST\NodeKind;
99
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
10+
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
1011
use GraphQL\Language\AST\UnionTypeDefinitionNode;
1112
use Illuminate\Support\Collection;
1213
use Nuwave\Lighthouse\Exceptions\DefinitionException;
1314
use Nuwave\Lighthouse\Schema\AST\ASTBuilder;
1415
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
16+
use Nuwave\Lighthouse\Schema\DirectiveLocator;
17+
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
1518
use Nuwave\Lighthouse\Schema\RootType;
1619
use Tests\TestCase;
1720

@@ -49,6 +52,35 @@ public function testMergeTypeExtensionFields(): void
4952
$this->assertCount(3, $queryType->fields);
5053
}
5154

55+
public function testMergeTypeExtensionDirectives(): void
56+
{
57+
$directive = new class() extends BaseDirective {
58+
public static function definition(): string
59+
{
60+
return /** @lang GraphQL */ 'directive @foo repeatable on OBJECT';
61+
}
62+
};
63+
64+
$directiveLocator = $this->app->make(DirectiveLocator::class);
65+
$directiveLocator->setResolved('foo', $directive::class);
66+
67+
$this->schema = /** @lang GraphQL */ '
68+
type MyType {
69+
field: String
70+
}
71+
72+
extend type MyType @foo
73+
74+
extend type MyType @foo
75+
';
76+
$documentAST = $this->astBuilder->documentAST();
77+
78+
$myType = $documentAST->types['MyType'];
79+
assert($myType instanceof ObjectTypeDefinitionNode);
80+
81+
$this->assertCount(2, $myType->directives);
82+
}
83+
5284
public function testAllowsExtendingUndefinedRootTypes(): void
5385
{
5486
$this->schema = /** @lang GraphQL */ '
@@ -105,6 +137,35 @@ public function testMergeInputExtensionFields(): void
105137
$this->assertCount(3, $inputs->fields);
106138
}
107139

140+
public function testMergeInputExtensionDirectives(): void
141+
{
142+
$directive = new class() extends BaseDirective {
143+
public static function definition(): string
144+
{
145+
return /** @lang GraphQL */ 'directive @foo repeatable on INPUT_OBJECT';
146+
}
147+
};
148+
149+
$directiveLocator = $this->app->make(DirectiveLocator::class);
150+
$directiveLocator->setResolved('foo', $directive::class);
151+
152+
$this->schema = /** @lang GraphQL */ '
153+
input MyInput {
154+
field: String
155+
}
156+
157+
extend input MyInput @foo
158+
159+
extend input MyInput @foo
160+
';
161+
$documentAST = $this->astBuilder->documentAST();
162+
163+
$myInput = $documentAST->types['MyInput'];
164+
assert($myInput instanceof InputObjectTypeDefinitionNode);
165+
166+
$this->assertCount(2, $myInput->directives);
167+
}
168+
108169
public function testMergeInterfaceExtensionFields(): void
109170
{
110171
$this->schema = /** @lang GraphQL */ '
@@ -128,6 +189,62 @@ interface Named {
128189
$this->assertCount(3, $named->fields);
129190
}
130191

192+
public function testMergeInterfaceExtensionDirectives(): void
193+
{
194+
$directive = new class() extends BaseDirective {
195+
public static function definition(): string
196+
{
197+
return /** @lang GraphQL */ 'directive @foo repeatable on INTERFACE';
198+
}
199+
};
200+
201+
$directiveLocator = $this->app->make(DirectiveLocator::class);
202+
$directiveLocator->setResolved('foo', $directive::class);
203+
204+
$this->schema = /** @lang GraphQL */ '
205+
interface MyInterface {
206+
field: String
207+
}
208+
209+
extend interface MyInterface @foo
210+
211+
extend interface MyInterface @foo
212+
';
213+
$documentAST = $this->astBuilder->documentAST();
214+
215+
$myInterface = $documentAST->types['MyInterface'];
216+
assert($myInterface instanceof InterfaceTypeDefinitionNode);
217+
218+
$this->assertCount(2, $myInterface->directives);
219+
}
220+
221+
public function testMergeScalarExtensionDirectives(): void
222+
{
223+
$directive = new class() extends BaseDirective {
224+
public static function definition(): string
225+
{
226+
return /** @lang GraphQL */ 'directive @foo repeatable on SCALAR';
227+
}
228+
};
229+
230+
$directiveLocator = $this->app->make(DirectiveLocator::class);
231+
$directiveLocator->setResolved('foo', $directive::class);
232+
233+
$this->schema = /** @lang GraphQL */ '
234+
scalar MyScalar
235+
236+
extend scalar MyScalar @foo
237+
238+
extend scalar MyScalar @foo
239+
';
240+
$documentAST = $this->astBuilder->documentAST();
241+
242+
$myScalar = $documentAST->types['MyScalar'];
243+
assert($myScalar instanceof ScalarTypeDefinitionNode);
244+
245+
$this->assertCount(2, $myScalar->directives);
246+
}
247+
131248
public function testMergeEnumExtensionFields(): void
132249
{
133250
$this->schema = /** @lang GraphQL */ '
@@ -152,6 +269,36 @@ enum MyEnum {
152269
$this->assertCount(4, $myEnum->values);
153270
}
154271

272+
public function testMergeEnumExtensionDirectives(): void
273+
{
274+
$directive = new class() extends BaseDirective {
275+
public static function definition(): string
276+
{
277+
return /** @lang GraphQL */ 'directive @foo repeatable on ENUM';
278+
}
279+
};
280+
281+
$directiveLocator = $this->app->make(DirectiveLocator::class);
282+
$directiveLocator->setResolved('foo', $directive::class);
283+
284+
$this->schema = /** @lang GraphQL */ '
285+
enum MyEnum {
286+
ONE
287+
TWO
288+
}
289+
290+
extend enum MyEnum @foo
291+
292+
extend enum MyEnum @foo
293+
';
294+
$documentAST = $this->astBuilder->documentAST();
295+
296+
$myEnum = $documentAST->types['MyEnum'];
297+
assert($myEnum instanceof EnumTypeDefinitionNode);
298+
299+
$this->assertCount(2, $myEnum->directives);
300+
}
301+
155302
public function testMergeUnionExtensionFields(): void
156303
{
157304
$this->schema = /** @lang GraphQL */ '
@@ -173,6 +320,26 @@ public function testMergeUnionExtensionFields(): void
173320
$this->assertCount(3, $myUnion->types);
174321
}
175322

323+
public function testDoesNotAllowExtendingUndefinedScalar(): void
324+
{
325+
$directive = new class() extends BaseDirective {
326+
public static function definition(): string
327+
{
328+
return /** @lang GraphQL */ 'directive @foo repeatable on SCALAR';
329+
}
330+
};
331+
332+
$directiveLocator = $this->app->make(DirectiveLocator::class);
333+
$directiveLocator->setResolved('foo', $directive::class);
334+
335+
$this->schema = /** @lang GraphQL */ '
336+
extend scalar MyScalar @foo
337+
';
338+
339+
$this->expectExceptionObject(new DefinitionException('Could not find a base definition MyScalar of kind ' . NodeKind::SCALAR_TYPE_EXTENSION . ' to extend.'));
340+
$this->astBuilder->documentAST();
341+
}
342+
176343
public function testDoesNotAllowExtendingUndefinedTypes(): void
177344
{
178345
$this->schema = /** @lang GraphQL */ '

0 commit comments

Comments
 (0)