Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend union support #2468

Merged
merged 5 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

### Added

- Add support for `extend union` syntax https://github.com/nuwave/lighthouse/pull/2468

## v6.23.2

### Fixed
Expand Down
23 changes: 21 additions & 2 deletions src/Schema/AST/ASTBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Language\Parser;
use Illuminate\Contracts\Events\Dispatcher as EventsDispatcher;
use Illuminate\Support\Arr;
Expand All @@ -31,6 +33,7 @@ class ASTBuilder
InputObjectTypeExtensionNode::class => InputObjectTypeDefinitionNode::class,
InterfaceTypeExtensionNode::class => InterfaceTypeDefinitionNode::class,
EnumTypeExtensionNode::class => EnumTypeDefinitionNode::class,
UnionTypeExtensionNode::class => UnionTypeDefinitionNode::class,
];

/** Initialized lazily in $this->documentAST(). */
Expand Down Expand Up @@ -119,6 +122,8 @@ protected function applyTypeExtensionManipulators(): void
$this->extendObjectLikeType($typeName, $typeExtension);
} elseif ($typeExtension instanceof EnumTypeExtensionNode) {
$this->extendEnumType($typeName, $typeExtension);
} elseif ($typeExtension instanceof UnionTypeExtensionNode) {
$this->extendUnionType($typeName, $typeExtension);
}
}
}
Expand Down Expand Up @@ -173,12 +178,26 @@ protected function extendEnumType(string $typeName, EnumTypeExtensionNode $typeE
);
}

protected function missingBaseDefinition(string $typeName, ObjectTypeExtensionNode|InputObjectTypeExtensionNode|InterfaceTypeExtensionNode|EnumTypeExtensionNode $typeExtension): string
protected function extendUnionType(string $typeName, UnionTypeExtensionNode $typeExtension): void
{
$extendedUnion = $this->documentAST->types[$typeName]
?? throw new DefinitionException($this->missingBaseDefinition($typeName, $typeExtension));
assert($extendedUnion instanceof UnionTypeDefinitionNode);

$this->assertExtensionMatchesDefinition($typeExtension, $extendedUnion);

$extendedUnion->types = ASTHelper::mergeUniqueNodeList(
$extendedUnion->types,
$typeExtension->types,
);
}

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

protected function assertExtensionMatchesDefinition(ObjectTypeExtensionNode|InputObjectTypeExtensionNode|InterfaceTypeExtensionNode|EnumTypeExtensionNode $extension, ObjectTypeDefinitionNode|InputObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeDefinitionNode $definition): void
protected function assertExtensionMatchesDefinition(ObjectTypeExtensionNode|InputObjectTypeExtensionNode|InterfaceTypeExtensionNode|EnumTypeExtensionNode|UnionTypeExtensionNode $extension, ObjectTypeDefinitionNode|InputObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeDefinitionNode|UnionTypeDefinitionNode $definition): void
{
if (static::EXTENSION_TO_DEFINITION_CLASS[$extension::class] !== $definition::class) {
throw new DefinitionException("The type extension {$extension->name->value} of kind {$extension->kind} can not extend a definition of kind {$definition->kind}.");
Expand Down
49 changes: 49 additions & 0 deletions tests/Unit/Schema/AST/ASTBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use Illuminate\Support\Collection;
use Nuwave\Lighthouse\Exceptions\DefinitionException;
use Nuwave\Lighthouse\Schema\AST\ASTBuilder;
Expand Down Expand Up @@ -152,6 +153,27 @@ enum MyEnum {
$this->assertCount(4, $myEnum->values);
}

public function testMergeUnionExtensionFields(): void
{
$this->schema = /** @lang GraphQL */ '
type Foo
type Bar
type Baz

union MyUnion = Foo

extend union MyUnion = Bar

extend union MyUnion = Baz
';
$documentAST = $this->astBuilder->documentAST();

$myUnion = $documentAST->types['MyUnion'];
assert($myUnion instanceof UnionTypeDefinitionNode);

$this->assertCount(3, $myUnion->types);
}

public function testDoesNotAllowExtendingUndefinedTypes(): void
{
$this->schema = /** @lang GraphQL */ '
Expand All @@ -168,6 +190,18 @@ public function testDoesNotAllowExtendingUndefinedTypes(): void
$this->astBuilder->documentAST();
}

public function testDoesNotAllowExtendingUndefinedUnions(): void
{
$this->schema = /** @lang GraphQL */ '
union MyFirstEnum = String

extend union MySecondUnion = Int
';

$this->expectExceptionObject(new DefinitionException('Could not find a base definition MySecondUnion of kind ' . NodeKind::UNION_TYPE_EXTENSION . ' to extend.'));
$this->astBuilder->documentAST();
}

public function testDoesNotAllowDuplicateFieldsOnTypeExtensions(): void
{
$this->schema = /** @lang GraphQL */ '
Expand Down Expand Up @@ -234,6 +268,21 @@ enum MyEnum {
$this->astBuilder->documentAST();
}

public function testDoesNotAllowDuplicateTypesOnUnionExtensions(): void
{
$this->schema = /** @lang GraphQL */ '
type Foo
type Bar

union MyUnion = Foo | Bar

extend union MyUnion = Bar
';

$this->expectExceptionObject(new DefinitionException(ASTHelper::duplicateDefinition('Bar')));
$this->astBuilder->documentAST();
}

public function testDoesNotAllowMergingNonMatchingTypes(): void
{
$this->schema = /** @lang GraphQL */ '
Expand Down
Loading