Skip to content

Commit 3f9cc91

Browse files
spawniabepsvpt
authored andcommitted
Generalize validation for mutually exclusive arguments (#2233)
1 parent 76e07f3 commit 3f9cc91

File tree

14 files changed

+105
-96
lines changed

14 files changed

+105
-96
lines changed

CHANGELOG.md

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

1010
## Unreleased
1111

12+
## v5.65.0
13+
14+
### Added
15+
16+
- Validate only one of any mutually exclusive directive arguments is defined https://github.com/nuwave/lighthouse/pull/2233
17+
1218
## v5.64.1
1319

1420
### Fixed

docs/master/api-reference/directives.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ directive @aggregate(
2121

2222
"""
2323
The relationship with the column to aggregate.
24-
Mutually exclusive with the `model` argument.
24+
Mutually exclusive with `model`.
2525
"""
2626
relation: String
2727

2828
"""
2929
The model with the column to aggregate.
30-
Mutually exclusive with the `relation` argument.
30+
Mutually exclusive with `relation`.
3131
"""
3232
model: String
3333

@@ -821,13 +821,13 @@ Returns the count of a given relationship or model.
821821
directive @count(
822822
"""
823823
The relationship to count.
824-
Mutually exclusive with the `model` argument.
824+
Mutually exclusive with `model`.
825825
"""
826826
relation: String
827827

828828
"""
829829
The model to count.
830-
Mutually exclusive with the `relation` argument.
830+
Mutually exclusive with `relation`.
831831
"""
832832
model: String
833833

@@ -2255,15 +2255,15 @@ directive @node(
22552255
Consists of two parts: a class name and a method name, seperated by an `@` symbol.
22562256
If you pass only a class name, the method name defaults to `__invoke`.
22572257
2258-
Mutually exclusive with the `model` argument.
2258+
Mutually exclusive with `model`.
22592259
"""
22602260
resolver: String
22612261

22622262
"""
22632263
Specify the class name of the model to use.
22642264
This is only needed when the default model detection does not work.
22652265
2266-
Mutually exclusive with the `model` argument.
2266+
Mutually exclusive with `resolver`.
22672267
"""
22682268
model: String
22692269
) on OBJECT
@@ -2334,15 +2334,15 @@ directive @orderBy(
23342334
"""
23352335
Restrict the allowed column names to a well-defined list.
23362336
This improves introspection capabilities and security.
2337-
Mutually exclusive with the `columnsEnum` argument.
2337+
Mutually exclusive with `columnsEnum`.
23382338
Only used when the directive is added on an argument.
23392339
"""
23402340
columns: [String!]
23412341

23422342
"""
23432343
Use an existing enumeration type to restrict the allowed columns to a predefined list.
2344-
This allowes you to re-use the same enum for multiple fields.
2345-
Mutually exclusive with the `columns` argument.
2344+
This allows you to re-use the same enum for multiple fields.
2345+
Mutually exclusive with `columns`.
23462346
Only used when the directive is added on an argument.
23472347
"""
23482348
columnsEnum: String
@@ -2386,21 +2386,21 @@ Options for the `relations` argument on `@orderBy`.
23862386
"""
23872387
input OrderByRelation {
23882388
"""
2389-
TODO: description
2389+
Name of the relation.
23902390
"""
23912391
relation: String!
23922392

23932393
"""
23942394
Restrict the allowed column names to a well-defined list.
23952395
This improves introspection capabilities and security.
2396-
Mutually exclusive with the `columnsEnum` argument.
2396+
Mutually exclusive with `columnsEnum`.
23972397
"""
23982398
columns: [String!]
23992399

24002400
"""
24012401
Use an existing enumeration type to restrict the allowed columns to a predefined list.
2402-
This allowes you to re-use the same enum for multiple fields.
2403-
Mutually exclusive with the `columns` argument.
2402+
This allows you to re-use the same enum for multiple fields.
2403+
Mutually exclusive with `columns`.
24042404
"""
24052405
columnsEnum: String
24062406
}

docs/master/eloquent/complex-where-conditions.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ directive @whereConditions(
3636
"""
3737
Restrict the allowed column names to a well-defined list.
3838
This improves introspection capabilities and security.
39-
Mutually exclusive with the `columnsEnum` argument.
39+
Mutually exclusive with `columnsEnum`.
4040
"""
4141
columns: [String!]
4242

4343
"""
4444
Use an existing enumeration type to restrict the allowed columns to a predefined list.
45-
This allowes you to re-use the same enum for multiple fields.
46-
Mutually exclusive with the `columns` argument.
45+
This allows you to re-use the same enum for multiple fields.
46+
Mutually exclusive with `columns`.
4747
"""
4848
columnsEnum: String
4949

@@ -246,23 +246,23 @@ directive @whereHasConditions(
246246
"""
247247
Restrict the allowed column names to a well-defined list.
248248
This improves introspection capabilities and security.
249-
Mutually exclusive with the `columnsEnum` argument.
249+
Mutually exclusive with `columnsEnum`.
250250
"""
251251
columns: [String!]
252252

253253
"""
254254
Use an existing enumeration type to restrict the allowed columns to a predefined list.
255-
This allowes you to re-use the same enum for multiple fields.
256-
Mutually exclusive with the `columns` argument.
255+
This allows you to re-use the same enum for multiple fields.
256+
Mutually exclusive with `columns`.
257257
"""
258258
columnsEnum: String
259259

260260
"""
261261
Reference a method that applies the client given conditions to the query builder.
262262
263263
Expected signature: `(
264-
\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder,
265-
array<string, mixed> $whereConditions
264+
\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $builder,
265+
array<string, mixed> $whereConditions
266266
): void`
267267
268268
Consists of two parts: a class name and a method name, separated by an `@` symbol.

src/Auth/CanDirective.php

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use Illuminate\Database\Eloquent\ModelNotFoundException;
1414
use Illuminate\Support\Arr;
1515
use Nuwave\Lighthouse\Exceptions\AuthorizationException;
16-
use Nuwave\Lighthouse\Exceptions\DefinitionException;
1716
use Nuwave\Lighthouse\Execution\Arguments\ArgumentSet;
1817
use Nuwave\Lighthouse\Execution\Resolved;
1918
use Nuwave\Lighthouse\Schema\AST\DocumentAST;
@@ -281,19 +280,6 @@ protected function buildCheckArguments(array $args): array
281280

282281
public function manipulateFieldDefinition(DocumentAST &$documentAST, FieldDefinitionNode &$fieldDefinition, ObjectTypeDefinitionNode &$parentType)
283282
{
284-
$mutuallyExclusive = [
285-
$this->directiveHasArgument('resolve'),
286-
$this->directiveHasArgument('query'),
287-
$this->directiveHasArgument('find'),
288-
];
289-
290-
if (count(array_filter($mutuallyExclusive)) > 1) {
291-
throw self::multipleMutuallyExclusiveArguments();
292-
}
293-
}
294-
295-
public static function multipleMutuallyExclusiveArguments(): DefinitionException
296-
{
297-
return new DefinitionException('The arguments `resolve`, `query` and `find` are mutually exclusive in the `@can` directive.');
283+
$this->validateMutuallyExclusiveArguments(['resolve', 'query', 'find']);
298284
}
299285
}

src/GlobalId/NodeDirective.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Nuwave\Lighthouse\GlobalId;
44

5+
use GraphQL\Language\AST\NamedTypeNode;
56
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
67
use GraphQL\Language\AST\TypeDefinitionNode;
78
use GraphQL\Language\Parser;
@@ -41,15 +42,15 @@ public static function definition(): string
4142
Consists of two parts: a class name and a method name, seperated by an `@` symbol.
4243
If you pass only a class name, the method name defaults to `__invoke`.
4344
44-
Mutually exclusive with the `model` argument.
45+
Mutually exclusive with `model`.
4546
"""
4647
resolver: String
4748
4849
"""
4950
Specify the class name of the model to use.
5051
This is only needed when the default model detection does not work.
5152
52-
Mutually exclusive with the `model` argument.
53+
Mutually exclusive with `resolver`.
5354
"""
5455
model: String
5556
) on OBJECT
@@ -84,14 +85,16 @@ public function handleNode(TypeValue $value, \Closure $next): Type
8485
*/
8586
public function manipulateTypeDefinition(DocumentAST &$documentAST, TypeDefinitionNode &$typeDefinition): void
8687
{
88+
$this->validateMutuallyExclusiveArguments(['model', 'resolver']);
89+
8790
if (! $typeDefinition instanceof ObjectTypeDefinitionNode) {
8891
throw new DefinitionException(
8992
"The {$this->name()} directive must only be used on object type definitions, not on {$typeDefinition->kind} {$typeDefinition->name->value}."
9093
);
9194
}
9295

93-
/** @var \GraphQL\Language\AST\NamedTypeNode $namedTypeNode */
9496
$namedTypeNode = Parser::parseType(GlobalIdServiceProvider::NODE, ['noLocation' => true]);
97+
assert($namedTypeNode instanceof NamedTypeNode);
9598
$typeDefinition->interfaces[] = $namedTypeNode;
9699

97100
$globalIdFieldName = config('lighthouse.global_id_field');

src/OrderBy/OrderByDirective.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ public static function definition(): string
3434
"""
3535
Restrict the allowed column names to a well-defined list.
3636
This improves introspection capabilities and security.
37-
Mutually exclusive with the `columnsEnum` argument.
37+
Mutually exclusive with `columnsEnum`.
3838
Only used when the directive is added on an argument.
3939
"""
4040
columns: [String!]
4141
4242
"""
4343
Use an existing enumeration type to restrict the allowed columns to a predefined list.
44-
This allowes you to re-use the same enum for multiple fields.
45-
Mutually exclusive with the `columns` argument.
44+
This allows you to re-use the same enum for multiple fields.
45+
Mutually exclusive with `columns`.
4646
Only used when the directive is added on an argument.
4747
"""
4848
columnsEnum: String
@@ -86,21 +86,21 @@ enum OrderByDirection {
8686
"""
8787
input OrderByRelation {
8888
"""
89-
TODO: description
89+
Name of the relation.
9090
"""
9191
relation: String!
9292
9393
"""
9494
Restrict the allowed column names to a well-defined list.
9595
This improves introspection capabilities and security.
96-
Mutually exclusive with the `columnsEnum` argument.
96+
Mutually exclusive with `columnsEnum`.
9797
"""
9898
columns: [String!]
9999
100100
"""
101101
Use an existing enumeration type to restrict the allowed columns to a predefined list.
102-
This allowes you to re-use the same enum for multiple fields.
103-
Mutually exclusive with the `columns` argument.
102+
This allows you to re-use the same enum for multiple fields.
103+
Mutually exclusive with `columns`.
104104
"""
105105
columnsEnum: String
106106
}
@@ -153,6 +153,8 @@ public function manipulateArgDefinition(
153153
FieldDefinitionNode &$parentField,
154154
ObjectTypeDefinitionNode &$parentType
155155
): void {
156+
$this->validateMutuallyExclusiveArguments(['columns', 'columnsEnum']);
157+
156158
if (! $this->hasAllowedColumns() && ! $this->directiveHasArgument('relations')) {
157159
$argDefinition->type = Parser::typeReference('[' . OrderByServiceProvider::DEFAULT_ORDER_BY_CLAUSE . '!]');
158160

src/Schema/Directives/AggregateDirective.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@
22

33
namespace Nuwave\Lighthouse\Schema\Directives;
44

5+
use GraphQL\Language\AST\FieldDefinitionNode;
6+
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
57
use GraphQL\Type\Definition\ResolveInfo;
68
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
79
use Illuminate\Database\Eloquent\Model;
810
use Nuwave\Lighthouse\Exceptions\DefinitionException;
911
use Nuwave\Lighthouse\Execution\BatchLoader\BatchLoaderRegistry;
1012
use Nuwave\Lighthouse\Execution\BatchLoader\RelationBatchLoader;
1113
use Nuwave\Lighthouse\Execution\ModelsLoader\AggregateModelsLoader;
14+
use Nuwave\Lighthouse\Schema\AST\DocumentAST;
1215
use Nuwave\Lighthouse\Schema\Values\FieldValue;
16+
use Nuwave\Lighthouse\Support\Contracts\FieldManipulator;
1317
use Nuwave\Lighthouse\Support\Contracts\FieldResolver;
1418
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
1519

16-
class AggregateDirective extends BaseDirective implements FieldResolver
20+
class AggregateDirective extends BaseDirective implements FieldResolver, FieldManipulator
1721
{
1822
use RelationDirectiveHelpers;
1923

@@ -38,13 +42,13 @@ function: AggregateFunction!
3842
3943
"""
4044
The relationship with the column to aggregate.
41-
Mutually exclusive with the `model` argument.
45+
Mutually exclusive with `model`.
4246
"""
4347
relation: String
4448
4549
"""
4650
The model with the column to aggregate.
47-
Mutually exclusive with the `relation` argument.
51+
Mutually exclusive with `relation`.
4852
"""
4953
model: String
5054
@@ -141,4 +145,9 @@ protected function column(): string
141145
{
142146
return $this->directiveArgValue('column');
143147
}
148+
149+
public function manipulateFieldDefinition(DocumentAST &$documentAST, FieldDefinitionNode &$fieldDefinition, ObjectTypeDefinitionNode &$parentType)
150+
{
151+
$this->validateMutuallyExclusiveArguments(['model', 'relation']);
152+
}
144153
}

src/Schema/Directives/BaseDirective.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,4 +266,20 @@ static function (string $classCandidate): bool {
266266

267267
return $modelClass;
268268
}
269+
270+
/**
271+
* Validate at most one of the given mutually exclusive arguments is used.
272+
*
273+
* @param array<string> $names
274+
*/
275+
protected function validateMutuallyExclusiveArguments(array $names): void
276+
{
277+
$given = array_filter($names, [$this, 'directiveHasArgument']);
278+
279+
if (count($given) > 1) {
280+
$namesString = implode(', ', $names);
281+
$givenString = implode(', ', $given);
282+
throw new DefinitionException("The arguments [{$namesString}] for @{$this->name()} are mutually exclusive, found [{$givenString}] on {$this->nodeName()}.");
283+
}
284+
}
269285
}

0 commit comments

Comments
 (0)