Skip to content

Commit

Permalink
Allow customizing query complexity calculation of @paginate with ar…
Browse files Browse the repository at this point in the history
…gument `complexityResolver` (#2372)
  • Loading branch information
spawnia authored Mar 29, 2023
1 parent 433ddef commit e7dc03a
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 27 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

## v6.4.0

### Added

- Allow customizing query complexity calculation of `@paginate` with argument `complexityResolver` https://github.com/nuwave/lighthouse/pull/2372

## v6.3.1

### Fixed
Expand Down
16 changes: 10 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,20 @@ up: ## Bring up the docker-compose stack
docker-compose up -d

.PHONY: fix
fix: rector php-cs-fixer ## Automatic code fixes
fix: rector php-cs-fixer prettier ## Automatic code fixes

.PHONY: rector
rector: up ## Automatic code fixes with Rector
${dcphp} vendor/bin/rector process

.PHONY: php-cs-fixer
php-cs-fixer: up ## Fix code style
php-cs-fixer: up ## Format code with php-cs-fixer
${dcphp} vendor/bin/php-cs-fixer fix

.PHONY: prettier
prettier: up ## Format code with prettier
${dcnode} yarn run prettify

.PHONY: stan
stan: up ## Runs static analysis
${dcphp} vendor/bin/phpstan
Expand All @@ -38,10 +46,6 @@ test: up ## Runs tests with PHPUnit
bench: up ## Run benchmarks
${dcphp} vendor/bin/phpbench run --report=aggregate

.PHONY: rector
rector: up ## Automatic code fixes with Rector
${dcphp} vendor/bin/rector process

vendor: up composer.json ## Install composer dependencies
${dcphp} composer update
${dcphp} composer validate --strict
Expand Down
5 changes: 3 additions & 2 deletions docs/4/subscriptions/client-implementations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ class PusherLink extends ApolloLink {
});

// Capture the super method
const prevSubscribe =
subscribeObservable.subscribe.bind(subscribeObservable);
const prevSubscribe = subscribeObservable.subscribe.bind(
subscribeObservable
);

// Override subscribe to return an `unsubscribe` object, see
// https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L182-L212
Expand Down
5 changes: 3 additions & 2 deletions docs/5/subscriptions/client-implementations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ class PusherLink extends ApolloLink {
});

// Capture the super method
const prevSubscribe =
subscribeObservable.subscribe.bind(subscribeObservable);
const prevSubscribe = subscribeObservable.subscribe.bind(
subscribeObservable
);

// Override subscribe to return an `unsubscribe` object, see
// https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L182-L212
Expand Down
5 changes: 3 additions & 2 deletions docs/6/subscriptions/client-implementations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ class PusherLink extends ApolloLink {
});

// Capture the super method
const prevSubscribe =
subscribeObservable.subscribe.bind(subscribeObservable);
const prevSubscribe = subscribeObservable.subscribe.bind(
subscribeObservable
);

// Override subscribe to return an `unsubscribe` object, see
// https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L182-L212
Expand Down
7 changes: 7 additions & 0 deletions docs/master/api-reference/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -2496,6 +2496,13 @@ directive @paginate(
Setting this to `null` means the count is unrestricted.
"""
maxCount: Int

"""
Reference a function to customize the complexity score calculation.
Consists of two parts: a class name and a method name, seperated by an `@` symbol.
If you pass only a class name, the method name defaults to `__invoke`.
"""
complexityResolver: String
) on FIELD_DEFINITION

"""
Expand Down
5 changes: 3 additions & 2 deletions docs/master/subscriptions/client-implementations.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ class PusherLink extends ApolloLink {
});

// Capture the super method
const prevSubscribe =
subscribeObservable.subscribe.bind(subscribeObservable);
const prevSubscribe = subscribeObservable.subscribe.bind(
subscribeObservable
);

// Override subscribe to return an `unsubscribe` object, see
// https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/client.ts#L182-L212
Expand Down
19 changes: 13 additions & 6 deletions src/Pagination/PaginateDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ public static function definition(): string
Setting this to `null` means the count is unrestricted.
"""
maxCount: Int
"""
Reference a function to customize the complexity score calculation.
Consists of two parts: a class name and a method name, seperated by an `@` symbol.
If you pass only a class name, the method name defaults to `__invoke`.
"""
complexityResolver: String
) on FIELD_DEFINITION
"""
Expand Down Expand Up @@ -222,15 +229,15 @@ protected function paginateMaxCount(): ?int

public function complexityResolver(FieldValue $fieldValue): callable
{
if ($this->directiveHasArgument('complexityResolver')) {
return $this->getResolverFromArgument('complexityResolver');
}

return static function (int $childrenComplexity, array $args): int {
/**
* @see PaginationManipulator::countArgument().
* @see \Nuwave\Lighthouse\Pagination\PaginationManipulator::countArgument()
*/
$first = $args['first'] ?? null;

$expectedNumberOfChildren = is_int($first)
? $first
: 1;
$expectedNumberOfChildren = $args['first'] ?? 1;

return
// Default complexity for this field itself
Expand Down
59 changes: 52 additions & 7 deletions tests/Unit/Pagination/PaginateDirectiveTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Utils\SchemaPrinter;
use GraphQL\Validator\Rules\QueryComplexity;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Illuminate\Pagination\LengthAwarePaginator;
use Nuwave\Lighthouse\Exceptions\DefinitionException;
use Nuwave\Lighthouse\Pagination\PaginationArgs;
Expand Down Expand Up @@ -526,7 +528,7 @@ public function testCountExplicitlyRequiredFromDirective(): void
}
type Query {
users: [User!] @paginate(defaultCount: null)
users: [User!]! @paginate(defaultCount: null)
}
';

Expand All @@ -552,7 +554,7 @@ public function testThrowsWhenPaginationWithNegativeCountIsRequested(): void
}
type Query {
users: [User!] @paginate
users: [User!]! @paginate
}
';

Expand All @@ -573,7 +575,7 @@ public function testDoesNotRequireModelWhenUsingBuilder(): void
{
$schema = $this->buildSchema(/** @lang GraphQL */ "
type Query {
users: [NotAnActualModelName!] @paginate(builder: \"{$this->qualifyTestResolver('testDoesNotRequireModelWhenUsingBuilder')}\")
users: [NotAnActualModelName!]! @paginate(builder: \"{$this->qualifyTestResolver('testDoesNotRequireModelWhenUsingBuilder')}\")
}
type NotAnActualModelName {
Expand All @@ -591,7 +593,7 @@ public function testThrowsIfBuilderIsNotPresent(): void

$this->buildSchema(/** @lang GraphQL */ '
type Query {
users: [Query!] @paginate(builder: "NonexistingClass@notFound")
users: [Query!]! @paginate(builder: "NonexistingClass@notFound")
}
');
}
Expand All @@ -600,8 +602,8 @@ public function testAllowsMultiplePaginatedFieldsOfTheSameModel(): void
{
$schema = $this->buildSchema(/** @lang GraphQL */ '
type Query {
users: [User!] @paginate
users2: [User!] @paginate
users: [User!]! @paginate
users2: [User!]! @paginate
}
type User {
Expand Down Expand Up @@ -660,7 +662,7 @@ public function testPaginatorResolver(): void
{
$this->buildSchema(/* @lang GraphQL */ "
type Query {
users: [User] @paginate(resolver: \"{$this->qualifyTestResolver('returnPaginatedDataInsteadOfBuilder')}\")
users: [User!]! @paginate(resolver: \"{$this->qualifyTestResolver('returnPaginatedDataInsteadOfBuilder')}\")
}
type User {
Expand Down Expand Up @@ -698,4 +700,47 @@ public function testThrowsIfResolverIsNotPresent(): void
}
');
}

public function testCustomizeQueryComplexityResolver(): void
{
$max = 42;
$this->setMaxQueryComplexity($max);

$this->buildSchema(/* @lang GraphQL */ "
type Query {
users(complexity: Int!): [User!]! @paginate(complexityResolver: \"{$this->qualifyTestResolver('complexityResolver')}\")
}
type User {
id: ID
}
");

$complexity = 123;
$this->graphQL(/* @lang GraphQL */ '
query ($complexity: Int!) {
users(first: 5, complexity: $complexity) {
data {
id
}
}
}
', [
'complexity' => $complexity,
])->assertGraphQLErrorMessage(QueryComplexity::maxQueryComplexityErrorMessage($max, $complexity));
}

/**
* @param array{complexity: int} $args
*/
public static function complexityResolver(int $childrenComplexity, array $args): int
{
return $args['complexity'];
}

protected function setMaxQueryComplexity(int $max): void
{
$config = $this->app->make(ConfigRepository::class);
$config->set('lighthouse.security.max_query_complexity', $max);
}
}

0 comments on commit e7dc03a

Please sign in to comment.