Skip to content

Commit

Permalink
Add InputFieldManipulator directive interface
Browse files Browse the repository at this point in the history
  • Loading branch information
jaulz authored Jan 17, 2024
1 parent e9930f7 commit 4426483
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 0 deletions.
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 `InputFieldManipulator` directive interface https://github.com/nuwave/lighthouse/pull/2476

## v6.30.0

### Added
Expand Down
50 changes: 50 additions & 0 deletions docs/master/custom-directives/input-field-directives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Input Field Directives

Input field directives can be (similarly to [Argument Directives](argument-directives.html)) applied to a [InputValueDefinition](https://graphql.github.io/graphql-spec/June2018/#InputValueDefinition). In contrast to argument directives, input field directives can be set on fields of an input.

## InputFieldManipulator

In order for an input field directive to work you must implement the [`\Nuwave\Lighthouse\Support\Contracts\InputFieldManipulator`](https://github.com/nuwave/lighthouse/tree/master/src/Support/Contracts/InputFieldManipulator.php) interface.

```php
namespace Nuwave\Lighthouse\Schema\Directives;

use Nuwave\Lighthouse\Support\Contracts\InputFieldManipulator;
use GraphQL\Language\Parser;

final class ModelIdDirective extends BaseDirective implements InputFieldManipulator
{
public static function definition(): string
{
return /** @lang GraphQL */ <<<'GRAPHQL'
"""
Append Id to name of input field and change the type to ID.
"""
directive @modelId on INPUT_FIELD_DEFINITION
GRAPHQL;
}

public function manipulateInputFieldDefinition(
DocumentAST &$documentAST,
InputValueDefinitionNode &$inputDefinition,
InputObjectTypeDefinitionNode &$parentType,
): void {
$inputDefinition->name->value = $inputDefinition->name->value . 'Id';
$inputDefinition->type = Parser::namedType('ID');
}
}
```

```graphql
input Payload {
post: Post @modelId
}

type Mutation {
createComment(
payload: Payload
): User
}
```

In the given example, the name of the input field will be renamed to `postId` and its type will be changed to `ID`.
22 changes: 22 additions & 0 deletions src/Schema/AST/ASTBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Nuwave\Lighthouse\Schema\Source\SchemaSourceProvider;
use Nuwave\Lighthouse\Support\Contracts\ArgManipulator;
use Nuwave\Lighthouse\Support\Contracts\FieldManipulator;
use Nuwave\Lighthouse\Support\Contracts\InputFieldManipulator;
use Nuwave\Lighthouse\Support\Contracts\TypeExtensionManipulator;
use Nuwave\Lighthouse\Support\Contracts\TypeManipulator;

Expand Down Expand Up @@ -76,6 +77,7 @@ public function build(): DocumentAST
$this->applyTypeExtensionManipulators();
$this->applyFieldManipulators();
$this->applyArgManipulators();
$this->applyInputFieldManipulators();

// Listeners may manipulate the DocumentAST that is passed by reference
// into the ManipulateAST event. This can be useful for extensions
Expand Down Expand Up @@ -241,5 +243,25 @@ protected function applyArgManipulators(): void
}
}
}
}

/** Apply directives on input fields that can manipulate the AST. */
protected function applyInputFieldManipulators(): void
{
foreach ($this->documentAST->types as $typeDefinition) {
if ($typeDefinition instanceof InputObjectTypeDefinitionNode) {
foreach ($typeDefinition->fields as $fieldDefinition) {
foreach (
$this->directiveLocator->associatedOfType($fieldDefinition, InputFieldManipulator::class) as $inputFieldManipulator
) {
$inputFieldManipulator->manipulateInputFieldDefinition(
$this->documentAST,
$fieldDefinition,
$typeDefinition,
);
}
}
}
}
}
}
17 changes: 17 additions & 0 deletions src/Support/Contracts/InputFieldManipulator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php declare(strict_types=1);

namespace Nuwave\Lighthouse\Support\Contracts;

use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use Nuwave\Lighthouse\Schema\AST\DocumentAST;

interface InputFieldManipulator extends Directive
{
/** Manipulate the AST. */
public function manipulateInputFieldDefinition(
DocumentAST &$documentAST,
InputValueDefinitionNode &$inputDefinition,
InputObjectTypeDefinitionNode &$parentType,
): void;
}
67 changes: 67 additions & 0 deletions tests/Unit/Schema/AST/ASTHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Tests\Unit\Schema\AST;

use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\NamedTypeNode;
Expand All @@ -19,6 +20,7 @@
use Nuwave\Lighthouse\Schema\RootType;
use Nuwave\Lighthouse\Support\Contracts\ArgManipulator;
use Nuwave\Lighthouse\Support\Contracts\FieldManipulator;
use Nuwave\Lighthouse\Support\Contracts\InputFieldManipulator;
use Tests\TestCase;

final class ASTHelperTest extends TestCase
Expand Down Expand Up @@ -364,4 +366,69 @@ public function manipulateArgDefinition(

$this->assertSame($typeType->name->value, 'Int');
}

public function testDynamicallyAddedInputFieldManipulatorDirective(): void
{
$directive = new class() extends BaseDirective implements InputFieldManipulator {
public static function definition(): string
{
return /** @lang GraphQL */ 'directive @foo on INPUT_FIELD_DEFINITION';
}

public function manipulateInputFieldDefinition(
DocumentAST &$documentAST,
InputValueDefinitionNode &$inputDefinition,
InputObjectTypeDefinitionNode &$parentType,
): void {
$inputDefinition->type = Parser::namedType('Int');
}
};

$directiveLocator = $this->app->make(DirectiveLocator::class);
$directiveLocator->setResolved('foo', $directive::class);

$dynamicDirective = new class() extends BaseDirective implements InputFieldManipulator {
public static function definition(): string
{
return /** @lang GraphQL */ 'directive @dynamic on INPUT_FIELD_DEFINITION';
}

public function manipulateInputFieldDefinition(
DocumentAST &$documentAST,
InputValueDefinitionNode &$inputDefinition,
InputObjectTypeDefinitionNode &$parentType,
): void {
$directiveInstance = ASTHelper::addDirectiveToNode('@foo', $inputDefinition);

assert($directiveInstance instanceof InputFieldManipulator);

$directiveInstance->manipulateInputFieldDefinition($documentAST, $inputDefinition, $parentType);
}
};

$directiveLocator->setResolved('dynamic', $dynamicDirective::class);

$this->schema = /** @lang GraphQL */ '
input Input {
name: String @dynamic
}
type Query {
foo(name: Input): String
}
';
$astBuilder = $this->app->make(ASTBuilder::class);
$documentAST = $astBuilder->documentAST();

$inputType = $documentAST->types['Input'];
assert($inputType instanceof InputObjectTypeDefinitionNode);

$fieldType = $inputType->fields[0];
assert($fieldType instanceof InputValueDefinitionNode);

$typeType = $fieldType->type;
assert($typeType instanceof NamedTypeNode);

$this->assertSame($typeType->name->value, 'Int');
}
}

0 comments on commit 4426483

Please sign in to comment.