From 26eea8b1d3bb7adf117cadca361528566b7f7334 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Tue, 5 Dec 2023 17:50:01 +0100 Subject: [PATCH 01/14] Prefer assert over @var --- tests/Integration/Cache/CacheDirectiveTest.php | 3 +-- .../Integration/OrderBy/OrderByDirectiveDBTest.php | 14 ++++++++------ .../Schema/Directives/HasOneDirectiveTest.php | 2 ++ tests/Unit/Schema/AST/ASTBuilderTest.php | 3 +-- .../Unit/Schema/Factories/DirectiveFactoryTest.php | 3 +-- tests/Unit/Schema/ResolverProviderTest.php | 3 +-- tests/Unit/Schema/TypeRegistryTest.php | 3 +-- tests/Unit/Subscriptions/BroadcastManagerTest.php | 3 +-- .../Iterators/AuthenticatingSyncIteratorTest.php | 2 +- .../Storage/CacheStorageManagerTest.php | 1 - tests/Utils/Mutations/Foo.php | 6 +----- tests/Utils/Queries/Foo.php | 6 +----- tests/Utils/QueriesSecondary/Baz.php | 6 +----- 13 files changed, 20 insertions(+), 35 deletions(-) diff --git a/tests/Integration/Cache/CacheDirectiveTest.php b/tests/Integration/Cache/CacheDirectiveTest.php index 4b0b7a1133..42be24d164 100644 --- a/tests/Integration/Cache/CacheDirectiveTest.php +++ b/tests/Integration/Cache/CacheDirectiveTest.php @@ -14,8 +14,7 @@ final class CacheDirectiveTest extends DBTestCase { - /** @var \Illuminate\Contracts\Cache\Repository */ - protected $cache; + protected CacheRepository $cache; protected function getEnvironmentSetUp($app): void { diff --git a/tests/Integration/OrderBy/OrderByDirectiveDBTest.php b/tests/Integration/OrderBy/OrderByDirectiveDBTest.php index 7591fdb6a7..1b2aa4464d 100644 --- a/tests/Integration/OrderBy/OrderByDirectiveDBTest.php +++ b/tests/Integration/OrderBy/OrderByDirectiveDBTest.php @@ -281,10 +281,11 @@ public function testOrderByRelationCount(): void } '; - /** @var \Tests\Utils\Models\User $userA */ $userA = factory(User::class)->create(); - /** @var \Tests\Utils\Models\User $userB */ + assert($userA instanceof User); + $userB = factory(User::class)->create(); + assert($userB instanceof User); $userA->tasks()->saveMany( factory(Task::class, 1)->create(), @@ -369,18 +370,19 @@ enum UserColumn { } '; - /** @var \Tests\Utils\Models\User $userA */ $userA = factory(User::class)->create(); - /** @var \Tests\Utils\Models\User $userB */ + assert($userA instanceof User); + $userB = factory(User::class)->create(); + assert($userB instanceof User); - /** @var \Tests\Utils\Models\Task $taskA1 */ $taskA1 = factory(Task::class)->make(); + assert($taskA1 instanceof Task); $taskA1->difficulty = 1; $userA->tasks()->save($taskA1); - /** @var \Tests\Utils\Models\Task $taskB1 */ $taskB1 = factory(Task::class)->make(); + assert($taskB1 instanceof Task); $taskB1->difficulty = 2; $userB->tasks()->save($taskB1); diff --git a/tests/Integration/Schema/Directives/HasOneDirectiveTest.php b/tests/Integration/Schema/Directives/HasOneDirectiveTest.php index 2c3865f9b0..f5363a38c4 100644 --- a/tests/Integration/Schema/Directives/HasOneDirectiveTest.php +++ b/tests/Integration/Schema/Directives/HasOneDirectiveTest.php @@ -12,8 +12,10 @@ public function testQueryHasOneRelationship(): void { // Task with no post factory(Task::class)->create(); + // Creates a task and assigns it to this post $post = factory(Post::class)->create(); + assert($post instanceof Post); $this->schema = /** @lang GraphQL */ ' type Post { diff --git a/tests/Unit/Schema/AST/ASTBuilderTest.php b/tests/Unit/Schema/AST/ASTBuilderTest.php index 0341eeb6f9..b897d9b337 100644 --- a/tests/Unit/Schema/AST/ASTBuilderTest.php +++ b/tests/Unit/Schema/AST/ASTBuilderTest.php @@ -17,8 +17,7 @@ final class ASTBuilderTest extends TestCase { - /** @var \Nuwave\Lighthouse\Schema\AST\ASTBuilder */ - protected $astBuilder; + protected ASTBuilder $astBuilder; protected function setUp(): void { diff --git a/tests/Unit/Schema/Factories/DirectiveFactoryTest.php b/tests/Unit/Schema/Factories/DirectiveFactoryTest.php index 256a5ae805..23fd75e111 100644 --- a/tests/Unit/Schema/Factories/DirectiveFactoryTest.php +++ b/tests/Unit/Schema/Factories/DirectiveFactoryTest.php @@ -12,8 +12,7 @@ final class DirectiveFactoryTest extends TestCase { - /** @var \Nuwave\Lighthouse\Schema\Factories\DirectiveFactory */ - protected $directiveFactory; + protected DirectiveFactory $directiveFactory; protected function setUp(): void { diff --git a/tests/Unit/Schema/ResolverProviderTest.php b/tests/Unit/Schema/ResolverProviderTest.php index a42dae7e2d..49f5291e91 100644 --- a/tests/Unit/Schema/ResolverProviderTest.php +++ b/tests/Unit/Schema/ResolverProviderTest.php @@ -12,8 +12,7 @@ final class ResolverProviderTest extends TestCase { - /** @var \Nuwave\Lighthouse\Schema\ResolverProvider */ - protected $resolverProvider; + protected ResolverProvider $resolverProvider; protected function setUp(): void { diff --git a/tests/Unit/Schema/TypeRegistryTest.php b/tests/Unit/Schema/TypeRegistryTest.php index 250341ff36..0e7ce41960 100644 --- a/tests/Unit/Schema/TypeRegistryTest.php +++ b/tests/Unit/Schema/TypeRegistryTest.php @@ -17,8 +17,7 @@ final class TypeRegistryTest extends TestCase { - /** @var \Nuwave\Lighthouse\Schema\TypeRegistry */ - protected $typeRegistry; + protected TypeRegistry $typeRegistry; protected function setUp(): void { diff --git a/tests/Unit/Subscriptions/BroadcastManagerTest.php b/tests/Unit/Subscriptions/BroadcastManagerTest.php index e97152516a..5e21db5449 100644 --- a/tests/Unit/Subscriptions/BroadcastManagerTest.php +++ b/tests/Unit/Subscriptions/BroadcastManagerTest.php @@ -17,8 +17,7 @@ final class BroadcastManagerTest extends TestCase { use EnablesSubscriptionServiceProvider; - /** @var \Nuwave\Lighthouse\Subscriptions\BroadcastManager */ - protected $broadcastManager; + protected BroadcastManager $broadcastManager; protected function setUp(): void { diff --git a/tests/Unit/Subscriptions/Iterators/AuthenticatingSyncIteratorTest.php b/tests/Unit/Subscriptions/Iterators/AuthenticatingSyncIteratorTest.php index d9d045bccb..c9d7600dcf 100644 --- a/tests/Unit/Subscriptions/Iterators/AuthenticatingSyncIteratorTest.php +++ b/tests/Unit/Subscriptions/Iterators/AuthenticatingSyncIteratorTest.php @@ -53,8 +53,8 @@ public function testSetsAndResetsGuardContextAfterEachIteration(): void }); }); - /** @var \Illuminate\Auth\AuthManager $authManager */ $authManager = $this->app->make(AuthManager::class); + assert($authManager instanceof AuthManager); $authManager->extend(SubscriptionGuard::GUARD_NAME, static fn (): SubscriptionGuard => $guard); diff --git a/tests/Unit/Subscriptions/Storage/CacheStorageManagerTest.php b/tests/Unit/Subscriptions/Storage/CacheStorageManagerTest.php index 016f2e2d22..735c285183 100644 --- a/tests/Unit/Subscriptions/Storage/CacheStorageManagerTest.php +++ b/tests/Unit/Subscriptions/Storage/CacheStorageManagerTest.php @@ -86,7 +86,6 @@ public function testDeleteSubscribersInCache(): void protected function assertSubscriberIsSame(Subscriber $expected, ?Subscriber $actual): void { $this->assertNotNull($actual); - /** @var \Nuwave\Lighthouse\Subscriptions\Subscriber $actual */ $this->assertSame( AST::toArray($expected->query), AST::toArray($actual->query), diff --git a/tests/Utils/Mutations/Foo.php b/tests/Utils/Mutations/Foo.php index 077f95b4e4..92f716a404 100644 --- a/tests/Utils/Mutations/Foo.php +++ b/tests/Utils/Mutations/Foo.php @@ -8,11 +8,7 @@ */ final class Foo { - /** - * The answer to life, the universe and everything. - * - * @var int - */ + /** The answer to life, the universe and everything. */ public const THE_ANSWER = 42; /** Return a value for the field. */ diff --git a/tests/Utils/Queries/Foo.php b/tests/Utils/Queries/Foo.php index 95dfe79ab1..1400d6ff41 100644 --- a/tests/Utils/Queries/Foo.php +++ b/tests/Utils/Queries/Foo.php @@ -8,11 +8,7 @@ */ final class Foo { - /** - * The answer to life, the universe and everything. - * - * @var int - */ + /** The answer to life, the universe and everything. */ public const THE_ANSWER = 42; /** Return a value for the field. */ diff --git a/tests/Utils/QueriesSecondary/Baz.php b/tests/Utils/QueriesSecondary/Baz.php index 54196131ea..1fc5bf0ed5 100644 --- a/tests/Utils/QueriesSecondary/Baz.php +++ b/tests/Utils/QueriesSecondary/Baz.php @@ -4,11 +4,7 @@ final class Baz { - /** - * The answer to life, the universe and everything. - * - * @var int - */ + /** The answer to life, the universe and everything. */ public const THE_ANSWER = 42; /** Return a value for the field. */ From 90950d1aabd2b0846e868a4ec70e460073679f1d Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 6 Dec 2023 10:50:07 +0100 Subject: [PATCH 02/14] Add `@namespaced` directive for namespacing by seperation of concerns (#2478) Co-authored-by: Will G --- CHANGELOG.md | 4 + docs/6/api-reference/directives.md | 33 +++++ docs/master/api-reference/directives.md | 33 +++++ src/Schema/Directives/NamespacedDirective.php | 25 ++++ .../Directives/NamespacedDirectiveTest.php | 135 ++++++++++++++++++ 5 files changed, 230 insertions(+) create mode 100644 src/Schema/Directives/NamespacedDirective.php create mode 100644 tests/Integration/Schema/Directives/NamespacedDirectiveTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 048c69ce6f..6278d8cd5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +### Added + +- Add `@namespaced` directive for namespacing by separation of concerns https://github.com/nuwave/lighthouse/pull/2469 + ## v6.24.0 ### Added diff --git a/docs/6/api-reference/directives.md b/docs/6/api-reference/directives.md index ee1fd019fb..e79a8e1db4 100644 --- a/docs/6/api-reference/directives.md +++ b/docs/6/api-reference/directives.md @@ -2279,6 +2279,39 @@ extend type Query @namespace(field: "App\\Blog") { A [@namespace](#namespace) directive defined on a field directive wins in case of a conflict. +## @namespaced + +```graphql +""" +Provides a no-op field resolver that allows nesting of queries and mutations. +Useful to implement [namespacing by separation of concerns](https://www.apollographql.com/docs/technotes/TN0012-namespacing-by-separation-of-concern). +""" +directive @namespaced on FIELD_DEFINITION +``` + +The following example shows how one can namespace queries and mutations. + +```graphql +type Query { + post: PostQueries! @namespaced +} + +type PostQueries { + find(id: ID! @whereKey): Post @find + list(title: String @where(operator: "like")): [Post!]! @paginate +} + +type Mutation { + post: PostMutations! @namespaced +} + +type PostMutations { + create(input: PostCreateInput! @spread): Post! @create + update(input: PostUpdateInput! @spread): Post! @update + delete(id: ID! @whereKey): Post! @delete +} +``` + ## @neq ```graphql diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index ee1fd019fb..e79a8e1db4 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -2279,6 +2279,39 @@ extend type Query @namespace(field: "App\\Blog") { A [@namespace](#namespace) directive defined on a field directive wins in case of a conflict. +## @namespaced + +```graphql +""" +Provides a no-op field resolver that allows nesting of queries and mutations. +Useful to implement [namespacing by separation of concerns](https://www.apollographql.com/docs/technotes/TN0012-namespacing-by-separation-of-concern). +""" +directive @namespaced on FIELD_DEFINITION +``` + +The following example shows how one can namespace queries and mutations. + +```graphql +type Query { + post: PostQueries! @namespaced +} + +type PostQueries { + find(id: ID! @whereKey): Post @find + list(title: String @where(operator: "like")): [Post!]! @paginate +} + +type Mutation { + post: PostMutations! @namespaced +} + +type PostMutations { + create(input: PostCreateInput! @spread): Post! @create + update(input: PostUpdateInput! @spread): Post! @update + delete(id: ID! @whereKey): Post! @delete +} +``` + ## @neq ```graphql diff --git a/src/Schema/Directives/NamespacedDirective.php b/src/Schema/Directives/NamespacedDirective.php new file mode 100644 index 0000000000..73a6aaff16 --- /dev/null +++ b/src/Schema/Directives/NamespacedDirective.php @@ -0,0 +1,25 @@ + true; + } +} diff --git a/tests/Integration/Schema/Directives/NamespacedDirectiveTest.php b/tests/Integration/Schema/Directives/NamespacedDirectiveTest.php new file mode 100644 index 0000000000..904beddb34 --- /dev/null +++ b/tests/Integration/Schema/Directives/NamespacedDirectiveTest.php @@ -0,0 +1,135 @@ +schema .= /** @lang GraphQL */ ' + type Query { + user: UserQueries! @namespaced + } + + type UserQueries { + find(id: ID! @eq): User @find + list: [User!]! @all + } + + type Mutation { + user: UserMutations! @namespaced + } + + type UserMutations { + create(name: String!): User @create + update(id: ID!, name: String): User @update + delete(id: ID! @whereKey): User @update + } + + type User { + id: ID! + name: String! + } + '; + + $name = 'foo'; + $createUserResponse = $this->graphQL(/** @lang GraphQL */ ' + mutation ($name: String!) { + user { + create(name: $name) { + id + name + } + } + } + ', [ + 'name' => $name, + ]); + $createUserResponse->assertJson([ + 'data' => [ + 'user' => [ + 'create' => [ + 'name' => $name, + ], + ], + ], + ]); + $userID = $createUserResponse->json('data.user.create.id'); + + $this->graphQL(/** @lang GraphQL */ ' + query ($id: ID!) { + user { + find(id: $id) { + id + } + list { + id + } + } + } + ', [ + 'id' => $userID, + ])->assertExactJson([ + 'data' => [ + 'user' => [ + 'find' => [ + 'id' => $userID, + ], + 'list' => [ + [ + 'id' => $userID, + ], + ], + ], + ], + ]); + + $newName = 'bar'; + $this->graphQL(/** @lang GraphQL */ ' + mutation ($id: ID!, $name: String) { + user { + update(id: $id, name: $name) { + id + name + } + } + } + ', [ + 'id' => $userID, + 'name' => $newName, + ])->assertExactJson([ + 'data' => [ + 'user' => [ + 'update' => [ + 'id' => $userID, + 'name' => $newName, + ], + ], + ], + ]); + + $this->graphQL(/** @lang GraphQL */ ' + mutation ($id: ID!) { + user { + delete(id: $id) { + id + name + } + } + } + ', [ + 'id' => $userID, + ])->assertExactJson([ + 'data' => [ + 'user' => [ + 'delete' => [ + 'id' => $userID, + 'name' => $newName, + ], + ], + ], + ]); + } +} From 6b0fddebcdb575ce08c5dd5ac884afa835b9a29d Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Wed, 6 Dec 2023 10:50:39 +0100 Subject: [PATCH 03/14] v6.25.0 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6278d8cd5d..8d58caf657 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +## v6.25.0 + ### Added - Add `@namespaced` directive for namespacing by separation of concerns https://github.com/nuwave/lighthouse/pull/2469 From 3806fb6b2cb1287c8a758e31e437d197a5d6437e Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Thu, 7 Dec 2023 09:07:29 +0100 Subject: [PATCH 04/14] Support federated tracing --- .editorconfig | 5 +- .github/workflows/proto.yml | 40 + .github/workflows/update-reports-proto.yml | 28 + .gitignore | 1 + CONTRIBUTING.md | 7 + Makefile | 16 + buf.gen.yaml | 7 + composer.json | 5 +- docs/master/federation/getting-started.md | 16 +- docs/master/performance/tracing.md | 15 +- php.dockerfile | 1 + phpstan.neon | 1 + rector.php | 1 + src/Events/BuildExtensionsResponse.php | 10 +- src/GraphQL.php | 2 +- src/Tracing/ApolloTracing/ApolloTracing.php | 76 ++ .../FederatedTracing/FederatedTracing.php | 194 ++++ .../Proto/ContextualizedQueryLatencyStats.php | 105 ++ .../Proto/ContextualizedStats.php | 147 +++ .../Proto/ContextualizedTypeStats.php | 95 ++ .../FederatedTracing/Proto/FieldStat.php | 335 ++++++ .../Proto/Metadata/Reports.php | Bin 0 -> 8855 bytes .../FederatedTracing/Proto/PathErrorStats.php | 114 ++ .../Proto/QueryLatencyStats.php | 509 +++++++++ .../FederatedTracing/Proto/QueryMetadata.php | 141 +++ .../Proto/ReferencedFieldsForType.php | 103 ++ src/Tracing/FederatedTracing/Proto/Report.php | 323 ++++++ .../Proto/Report/OperationCountByType.php | 117 +++ .../FederatedTracing/Proto/ReportHeader.php | 309 ++++++ .../FederatedTracing/Proto/StatsContext.php | 146 +++ src/Tracing/FederatedTracing/Proto/Trace.php | 981 ++++++++++++++++++ .../Proto/Trace/CachePolicy.php | 97 ++ .../Proto/Trace/CachePolicy/Scope.php | 59 ++ .../FederatedTracing/Proto/Trace/Details.php | 126 +++ .../FederatedTracing/Proto/Trace/Error.php | 155 +++ .../FederatedTracing/Proto/Trace/HTTP.php | 155 +++ .../Proto/Trace/HTTP/Method.php | 75 ++ .../Proto/Trace/HTTP/Values.php | 59 ++ .../FederatedTracing/Proto/Trace/Location.php | 88 ++ .../FederatedTracing/Proto/Trace/Node.php | 384 +++++++ .../Proto/Trace/QueryPlanNode.php | 229 ++++ .../Trace/QueryPlanNode/ConditionNode.php | 137 +++ .../Proto/Trace/QueryPlanNode/DeferNode.php | 100 ++ .../Trace/QueryPlanNode/DeferNodePrimary.php | 69 ++ .../Trace/QueryPlanNode/DeferredNode.php | 156 +++ .../QueryPlanNode/DeferredNodeDepends.php | 88 ++ .../Proto/Trace/QueryPlanNode/FetchNode.php | 292 ++++++ .../Proto/Trace/QueryPlanNode/FlattenNode.php | 101 ++ .../Trace/QueryPlanNode/ParallelNode.php | 61 ++ .../QueryPlanNode/ResponsePathElement.php | 100 ++ .../Trace/QueryPlanNode/SequenceNode.php | 61 ++ .../FederatedTracing/Proto/TracesAndStats.php | 247 +++++ .../FederatedTracing/Proto/TypeStat.php | 65 ++ src/Tracing/FederatedTracing/reports.proto | 530 ++++++++++ src/Tracing/Tracing.php | 94 +- src/Tracing/TracingDirective.php | 4 +- src/Tracing/TracingServiceProvider.php | 16 +- src/Tracing/TracingUtilities.php | 45 + src/lighthouse.php | 23 + ...est.php => ApolloTracingExtensionTest.php} | 4 +- .../Tracing/FederatedTracingExtensionTest.php | 206 ++++ 61 files changed, 7574 insertions(+), 102 deletions(-) create mode 100644 .github/workflows/proto.yml create mode 100644 .github/workflows/update-reports-proto.yml create mode 100644 buf.gen.yaml create mode 100644 src/Tracing/ApolloTracing/ApolloTracing.php create mode 100644 src/Tracing/FederatedTracing/FederatedTracing.php create mode 100644 src/Tracing/FederatedTracing/Proto/ContextualizedQueryLatencyStats.php create mode 100644 src/Tracing/FederatedTracing/Proto/ContextualizedStats.php create mode 100644 src/Tracing/FederatedTracing/Proto/ContextualizedTypeStats.php create mode 100644 src/Tracing/FederatedTracing/Proto/FieldStat.php create mode 100644 src/Tracing/FederatedTracing/Proto/Metadata/Reports.php create mode 100644 src/Tracing/FederatedTracing/Proto/PathErrorStats.php create mode 100644 src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php create mode 100644 src/Tracing/FederatedTracing/Proto/QueryMetadata.php create mode 100644 src/Tracing/FederatedTracing/Proto/ReferencedFieldsForType.php create mode 100644 src/Tracing/FederatedTracing/Proto/Report.php create mode 100644 src/Tracing/FederatedTracing/Proto/Report/OperationCountByType.php create mode 100644 src/Tracing/FederatedTracing/Proto/ReportHeader.php create mode 100644 src/Tracing/FederatedTracing/Proto/StatsContext.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/CachePolicy.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/CachePolicy/Scope.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/Details.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/Error.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/HTTP.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/HTTP/Method.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/HTTP/Values.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/Location.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/Node.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ConditionNode.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferNode.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferNodePrimary.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferredNode.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferredNodeDepends.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FlattenNode.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ParallelNode.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ResponsePathElement.php create mode 100644 src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/SequenceNode.php create mode 100644 src/Tracing/FederatedTracing/Proto/TracesAndStats.php create mode 100644 src/Tracing/FederatedTracing/Proto/TypeStat.php create mode 100644 src/Tracing/FederatedTracing/reports.proto create mode 100644 src/Tracing/TracingUtilities.php rename tests/Integration/Tracing/{TracingExtensionTest.php => ApolloTracingExtensionTest.php} (96%) create mode 100644 tests/Integration/Tracing/FederatedTracingExtensionTest.php diff --git a/.editorconfig b/.editorconfig index 4e94d325f9..00334c76a9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,5 +17,8 @@ trim_trailing_whitespace = false [*.neon] indent_size = 2 -[*.yml] +[{*.yml, *.yaml}] +indent_size = 2 + +[*.proto] indent_size = 2 diff --git a/.github/workflows/proto.yml b/.github/workflows/proto.yml new file mode 100644 index 0000000000..31e1f7d5cd --- /dev/null +++ b/.github/workflows/proto.yml @@ -0,0 +1,40 @@ +name: "Build protobuf" +on: + push: + branches: + - '*' + paths: + - '**.proto' + - 'buf.gen.yaml' + +jobs: + proto: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - uses: shivammathur/setup-php@v2 + with: + coverage: none + extensions: mbstring + php-version: 8.2 + + - run: composer install --no-interaction --no-progress --no-suggest + + - uses: bufbuild/buf-setup-action@v1 + + - run: | + buf generate + rm -rf src/Tracing/FederatedTracing/Proto + mv proto-tmp/Nuwave/Lighthouse/Tracing/FederatedTracing/Proto src/Tracing/FederatedTracing/Proto + rm -rf proto-tmp + + - run: vendor/bin/php-cs-fixer fix src/Tracing/FederatedTracing/Proto + + - run: git pull + + - uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: Apply proto changes diff --git a/.github/workflows/update-reports-proto.yml b/.github/workflows/update-reports-proto.yml new file mode 100644 index 0000000000..a5e84deedf --- /dev/null +++ b/.github/workflows/update-reports-proto.yml @@ -0,0 +1,28 @@ +name: "Update reports.proto" +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * 0" + +jobs: + proto: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - run: curl -sSfo src/Tracing/FederatedTracing/reports.proto https://usage-reporting.api.apollographql.com/proto/reports.proto + + - run: | + sed -i 's/ \[(js_use_toArray) = true]//g' src/Tracing/FederatedTracing/reports.proto + sed -i 's/ \[(js_preEncoded) = true]//g' src/Tracing/FederatedTracing/reports.proto + sed -i '3 i option php_namespace = "Nuwave\\\\Lighthouse\\\\Tracing\\\\FederatedTracing\\\\Proto";' src/Tracing/FederatedTracing/reports.proto + sed -i '4 i option php_metadata_namespace = "Nuwave\\\\Lighthouse\\\\Tracing\\\\FederatedTracing\\\\Proto\\\\Metadata";' src/Tracing/FederatedTracing/reports.proto + + - uses: peter-evans/create-pull-request@v5 + with: + title: 'Updated `reports.proto`' + commit_message: Updated `reports.proto` + branch: patch/update-reports-proto + delete-branch: true diff --git a/.gitignore b/.gitignore index 309b0da651..4da3699244 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ phpunit.xml .php-cs-fixer.cache build phpstan-tmp-dir +proto-tmp # composer composer.phar diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55443dacd3..f998c3ffad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -113,6 +113,13 @@ $user = User::create([ ]); ``` +## Working with proto files + +Lighthouse uses [protobuf](https://developers.google.com/protocol-buffers) files for [federated tracing](src/Tracing/FederatedTracing/reports.proto). +When updating the proto files, the PHP classes need to be regenerated. +The generation is done with [buf](https://buf.build/docs/generate/overview). +The `make proto` command generates the new PHP classes and replace the old ones. + ## Documentation ### External diff --git a/Makefile b/Makefile index 0afeb9d159..092084b23c 100644 --- a/Makefile +++ b/Makefile @@ -69,3 +69,19 @@ docs: up ## Render the docs in a development server docs/node_modules: up docs/package.json docs/yarn.lock ## Install yarn dependencies ${dcnode} yarn + +.PHONY: proto/update-reports +proto/update-reports: + ${dcphp} curl -sSfo src/Tracing/FederatedTracing/reports.proto https://usage-reporting.api.apollographql.com/proto/reports.proto + ${dcphp} sed -i 's/ \[(js_use_toArray) = true]//g' src/Tracing/FederatedTracing/reports.proto + ${dcphp} sed -i 's/ \[(js_preEncoded) = true]//g' src/Tracing/FederatedTracing/reports.proto + ${dcphp} sed -i '3 i option php_namespace = "Nuwave\\\\Lighthouse\\\\Tracing\\\\FederatedTracing\\\\Proto";' src/Tracing/FederatedTracing/reports.proto + ${dcphp} sed -i '4 i option php_metadata_namespace = "Nuwave\\\\Lighthouse\\\\Tracing\\\\FederatedTracing\\\\Proto\\\\Metadata";' src/Tracing/FederatedTracing/reports.proto + +.PHONY: proto +proto: + docker run --rm --volume ".:/tmp" --workdir /tmp bufbuild/buf generate + ${dcphp} rm -rf src/Tracing/FederatedTracing/Proto + ${dcphp} mv proto-tmp/Nuwave/Lighthouse/Tracing/FederatedTracing/Proto src/Tracing/FederatedTracing/Proto + ${dcphp} rm -rf proto-tmp + $(MAKE) php-cs-fixer diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000000..158c5fc9d7 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,7 @@ +version: v1 +managed: + enabled: true +plugins: + # For new releases, see https://github.com/protocolbuffers/protobuf/releases and https://buf.build/protocolbuffers/php + - plugin: buf.build/protocolbuffers/php:v25.1 + out: proto-tmp diff --git a/composer.json b/composer.json index 06673034a7..8dc74b9f93 100644 --- a/composer.json +++ b/composer.json @@ -48,6 +48,7 @@ "dms/phpunit-arraysubset-asserts": "^0.4 || ^0.5", "ergebnis/composer-normalize": "^2.2.2", "fakerphp/faker": "^1.21", + "google/protobuf": "^3.21", "laravel/framework": "^9 || ^10", "laravel/legacy-factories": "^1.1.1", "laravel/lumen-framework": "^9 || ^10 || dev-master", @@ -77,7 +78,9 @@ "laravel/scout": "Required for the @search directive", "mll-lab/graphql-php-scalars": "Useful scalar types, required for @whereConditions", "mll-lab/laravel-graphiql": "A graphical interactive in-browser GraphQL IDE - integrated with Laravel", - "pusher/pusher-php-server": "Required when using the Pusher Subscriptions driver" + "pusher/pusher-php-server": "Required when using the Pusher Subscriptions driver", + "google/protobuf": "Required when using the tracing driver federated-tracing", + "ext-protobuf": "Improve protobuf serialization performance (used for tracing)" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/docs/master/federation/getting-started.md b/docs/master/federation/getting-started.md index 2332ee21cf..ba3a45a9fb 100644 --- a/docs/master/federation/getting-started.md +++ b/docs/master/federation/getting-started.md @@ -50,6 +50,19 @@ extend schema @link( ) ``` +## Federated tracing + +In order to use federated tracing, you need to enabled [tracing](../performance/tracing.md) +and set the driver to `Nuwave\Lighthouse\Tracing\FederatedTracing\FederatedTracing::class` in your `config/lighthouse.php`: + +```php +'tracing' => [ + 'driver' => Nuwave\Lighthouse\Tracing\FederatedTracing\FederatedTracing::class, +], +``` + +Note that federated tracing requires `google/protobuf` to be installed (for better performance you can also install the `protobuf` php extension). + ### Unsupported features Some features of the Apollo Federation specification **are not supported** by Lighthouse: @@ -86,6 +99,3 @@ type Book @federation__shareable { } ``` -#### Federated tracing - -[Federated tracing](https://www.apollographql.com/docs/federation/metrics) is not supported. diff --git a/docs/master/performance/tracing.md b/docs/master/performance/tracing.md index 738f6e1334..eca71d2d96 100644 --- a/docs/master/performance/tracing.md +++ b/docs/master/performance/tracing.md @@ -1,7 +1,6 @@ # Tracing Tracing offers field-level performance monitoring for your GraphQL server. -Lighthouse follows the [Apollo Tracing response format](https://github.com/apollographql/apollo-tracing#response-format). ## Setup @@ -12,3 +11,17 @@ Add the service provider to your `config/app.php` \Nuwave\Lighthouse\Tracing\TracingServiceProvider::class, ], ``` + +## Drivers + +Lighthouse tracing is implemented though drivers, this allows supporting different tracing formats. + +Lighthouse includes the following drivers: + +- `Nuwave\Lighthouse\Tracing\ApolloTracing\ApolloTracing::class` (default) which implements [Apollo Tracing response format](https://github.com/apollographql/apollo-tracing#response-format) +- `Nuwave\Lighthouse\Tracing\FederatedTracing\FederatedTracing::class` which implements [Apollo Federated tracing](https://www.apollographql.com/docs/federation/metrics/) + +### Federated tracing + +Federated tracing driver requires `google/protobuf` to be installed. +For better performance you can also install the `protobuf` php extension. diff --git a/php.dockerfile b/php.dockerfile index 6e9b2551a9..de2ce6f716 100644 --- a/php.dockerfile +++ b/php.dockerfile @@ -15,6 +15,7 @@ RUN apt-get update && \ mysqli \ pdo_mysql \ intl \ + bcmath \ && rm -rf /var/lib/apt/lists/* \ && pecl install \ xdebug \ diff --git a/phpstan.neon b/phpstan.neon index 1fcc56a697..0ce1ec066f 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -18,6 +18,7 @@ parameters: # laravel/pennant requires Laravel 10 - src/Pennant - tests/Integration/Pennant + - src/Tracing/FederatedTracing/Proto # Generated classes from protobuf ignoreErrors: # PHPStan does not get it - '#Parameter \#1 \$callback of static method Closure::fromCallable\(\) expects callable\(\): mixed, array{object, .*} given\.#' diff --git a/rector.php b/rector.php index 9d7eedacf8..dfb69198d0 100644 --- a/rector.php +++ b/rector.php @@ -39,6 +39,7 @@ PHPUnitSetList::REMOVE_MOCKS, ]); $rectorConfig->skip([ + __DIR__ . '/src/Tracing/FederatedTracing/Proto', // Generated code __DIR__ . '/tests/database/migrations', // Does not fit autoloading standards __DIR__ . '/tests/LaravelPhpdocAlignmentFixer.php', // Copied from Laravel CallableThisArrayToAnonymousFunctionRector::class, // Callable in array form is shorter and more efficient diff --git a/src/Events/BuildExtensionsResponse.php b/src/Events/BuildExtensionsResponse.php index 252efa43aa..d3594d6a0a 100644 --- a/src/Events/BuildExtensionsResponse.php +++ b/src/Events/BuildExtensionsResponse.php @@ -2,10 +2,18 @@ namespace Nuwave\Lighthouse\Events; +use GraphQL\Executor\ExecutionResult; + /** * Fires after a query was resolved. * * Listeners may return a @see \Nuwave\Lighthouse\Execution\ExtensionsResponse * to include in the response. */ -class BuildExtensionsResponse {} +class BuildExtensionsResponse +{ + public function __construct( + /** The result of resolving a single operation. */ + public ExecutionResult $result, + ) {} +} diff --git a/src/GraphQL.php b/src/GraphQL.php index 588c77ded1..bc04c85982 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -128,7 +128,7 @@ public function executeParsedQuery( /** @var array<\Nuwave\Lighthouse\Execution\ExtensionsResponse|null> $extensionsResponses */ $extensionsResponses = (array) $this->eventDispatcher->dispatch( - new BuildExtensionsResponse(), + new BuildExtensionsResponse($result), ); foreach ($extensionsResponses as $extensionsResponse) { diff --git a/src/Tracing/ApolloTracing/ApolloTracing.php b/src/Tracing/ApolloTracing/ApolloTracing.php new file mode 100644 index 0000000000..8b80c46644 --- /dev/null +++ b/src/Tracing/ApolloTracing/ApolloTracing.php @@ -0,0 +1,76 @@ +> + */ + protected array $resolverTraces = []; + + public function handleStartRequest(StartRequest $startRequest): void {} + + public function handleStartExecution(StartExecution $startExecution): void + { + $this->executionStartAbsolute = Carbon::now(); + $this->executionStartPrecise = $this->timestamp(); + $this->resolverTraces = []; + } + + public function handleBuildExtensionsResponse(BuildExtensionsResponse $buildExtensionsResponse): ?ExtensionsResponse + { + $requestEndAbsolute = Carbon::now(); + $requestEndPrecise = $this->timestamp(); + + return new ExtensionsResponse( + 'tracing', + [ + 'version' => 1, + 'startTime' => $this->formatTimestamp($this->executionStartAbsolute), + 'endTime' => $this->formatTimestamp($requestEndAbsolute), + 'duration' => $this->diffTimeInNanoseconds($this->executionStartPrecise, $requestEndPrecise), + 'execution' => [ + 'resolvers' => $this->resolverTraces, + ], + ], + ); + } + + /** Record resolver execution time. */ + public function record(ResolveInfo $resolveInfo, float|int $start, float|int $end): void + { + $this->resolverTraces[] = [ + 'path' => $resolveInfo->path, + 'parentType' => $resolveInfo->parentType->name, + 'fieldName' => $resolveInfo->fieldName, + 'returnType' => $resolveInfo->returnType->toString(), + 'startOffset' => $this->diffTimeInNanoseconds($this->executionStartPrecise, $start), + 'duration' => $this->diffTimeInNanoseconds($start, $end), + ]; + } +} diff --git a/src/Tracing/FederatedTracing/FederatedTracing.php b/src/Tracing/FederatedTracing/FederatedTracing.php new file mode 100644 index 0000000000..2c7670d31e --- /dev/null +++ b/src/Tracing/FederatedTracing/FederatedTracing.php @@ -0,0 +1,194 @@ + */ + protected array $nodes = []; + + public function __construct() + { + $app = Container::getInstance(); + assert($app instanceof Application); + $this->isSubgraph = $app->providerIsLoaded(FederationServiceProvider::class); + } + + public function handleStartRequest(StartRequest $startRequest): void + { + if ($this->isSubgraph && $startRequest->request->header('apollo-federation-include-trace') !== self::V1) { + $this->enabled = false; + + return; + } + + $this->enabled = true; + } + + public function handleStartExecution(StartExecution $startExecution): void + { + if (! $this->enabled) { + return; + } + + $this->requestStartPrecise = $this->timestamp(); + $this->nodes = []; + + $this->trace = new Trace(); + $this->trace->setRoot(new Node()); + $this->trace->setFieldExecutionWeight(1); + $this->trace->setStartTime( + (new Timestamp()) + ->setSeconds($startExecution->moment->getTimestamp()) + ->setNanos($startExecution->moment->micro * 1000), + ); + } + + public function handleBuildExtensionsResponse(BuildExtensionsResponse $buildExtensionsResponse): ?ExtensionsResponse + { + if (! $this->enabled) { + return null; + } + + $requestEnd = Carbon::now(); + $requestEndPrecise = $this->timestamp(); + + $this->trace->setEndTime( + (new Timestamp()) + ->setSeconds($requestEnd->getTimestamp()) + ->setNanos($requestEnd->micro * 1000), + ); + $this->trace->setDurationNs($this->diffTimeInNanoseconds($this->requestStartPrecise, $requestEndPrecise)); + + foreach ($buildExtensionsResponse->result->errors as $resultError) { + $this->recordError($resultError); + } + + assert($this->trace !== null); + + return new ExtensionsResponse( + self::V1, + base64_encode($this->trace->serializeToString()), + ); + } + + /** Record resolver execution time. */ + public function record(ResolveInfo $resolveInfo, float|int $start, float|int $end): void + { + if (! $this->enabled) { + return; + } + + $node = $this->findOrNewNode($resolveInfo->path); + $node->setType($resolveInfo->returnType->toString()); + $node->setParentType($resolveInfo->parentType->toString()); + $node->setStartTime($this->diffTimeInNanoseconds($this->requestStartPrecise, $start)); + $node->setEndTime($this->diffTimeInNanoseconds($this->requestStartPrecise, $end)); + } + + protected function recordError(Error $error): void + { + if (! $this->enabled) { + return; + } + + $traceError = new Trace\Error(); + $traceError->setMessage($error->isClientSafe() ? $error->getMessage() : 'Internal server error'); + $traceError->setLocation(array_map(static function (SourceLocation $sourceLocation): Location { + $location = new Location(); + $location->setLine($sourceLocation->line); + $location->setColumn($sourceLocation->column); + + return $location; + }, $error->getLocations())); + + $node = $this->findOrNewNode($error->getPath() ?? []); + $node->setError([$traceError]); + } + + /** @param array $path */ + protected function findOrNewNode(array $path): Node + { + assert($this->trace !== null); + assert($this->trace->getRoot() !== null); + + if ($path === []) { + return $this->trace->getRoot(); + } + + $pathKey = implode('.', $path); + + return $this->nodes[$pathKey] ??= $this->newNode($path); + } + + /** @param non-empty-array $path */ + protected function newNode(array $path): Node + { + $node = new Node(); + + $field = $path[count($path) - 1]; + if (is_int($field)) { + $node->setIndex($field); + } else { + $node->setResponseName($field); + } + + $parentNode = $this->ensureParentNode($path); + $parentNode->getChild()[] = $node; + + return $node; + } + + /** @param non-empty-array $path */ + protected function ensureParentNode(array $path): Node + { + if (count($path) === 1) { + $rootNode = $this->trace->getRoot(); + assert($rootNode !== null); + + return $rootNode; + } + + $parentPath = array_slice($path, 0, -1); + + $parentNode = $this->nodes[implode('.', $parentPath)] ?? null; + + return $parentNode ?? $this->findOrNewNode($parentPath); + } +} diff --git a/src/Tracing/FederatedTracing/Proto/ContextualizedQueryLatencyStats.php b/src/Tracing/FederatedTracing/Proto/ContextualizedQueryLatencyStats.php new file mode 100644 index 0000000000..ff563778e8 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/ContextualizedQueryLatencyStats.php @@ -0,0 +1,105 @@ +ContextualizedQueryLatencyStats. + */ +class ContextualizedQueryLatencyStats extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field .QueryLatencyStats query_latency_stats = 1 [json_name = "queryLatencyStats"]; */ + protected $query_latency_stats; + + /** Generated from protobuf field .StatsContext context = 2 [json_name = "context"]; */ + protected $context; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryLatencyStats $query_latency_stats + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext $context + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field .QueryLatencyStats query_latency_stats = 1 [json_name = "queryLatencyStats"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryLatencyStats|null + */ + public function getQueryLatencyStats() + { + return $this->query_latency_stats; + } + + public function hasQueryLatencyStats() + { + return isset($this->query_latency_stats); + } + + public function clearQueryLatencyStats() + { + unset($this->query_latency_stats); + } + + /** + * Generated from protobuf field .QueryLatencyStats query_latency_stats = 1 [json_name = "queryLatencyStats"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryLatencyStats $var + * + * @return $this + */ + public function setQueryLatencyStats($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryLatencyStats::class); + $this->query_latency_stats = $var; + + return $this; + } + + /** + * Generated from protobuf field .StatsContext context = 2 [json_name = "context"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext|null + */ + public function getContext() + { + return $this->context; + } + + public function hasContext() + { + return isset($this->context); + } + + public function clearContext() + { + unset($this->context); + } + + /** + * Generated from protobuf field .StatsContext context = 2 [json_name = "context"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext $var + * + * @return $this + */ + public function setContext($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext::class); + $this->context = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/ContextualizedStats.php b/src/Tracing/FederatedTracing/Proto/ContextualizedStats.php new file mode 100644 index 0000000000..c8d675ffc4 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/ContextualizedStats.php @@ -0,0 +1,147 @@ +ContextualizedStats. + */ +class ContextualizedStats extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field .StatsContext context = 1 [json_name = "context"]; */ + protected $context; + + /** Generated from protobuf field .QueryLatencyStats query_latency_stats = 2 [json_name = "queryLatencyStats"]; */ + protected $query_latency_stats; + + /** + * Key is type name. This structure provides data for the count and latency of individual + * field executions and thus only reflects operations for which field-level tracing occurred. + * + * Generated from protobuf field map per_type_stat = 3 [json_name = "perTypeStat"]; + */ + private $per_type_stat; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext $context + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryLatencyStats $query_latency_stats + * @var array|\Google\Protobuf\Internal\MapField $per_type_stat + * Key is type name. This structure provides data for the count and latency of individual + * field executions and thus only reflects operations for which field-level tracing occurred. + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field .StatsContext context = 1 [json_name = "context"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext|null + */ + public function getContext() + { + return $this->context; + } + + public function hasContext() + { + return isset($this->context); + } + + public function clearContext() + { + unset($this->context); + } + + /** + * Generated from protobuf field .StatsContext context = 1 [json_name = "context"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext $var + * + * @return $this + */ + public function setContext($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext::class); + $this->context = $var; + + return $this; + } + + /** + * Generated from protobuf field .QueryLatencyStats query_latency_stats = 2 [json_name = "queryLatencyStats"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryLatencyStats|null + */ + public function getQueryLatencyStats() + { + return $this->query_latency_stats; + } + + public function hasQueryLatencyStats() + { + return isset($this->query_latency_stats); + } + + public function clearQueryLatencyStats() + { + unset($this->query_latency_stats); + } + + /** + * Generated from protobuf field .QueryLatencyStats query_latency_stats = 2 [json_name = "queryLatencyStats"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryLatencyStats $var + * + * @return $this + */ + public function setQueryLatencyStats($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryLatencyStats::class); + $this->query_latency_stats = $var; + + return $this; + } + + /** + * Key is type name. This structure provides data for the count and latency of individual + * field executions and thus only reflects operations for which field-level tracing occurred. + * + * Generated from protobuf field map per_type_stat = 3 [json_name = "perTypeStat"]; + * + * @return \Google\Protobuf\Internal\MapField + */ + public function getPerTypeStat() + { + return $this->per_type_stat; + } + + /** + * Key is type name. This structure provides data for the count and latency of individual + * field executions and thus only reflects operations for which field-level tracing occurred. + * + * Generated from protobuf field map per_type_stat = 3 [json_name = "perTypeStat"]; + * + * @param array|\Google\Protobuf\Internal\MapField $var + * + * @return $this + */ + public function setPerTypeStat($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\TypeStat::class); + $this->per_type_stat = $arr; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/ContextualizedTypeStats.php b/src/Tracing/FederatedTracing/Proto/ContextualizedTypeStats.php new file mode 100644 index 0000000000..aa272b6156 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/ContextualizedTypeStats.php @@ -0,0 +1,95 @@ +ContextualizedTypeStats. + */ +class ContextualizedTypeStats extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field .StatsContext context = 1 [json_name = "context"]; */ + protected $context; + + /** Generated from protobuf field map per_type_stat = 2 [json_name = "perTypeStat"]; */ + private $per_type_stat; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext $context + * @var array|\Google\Protobuf\Internal\MapField $per_type_stat + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field .StatsContext context = 1 [json_name = "context"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext|null + */ + public function getContext() + { + return $this->context; + } + + public function hasContext() + { + return isset($this->context); + } + + public function clearContext() + { + unset($this->context); + } + + /** + * Generated from protobuf field .StatsContext context = 1 [json_name = "context"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext $var + * + * @return $this + */ + public function setContext($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\StatsContext::class); + $this->context = $var; + + return $this; + } + + /** + * Generated from protobuf field map per_type_stat = 2 [json_name = "perTypeStat"];. + * + * @return \Google\Protobuf\Internal\MapField + */ + public function getPerTypeStat() + { + return $this->per_type_stat; + } + + /** + * Generated from protobuf field map per_type_stat = 2 [json_name = "perTypeStat"];. + * + * @param array|\Google\Protobuf\Internal\MapField $var + * + * @return $this + */ + public function setPerTypeStat($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\TypeStat::class); + $this->per_type_stat = $arr; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/FieldStat.php b/src/Tracing/FederatedTracing/Proto/FieldStat.php new file mode 100644 index 0000000000..995f4f6466 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/FieldStat.php @@ -0,0 +1,335 @@ +FieldStat. + */ +class FieldStat extends \Google\Protobuf\Internal\Message +{ + /** + * required; eg "String!" for User.email:String! + * + * Generated from protobuf field string return_type = 3 [json_name = "returnType"]; + */ + protected $return_type = ''; + + /** + * Number of errors whose path is this field. Note that we assume that error + * tracking does *not* require field-level instrumentation so this *will* + * include errors from requests that don't contribute to the + * `observed_execution_count` field (and does not need to be scaled by + * field_execution_weight). + * + * Generated from protobuf field uint64 errors_count = 4 [json_name = "errorsCount"]; + */ + protected $errors_count = 0; + + /** + * Number of times that the resolver for this field is directly observed being + * executed. + * + * Generated from protobuf field uint64 observed_execution_count = 5 [json_name = "observedExecutionCount"]; + */ + protected $observed_execution_count = 0; + + /** + * Same as `observed_execution_count` but potentially scaled upwards if the server was only + * performing field-level instrumentation on a sampling of operations. For + * example, if the server randomly instruments 1% of requests for this + * operation, this number will be 100 times greater than + * `observed_execution_count`. (When aggregating a Trace into FieldStats, + * this number goes up by the trace's `field_execution_weight` for each + * observed field execution, while `observed_execution_count` above goes + * up by 1.). + * + * Generated from protobuf field uint64 estimated_execution_count = 10 [json_name = "estimatedExecutionCount"]; + */ + protected $estimated_execution_count = 0; + + /** + * Number of times the resolver for this field is executed that resulted in + * at least one error. "Request" is a misnomer here as this corresponds to + * resolver calls, not overall operations. Like `errors_count` above, this + * includes all requests rather than just requests with field-level + * instrumentation. + * + * Generated from protobuf field uint64 requests_with_errors_count = 6 [json_name = "requestsWithErrorsCount"]; + */ + protected $requests_with_errors_count = 0; + + /** + * Duration histogram for the latency of this field. Note that it is scaled in + * the same way as estimated_execution_count so its "total count" might be + * greater than `observed_execution_count` and may not exactly equal + * `estimated_execution_count` due to rounding. + * See comment on QueryLatencyStats's latency_count for details. + * + * Generated from protobuf field repeated sint64 latency_count = 9 [json_name = "latencyCount"]; + */ + private $latency_count; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $return_type + * required; eg "String!" for User.email:String! + * @var int|string $errors_count + * Number of errors whose path is this field. Note that we assume that error + * tracking does *not* require field-level instrumentation so this *will* + * include errors from requests that don't contribute to the + * `observed_execution_count` field (and does not need to be scaled by + * field_execution_weight). + * @var int|string $observed_execution_count + * Number of times that the resolver for this field is directly observed being + * executed. + * @var int|string $estimated_execution_count + * Same as `observed_execution_count` but potentially scaled upwards if the server was only + * performing field-level instrumentation on a sampling of operations. For + * example, if the server randomly instruments 1% of requests for this + * operation, this number will be 100 times greater than + * `observed_execution_count`. (When aggregating a Trace into FieldStats, + * this number goes up by the trace's `field_execution_weight` for each + * observed field execution, while `observed_execution_count` above goes + * up by 1.) + * @var int|string $requests_with_errors_count + * Number of times the resolver for this field is executed that resulted in + * at least one error. "Request" is a misnomer here as this corresponds to + * resolver calls, not overall operations. Like `errors_count` above, this + * includes all requests rather than just requests with field-level + * instrumentation. + * @var array|array|\Google\Protobuf\Internal\RepeatedField $latency_count + * Duration histogram for the latency of this field. Note that it is scaled in + * the same way as estimated_execution_count so its "total count" might be + * greater than `observed_execution_count` and may not exactly equal + * `estimated_execution_count` due to rounding. + * See comment on QueryLatencyStats's latency_count for details. + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * required; eg "String!" for User.email:String! + * + * Generated from protobuf field string return_type = 3 [json_name = "returnType"]; + * + * @return string + */ + public function getReturnType() + { + return $this->return_type; + } + + /** + * required; eg "String!" for User.email:String! + * + * Generated from protobuf field string return_type = 3 [json_name = "returnType"]; + * + * @param string $var + * + * @return $this + */ + public function setReturnType($var) + { + GPBUtil::checkString($var, true); + $this->return_type = $var; + + return $this; + } + + /** + * Number of errors whose path is this field. Note that we assume that error + * tracking does *not* require field-level instrumentation so this *will* + * include errors from requests that don't contribute to the + * `observed_execution_count` field (and does not need to be scaled by + * field_execution_weight). + * + * Generated from protobuf field uint64 errors_count = 4 [json_name = "errorsCount"]; + * + * @return int|string + */ + public function getErrorsCount() + { + return $this->errors_count; + } + + /** + * Number of errors whose path is this field. Note that we assume that error + * tracking does *not* require field-level instrumentation so this *will* + * include errors from requests that don't contribute to the + * `observed_execution_count` field (and does not need to be scaled by + * field_execution_weight). + * + * Generated from protobuf field uint64 errors_count = 4 [json_name = "errorsCount"]; + * + * @param int|string $var + * + * @return $this + */ + public function setErrorsCount($var) + { + GPBUtil::checkUint64($var); + $this->errors_count = $var; + + return $this; + } + + /** + * Number of times that the resolver for this field is directly observed being + * executed. + * + * Generated from protobuf field uint64 observed_execution_count = 5 [json_name = "observedExecutionCount"]; + * + * @return int|string + */ + public function getObservedExecutionCount() + { + return $this->observed_execution_count; + } + + /** + * Number of times that the resolver for this field is directly observed being + * executed. + * + * Generated from protobuf field uint64 observed_execution_count = 5 [json_name = "observedExecutionCount"]; + * + * @param int|string $var + * + * @return $this + */ + public function setObservedExecutionCount($var) + { + GPBUtil::checkUint64($var); + $this->observed_execution_count = $var; + + return $this; + } + + /** + * Same as `observed_execution_count` but potentially scaled upwards if the server was only + * performing field-level instrumentation on a sampling of operations. For + * example, if the server randomly instruments 1% of requests for this + * operation, this number will be 100 times greater than + * `observed_execution_count`. (When aggregating a Trace into FieldStats, + * this number goes up by the trace's `field_execution_weight` for each + * observed field execution, while `observed_execution_count` above goes + * up by 1.). + * + * Generated from protobuf field uint64 estimated_execution_count = 10 [json_name = "estimatedExecutionCount"]; + * + * @return int|string + */ + public function getEstimatedExecutionCount() + { + return $this->estimated_execution_count; + } + + /** + * Same as `observed_execution_count` but potentially scaled upwards if the server was only + * performing field-level instrumentation on a sampling of operations. For + * example, if the server randomly instruments 1% of requests for this + * operation, this number will be 100 times greater than + * `observed_execution_count`. (When aggregating a Trace into FieldStats, + * this number goes up by the trace's `field_execution_weight` for each + * observed field execution, while `observed_execution_count` above goes + * up by 1.). + * + * Generated from protobuf field uint64 estimated_execution_count = 10 [json_name = "estimatedExecutionCount"]; + * + * @param int|string $var + * + * @return $this + */ + public function setEstimatedExecutionCount($var) + { + GPBUtil::checkUint64($var); + $this->estimated_execution_count = $var; + + return $this; + } + + /** + * Number of times the resolver for this field is executed that resulted in + * at least one error. "Request" is a misnomer here as this corresponds to + * resolver calls, not overall operations. Like `errors_count` above, this + * includes all requests rather than just requests with field-level + * instrumentation. + * + * Generated from protobuf field uint64 requests_with_errors_count = 6 [json_name = "requestsWithErrorsCount"]; + * + * @return int|string + */ + public function getRequestsWithErrorsCount() + { + return $this->requests_with_errors_count; + } + + /** + * Number of times the resolver for this field is executed that resulted in + * at least one error. "Request" is a misnomer here as this corresponds to + * resolver calls, not overall operations. Like `errors_count` above, this + * includes all requests rather than just requests with field-level + * instrumentation. + * + * Generated from protobuf field uint64 requests_with_errors_count = 6 [json_name = "requestsWithErrorsCount"]; + * + * @param int|string $var + * + * @return $this + */ + public function setRequestsWithErrorsCount($var) + { + GPBUtil::checkUint64($var); + $this->requests_with_errors_count = $var; + + return $this; + } + + /** + * Duration histogram for the latency of this field. Note that it is scaled in + * the same way as estimated_execution_count so its "total count" might be + * greater than `observed_execution_count` and may not exactly equal + * `estimated_execution_count` due to rounding. + * See comment on QueryLatencyStats's latency_count for details. + * + * Generated from protobuf field repeated sint64 latency_count = 9 [json_name = "latencyCount"]; + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getLatencyCount() + { + return $this->latency_count; + } + + /** + * Duration histogram for the latency of this field. Note that it is scaled in + * the same way as estimated_execution_count so its "total count" might be + * greater than `observed_execution_count` and may not exactly equal + * `estimated_execution_count` due to rounding. + * See comment on QueryLatencyStats's latency_count for details. + * + * Generated from protobuf field repeated sint64 latency_count = 9 [json_name = "latencyCount"]; + * + * @param array|array|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setLatencyCount($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::SINT64); + $this->latency_count = $arr; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/Metadata/Reports.php b/src/Tracing/FederatedTracing/Proto/Metadata/Reports.php new file mode 100644 index 0000000000000000000000000000000000000000..2ff4a2a9569cf6817654d71216f6e3b67efb6f17 GIT binary patch literal 8855 zcmcH^7EHIzt+T#7UVDH2R4G0nt{oHTTzG-)EoP4#IjaU>R^ogUP|K;%+HO93_;e_vyKmjPBW~ZW-;=%!-B@<(N2m3;FYCiFh=Wcrs2`1b zJsH+J!O74Y$go+jZ|&Fj_S^OCt=;wwwBZ8?h|?Vz&q~8{a_TyL&b7sYkIJ!+)=Mk>mMZ?70K)=Wx2a^`1M3B=DZ6?T!3S>;--u8TS2- zT)9P5ye`teG7LZ?ck74ug5Y={Ss+l*;jSOc(02!iTQcf|-Y^cr7Lcy39m_aTEkag6 z-Lxn!=k@Af43u+i?VO-@>v1@ex9a2FktLMzDD>|nOMIUEI=t7~(5g8nU)uw9$L`5+ zZEbwG@hWt#H-6x$Q>=HpiO@S>^p#{26ya6zj}OIb-`v=Zf7V3-Z7R+8g#al+@I8>n z%GYx%<<(mAn3Tz$0m^J9!rCRG#%J>#QIvj{8K=LNaTaKa?Yby;N8s3CR>zO5GH~W? zp+S3*d0phgAc(Chtyyl8pv}FYD{VrUFN+HK5C*_N#?rb0ltp{ai*|htGhYy;$UF93 zFpjhqfS?4jYk>KlSRVQEne2=p!1jk27$DC^&^@)T5$#3j3pn>6c&ryLrXlMUqBIi30mIQc{Wu<40`TQkZJqn=cFQIN zxW!z@?ewKH3?S-HEt3dMb=FNpwFqOEiH;x4w}p5-lHsW{9Jsz!Bc?^g^dm&H08UO& zB079DZ;Qp=XfSY?leg`Av9$>7MSC7`#7n#0_hPdt7C<497lGLo7t%k^^a+ZXkp4Yk zB|t?V%41{?ttDbwO7O&FE3EmJSk#oTRiZ}ABCdroZXXsP?}O7U^n&on>vpA|;pJk{>_x(ElbZwbQ^M7LofGAh89*n6r~?98Ibf=; zTIVg*JV3n7WDr^6#C_(t#}ear3YeztB0$!U4#OZcEinuEJOZUCM*RrURU8BzvM})Dd9JAQJWyaeAuyN4EGA-9 zEJ~cWXYdh{qABu9BX|z_UHdXG@)&sLYhva^LaFX56Y5|?WD_b7U~EMJO1=_Gh?gjK z`qG7#AW~YsuB0L+n@R371?#w~_!Q9mSQsI641piXWT^rju%2CtX)he} zR#Z~P|GIADshDBIWa9*4W53nj-QPQaK$#WO_qN-{lvxBjZ?A6|)A*~kf6zAah-^J< z8wCult?dWf5KS{?3Cg;?zPWA8;v0|#gx|tbLEXf?N=31p;Ds@qWPgHMi{TqA5#Jt zXUq&;^FR^d%Aoj!2^Rn*xr2cmkXbHTprwfP0-DKuR}^|O?(`{Xl~-R<$INcOgA|ID z$;}VMY;WMkvBXr5rqG$=3@6hlUd@8Lb|u_|EH>lhUAK@z?@Hw6hA4FczYA9oC^6iM zH6}{Dsi_qIn60)Z=F;kf6kPB@tq~Xi4L!MYVG`s`Yw}z1elaCVi6X&u<{}wB@j6g3 zuv4Uvvºq^~EjIq;qz*rI71Dzgppt4JLF4`vHKpI5t5C-adfuPEmGprh$fzZVo z5k(l5si4=3WK6cp+f{^Pi@#4$^ZU?&Yf5FJ%Wf9nj?5132$`fKy(iE(Vfj-wx{Mie z3^U;VwO>MqvorP?^OIC+8oF^`8EIvr(RI3S0pa#Qo`6*26ox=hK^|?bLS?e4iK6zK zoG2v@2!yzo-qJg*pk~_1%z7#E4zSz&%n45xNX|fP|GN2b!iR zpS$+SnCwr1Vw5LwgOyUEg^zb(icp0_rR8;j7|` zPKlb1@6V8)C{v~>!;y~(CRveh(NzW3l;8>@k_d}CxB@+xkXZTJh4&RFf@|%G>wtA| zpi#2TG46m+yI8-W>1zBtC#qx%>Z-&FDR9t4*CW6d6qNY0c$a(6;eJ8_!TF%?cn&ML+pzZ{^ZeXrGfRvHmdP3uS zGuu#KIU{{MM5$V>+W*na;tigabf1N z#Isq4#f{^EQtlNJb%C*)IJ0TT0~Yoju?$u02^j1gmqA>M3Y}&90KJeWC+T^J+K;Dz z)|et0a`2{W;%YMg7>`C2GbRym;e_A}?}7M^_HOe3U=*txhUdfJeS{UBujli$(94<{ zV*!Zi&as;%g;?g)H)G7gZ%%!}C^!BDwTLVOg*KMYVocHLSsr?A$-aD+r)CvjH_Jn> zO^-C_hBI-N$C>opERPS-_+3t1SJIAnCVxIeH^c&j8BFK2N3)Z1Z8n?d80>i>S6Lx19w5n{4ZTvMSma`J!+0L)R+h4E>c6|`LbQPqSf=#hyy4Ad3>1IoY zdLK@+W#HG4+O9Cp5==;j*R#p6)YSaxH24>6;_)VnM?w$bcL;oIW$OzDA@?`Q@i3kJ39V!W&sg^W883!hj1?dyIQk;mMWyK*cL{>s@hWGO1gXQ_+%7W!wI}^)U zqU@2$`bn5^hcRmu0Rl6N#vgN{s5NV@iz*tLx)-)Iw`bQ9<)dj6g|ltvfF%LdJ{I%m zNG4bpUYm>uf#p@vOjbylH!XDICt?};8VQxVBfCT$?F8ghX!jmE0bSj=Q-#bxAnd^+ zv^%}$fMIhFZg3PdMZ1izITWbzMPAHscLnp8nLdr{s6Q;PR+{Oy%T`EpTNre^iyYJ$ zMJd!W42)}vfiEQy=!3$V)m3FC4a~g!!JKyaTcczig6&xV8&kej>6`sz{HB8a@<1$~ zA#{#TIYNpg?h9JneZ3wrZk(cD*oz5s3Tm#3=k?)32@~vNC^+c&RBem)f;wa<*ArCj zL@Z9)0f%HUz9^yY+2#QhOo8?L6HfH{(VTQ}jC8m*XCoaZnyE(w5YNnwME906&@0unXGIGGs4!7 zoAhy|ngsrR1ij*f%6kb5G#FYyjmJ* z{#e}9ucjRpQ7CZe9gSifGR1*vW7)*|q&GAZD+Z<3EbV|@@*Pp7q~yRG4f?1+jgG3^ zWN0hd6{6CQwT`%U$vGxO54`nCr*i9@8dRE2h0(?@Hq=!%qVFDBxi6>QR3A&Ezm`Zo zmpK2u1b?6K_o?^)8@9vbLyMys`e@_rSHGM24gfz-7(>$EHK3zR{HB3EY2eQeJ{O;h F{{W(}!Z82< literal 0 HcmV?d00001 diff --git a/src/Tracing/FederatedTracing/Proto/PathErrorStats.php b/src/Tracing/FederatedTracing/Proto/PathErrorStats.php new file mode 100644 index 0000000000..d8edf3e76e --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/PathErrorStats.php @@ -0,0 +1,114 @@ +PathErrorStats. + */ +class PathErrorStats extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field map children = 1 [json_name = "children"]; */ + private $children; + + /** Generated from protobuf field uint64 errors_count = 4 [json_name = "errorsCount"]; */ + protected $errors_count = 0; + + /** Generated from protobuf field uint64 requests_with_errors_count = 5 [json_name = "requestsWithErrorsCount"]; */ + protected $requests_with_errors_count = 0; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array|\Google\Protobuf\Internal\MapField $children + * @var int|string $errors_count + * @var int|string $requests_with_errors_count + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field map children = 1 [json_name = "children"];. + * + * @return \Google\Protobuf\Internal\MapField + */ + public function getChildren() + { + return $this->children; + } + + /** + * Generated from protobuf field map children = 1 [json_name = "children"];. + * + * @param array|\Google\Protobuf\Internal\MapField $var + * + * @return $this + */ + public function setChildren($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\PathErrorStats::class); + $this->children = $arr; + + return $this; + } + + /** + * Generated from protobuf field uint64 errors_count = 4 [json_name = "errorsCount"];. + * + * @return int|string + */ + public function getErrorsCount() + { + return $this->errors_count; + } + + /** + * Generated from protobuf field uint64 errors_count = 4 [json_name = "errorsCount"];. + * + * @param int|string $var + * + * @return $this + */ + public function setErrorsCount($var) + { + GPBUtil::checkUint64($var); + $this->errors_count = $var; + + return $this; + } + + /** + * Generated from protobuf field uint64 requests_with_errors_count = 5 [json_name = "requestsWithErrorsCount"];. + * + * @return int|string + */ + public function getRequestsWithErrorsCount() + { + return $this->requests_with_errors_count; + } + + /** + * Generated from protobuf field uint64 requests_with_errors_count = 5 [json_name = "requestsWithErrorsCount"];. + * + * @param int|string $var + * + * @return $this + */ + public function setRequestsWithErrorsCount($var) + { + GPBUtil::checkUint64($var); + $this->requests_with_errors_count = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php b/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php new file mode 100644 index 0000000000..a62ca72476 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php @@ -0,0 +1,509 @@ +QueryLatencyStats. + */ +class QueryLatencyStats extends \Google\Protobuf\Internal\Message +{ + /** + * The latencies of all non-cached requests, so the sum of all counts should equal request_count minus cache_hits. + * This is an array of counts within a logarithmic range of 384 latency buckets. To calculate the bucket from a + * microsecond, use the formula: max(0, min(ceil(ln(x)/ln(1.1)), 383)). So for example, 323424 microseconds (323.424 + * ms) corresponds to bucket 134. Buckets can be skipped using a negative number, so one request on that bucket could + * be represented as [-134, 1] (skip buckets numbered 0 to 133 and set a 1 in bucket 134). + * + * Generated from protobuf field repeated sint64 latency_count = 13 [json_name = "latencyCount"]; + */ + private $latency_count; + + /** + * The total number of requests, including both cache hits and cache misses. + * + * Generated from protobuf field uint64 request_count = 2 [json_name = "requestCount"]; + */ + protected $request_count = 0; + + /** + * The total number of requests that were cache hits. Each request should be represented in cache_latency_count. + * + * Generated from protobuf field uint64 cache_hits = 3 [json_name = "cacheHits"]; + */ + protected $cache_hits = 0; + + /** Generated from protobuf field uint64 persisted_query_hits = 4 [json_name = "persistedQueryHits"]; */ + protected $persisted_query_hits = 0; + + /** Generated from protobuf field uint64 persisted_query_misses = 5 [json_name = "persistedQueryMisses"]; */ + protected $persisted_query_misses = 0; + + /** + * This array includes the latency buckets for all operations included in cache_hits + * See comment on latency_count for details. + * + * Generated from protobuf field repeated sint64 cache_latency_count = 14 [json_name = "cacheLatencyCount"]; + */ + private $cache_latency_count; + + /** + * Paths and counts for each error. The total number of requests with errors within this object should be the same as + * requests_with_errors_count below. + * + * Generated from protobuf field .PathErrorStats root_error_stats = 7 [json_name = "rootErrorStats"]; + */ + protected $root_error_stats; + + /** + * Total number of requests that contained at least one error. + * + * Generated from protobuf field uint64 requests_with_errors_count = 8 [json_name = "requestsWithErrorsCount"]; + */ + protected $requests_with_errors_count = 0; + + /** Generated from protobuf field repeated sint64 public_cache_ttl_count = 15 [json_name = "publicCacheTtlCount"]; */ + private $public_cache_ttl_count; + + /** Generated from protobuf field repeated sint64 private_cache_ttl_count = 16 [json_name = "privateCacheTtlCount"]; */ + private $private_cache_ttl_count; + + /** Generated from protobuf field uint64 registered_operation_count = 11 [json_name = "registeredOperationCount"]; */ + protected $registered_operation_count = 0; + + /** Generated from protobuf field uint64 forbidden_operation_count = 12 [json_name = "forbiddenOperationCount"]; */ + protected $forbidden_operation_count = 0; + + /** + * The number of requests that were executed without field-level + * instrumentation (and thus do not contribute to `observed_execution_count` + * fields on this message's cousin-twice-removed FieldStats). + * + * Generated from protobuf field uint64 requests_without_field_instrumentation = 17 [json_name = "requestsWithoutFieldInstrumentation"]; + */ + protected $requests_without_field_instrumentation = 0; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array|array|\Google\Protobuf\Internal\RepeatedField $latency_count + * The latencies of all non-cached requests, so the sum of all counts should equal request_count minus cache_hits. + * This is an array of counts within a logarithmic range of 384 latency buckets. To calculate the bucket from a + * microsecond, use the formula: max(0, min(ceil(ln(x)/ln(1.1)), 383)). So for example, 323424 microseconds (323.424 + * ms) corresponds to bucket 134. Buckets can be skipped using a negative number, so one request on that bucket could + * be represented as [-134, 1] (skip buckets numbered 0 to 133 and set a 1 in bucket 134). + * @var int|string $request_count + * The total number of requests, including both cache hits and cache misses + * @var int|string $cache_hits + * The total number of requests that were cache hits. Each request should be represented in cache_latency_count + * @var int|string $persisted_query_hits + * @var int|string $persisted_query_misses + * @var array|array|\Google\Protobuf\Internal\RepeatedField $cache_latency_count + * This array includes the latency buckets for all operations included in cache_hits + * See comment on latency_count for details. + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\PathErrorStats $root_error_stats + * Paths and counts for each error. The total number of requests with errors within this object should be the same as + * requests_with_errors_count below. + * @var int|string $requests_with_errors_count + * Total number of requests that contained at least one error + * @var array|array|\Google\Protobuf\Internal\RepeatedField $public_cache_ttl_count + * @var array|array|\Google\Protobuf\Internal\RepeatedField $private_cache_ttl_count + * @var int|string $registered_operation_count + * @var int|string $forbidden_operation_count + * @var int|string $requests_without_field_instrumentation + * The number of requests that were executed without field-level + * instrumentation (and thus do not contribute to `observed_execution_count` + * fields on this message's cousin-twice-removed FieldStats). + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * The latencies of all non-cached requests, so the sum of all counts should equal request_count minus cache_hits. + * This is an array of counts within a logarithmic range of 384 latency buckets. To calculate the bucket from a + * microsecond, use the formula: max(0, min(ceil(ln(x)/ln(1.1)), 383)). So for example, 323424 microseconds (323.424 + * ms) corresponds to bucket 134. Buckets can be skipped using a negative number, so one request on that bucket could + * be represented as [-134, 1] (skip buckets numbered 0 to 133 and set a 1 in bucket 134). + * + * Generated from protobuf field repeated sint64 latency_count = 13 [json_name = "latencyCount"]; + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getLatencyCount() + { + return $this->latency_count; + } + + /** + * The latencies of all non-cached requests, so the sum of all counts should equal request_count minus cache_hits. + * This is an array of counts within a logarithmic range of 384 latency buckets. To calculate the bucket from a + * microsecond, use the formula: max(0, min(ceil(ln(x)/ln(1.1)), 383)). So for example, 323424 microseconds (323.424 + * ms) corresponds to bucket 134. Buckets can be skipped using a negative number, so one request on that bucket could + * be represented as [-134, 1] (skip buckets numbered 0 to 133 and set a 1 in bucket 134). + * + * Generated from protobuf field repeated sint64 latency_count = 13 [json_name = "latencyCount"]; + * + * @param array|array|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setLatencyCount($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::SINT64); + $this->latency_count = $arr; + + return $this; + } + + /** + * The total number of requests, including both cache hits and cache misses. + * + * Generated from protobuf field uint64 request_count = 2 [json_name = "requestCount"]; + * + * @return int|string + */ + public function getRequestCount() + { + return $this->request_count; + } + + /** + * The total number of requests, including both cache hits and cache misses. + * + * Generated from protobuf field uint64 request_count = 2 [json_name = "requestCount"]; + * + * @param int|string $var + * + * @return $this + */ + public function setRequestCount($var) + { + GPBUtil::checkUint64($var); + $this->request_count = $var; + + return $this; + } + + /** + * The total number of requests that were cache hits. Each request should be represented in cache_latency_count. + * + * Generated from protobuf field uint64 cache_hits = 3 [json_name = "cacheHits"]; + * + * @return int|string + */ + public function getCacheHits() + { + return $this->cache_hits; + } + + /** + * The total number of requests that were cache hits. Each request should be represented in cache_latency_count. + * + * Generated from protobuf field uint64 cache_hits = 3 [json_name = "cacheHits"]; + * + * @param int|string $var + * + * @return $this + */ + public function setCacheHits($var) + { + GPBUtil::checkUint64($var); + $this->cache_hits = $var; + + return $this; + } + + /** + * Generated from protobuf field uint64 persisted_query_hits = 4 [json_name = "persistedQueryHits"];. + * + * @return int|string + */ + public function getPersistedQueryHits() + { + return $this->persisted_query_hits; + } + + /** + * Generated from protobuf field uint64 persisted_query_hits = 4 [json_name = "persistedQueryHits"];. + * + * @param int|string $var + * + * @return $this + */ + public function setPersistedQueryHits($var) + { + GPBUtil::checkUint64($var); + $this->persisted_query_hits = $var; + + return $this; + } + + /** + * Generated from protobuf field uint64 persisted_query_misses = 5 [json_name = "persistedQueryMisses"];. + * + * @return int|string + */ + public function getPersistedQueryMisses() + { + return $this->persisted_query_misses; + } + + /** + * Generated from protobuf field uint64 persisted_query_misses = 5 [json_name = "persistedQueryMisses"];. + * + * @param int|string $var + * + * @return $this + */ + public function setPersistedQueryMisses($var) + { + GPBUtil::checkUint64($var); + $this->persisted_query_misses = $var; + + return $this; + } + + /** + * This array includes the latency buckets for all operations included in cache_hits + * See comment on latency_count for details. + * + * Generated from protobuf field repeated sint64 cache_latency_count = 14 [json_name = "cacheLatencyCount"]; + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getCacheLatencyCount() + { + return $this->cache_latency_count; + } + + /** + * This array includes the latency buckets for all operations included in cache_hits + * See comment on latency_count for details. + * + * Generated from protobuf field repeated sint64 cache_latency_count = 14 [json_name = "cacheLatencyCount"]; + * + * @param array|array|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setCacheLatencyCount($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::SINT64); + $this->cache_latency_count = $arr; + + return $this; + } + + /** + * Paths and counts for each error. The total number of requests with errors within this object should be the same as + * requests_with_errors_count below. + * + * Generated from protobuf field .PathErrorStats root_error_stats = 7 [json_name = "rootErrorStats"]; + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\PathErrorStats|null + */ + public function getRootErrorStats() + { + return $this->root_error_stats; + } + + public function hasRootErrorStats() + { + return isset($this->root_error_stats); + } + + public function clearRootErrorStats() + { + unset($this->root_error_stats); + } + + /** + * Paths and counts for each error. The total number of requests with errors within this object should be the same as + * requests_with_errors_count below. + * + * Generated from protobuf field .PathErrorStats root_error_stats = 7 [json_name = "rootErrorStats"]; + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\PathErrorStats $var + * + * @return $this + */ + public function setRootErrorStats($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\PathErrorStats::class); + $this->root_error_stats = $var; + + return $this; + } + + /** + * Total number of requests that contained at least one error. + * + * Generated from protobuf field uint64 requests_with_errors_count = 8 [json_name = "requestsWithErrorsCount"]; + * + * @return int|string + */ + public function getRequestsWithErrorsCount() + { + return $this->requests_with_errors_count; + } + + /** + * Total number of requests that contained at least one error. + * + * Generated from protobuf field uint64 requests_with_errors_count = 8 [json_name = "requestsWithErrorsCount"]; + * + * @param int|string $var + * + * @return $this + */ + public function setRequestsWithErrorsCount($var) + { + GPBUtil::checkUint64($var); + $this->requests_with_errors_count = $var; + + return $this; + } + + /** + * Generated from protobuf field repeated sint64 public_cache_ttl_count = 15 [json_name = "publicCacheTtlCount"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getPublicCacheTtlCount() + { + return $this->public_cache_ttl_count; + } + + /** + * Generated from protobuf field repeated sint64 public_cache_ttl_count = 15 [json_name = "publicCacheTtlCount"];. + * + * @param array|array|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setPublicCacheTtlCount($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::SINT64); + $this->public_cache_ttl_count = $arr; + + return $this; + } + + /** + * Generated from protobuf field repeated sint64 private_cache_ttl_count = 16 [json_name = "privateCacheTtlCount"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getPrivateCacheTtlCount() + { + return $this->private_cache_ttl_count; + } + + /** + * Generated from protobuf field repeated sint64 private_cache_ttl_count = 16 [json_name = "privateCacheTtlCount"];. + * + * @param array|array|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setPrivateCacheTtlCount($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::SINT64); + $this->private_cache_ttl_count = $arr; + + return $this; + } + + /** + * Generated from protobuf field uint64 registered_operation_count = 11 [json_name = "registeredOperationCount"];. + * + * @return int|string + */ + public function getRegisteredOperationCount() + { + return $this->registered_operation_count; + } + + /** + * Generated from protobuf field uint64 registered_operation_count = 11 [json_name = "registeredOperationCount"];. + * + * @param int|string $var + * + * @return $this + */ + public function setRegisteredOperationCount($var) + { + GPBUtil::checkUint64($var); + $this->registered_operation_count = $var; + + return $this; + } + + /** + * Generated from protobuf field uint64 forbidden_operation_count = 12 [json_name = "forbiddenOperationCount"];. + * + * @return int|string + */ + public function getForbiddenOperationCount() + { + return $this->forbidden_operation_count; + } + + /** + * Generated from protobuf field uint64 forbidden_operation_count = 12 [json_name = "forbiddenOperationCount"];. + * + * @param int|string $var + * + * @return $this + */ + public function setForbiddenOperationCount($var) + { + GPBUtil::checkUint64($var); + $this->forbidden_operation_count = $var; + + return $this; + } + + /** + * The number of requests that were executed without field-level + * instrumentation (and thus do not contribute to `observed_execution_count` + * fields on this message's cousin-twice-removed FieldStats). + * + * Generated from protobuf field uint64 requests_without_field_instrumentation = 17 [json_name = "requestsWithoutFieldInstrumentation"]; + * + * @return int|string + */ + public function getRequestsWithoutFieldInstrumentation() + { + return $this->requests_without_field_instrumentation; + } + + /** + * The number of requests that were executed without field-level + * instrumentation (and thus do not contribute to `observed_execution_count` + * fields on this message's cousin-twice-removed FieldStats). + * + * Generated from protobuf field uint64 requests_without_field_instrumentation = 17 [json_name = "requestsWithoutFieldInstrumentation"]; + * + * @param int|string $var + * + * @return $this + */ + public function setRequestsWithoutFieldInstrumentation($var) + { + GPBUtil::checkUint64($var); + $this->requests_without_field_instrumentation = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/QueryMetadata.php b/src/Tracing/FederatedTracing/Proto/QueryMetadata.php new file mode 100644 index 0000000000..921f10aa66 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/QueryMetadata.php @@ -0,0 +1,141 @@ +QueryMetadata. + */ +class QueryMetadata extends \Google\Protobuf\Internal\Message +{ + /** + * The operation name. For now this is a required field if QueryMetadata is present. + * + * Generated from protobuf field string name = 1 [json_name = "name"]; + */ + protected $name = ''; + + /** + * the operation signature. For now this is a required field if QueryMetadata is present. + * + * Generated from protobuf field string signature = 2 [json_name = "signature"]; + */ + protected $signature = ''; + + /** + * (Optional) Persisted query ID that was used to request this operation. + * + * Generated from protobuf field string pq_id = 3 [json_name = "pqId"]; + */ + protected $pq_id = ''; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $name + * The operation name. For now this is a required field if QueryMetadata is present. + * @var string $signature + * the operation signature. For now this is a required field if QueryMetadata is present. + * @var string $pq_id + * (Optional) Persisted query ID that was used to request this operation. + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * The operation name. For now this is a required field if QueryMetadata is present. + * + * Generated from protobuf field string name = 1 [json_name = "name"]; + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * The operation name. For now this is a required field if QueryMetadata is present. + * + * Generated from protobuf field string name = 1 [json_name = "name"]; + * + * @param string $var + * + * @return $this + */ + public function setName($var) + { + GPBUtil::checkString($var, true); + $this->name = $var; + + return $this; + } + + /** + * the operation signature. For now this is a required field if QueryMetadata is present. + * + * Generated from protobuf field string signature = 2 [json_name = "signature"]; + * + * @return string + */ + public function getSignature() + { + return $this->signature; + } + + /** + * the operation signature. For now this is a required field if QueryMetadata is present. + * + * Generated from protobuf field string signature = 2 [json_name = "signature"]; + * + * @param string $var + * + * @return $this + */ + public function setSignature($var) + { + GPBUtil::checkString($var, true); + $this->signature = $var; + + return $this; + } + + /** + * (Optional) Persisted query ID that was used to request this operation. + * + * Generated from protobuf field string pq_id = 3 [json_name = "pqId"]; + * + * @return string + */ + public function getPqId() + { + return $this->pq_id; + } + + /** + * (Optional) Persisted query ID that was used to request this operation. + * + * Generated from protobuf field string pq_id = 3 [json_name = "pqId"]; + * + * @param string $var + * + * @return $this + */ + public function setPqId($var) + { + GPBUtil::checkString($var, true); + $this->pq_id = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/ReferencedFieldsForType.php b/src/Tracing/FederatedTracing/Proto/ReferencedFieldsForType.php new file mode 100644 index 0000000000..1e5020649a --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/ReferencedFieldsForType.php @@ -0,0 +1,103 @@ +ReferencedFieldsForType. + */ +class ReferencedFieldsForType extends \Google\Protobuf\Internal\Message +{ + /** + * Contains (eg) "email" for User.email:String! + * + * Generated from protobuf field repeated string field_names = 1 [json_name = "fieldNames"]; + */ + private $field_names; + + /** + * True if this type is an interface. + * + * Generated from protobuf field bool is_interface = 2 [json_name = "isInterface"]; + */ + protected $is_interface = false; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array|\Google\Protobuf\Internal\RepeatedField $field_names + * Contains (eg) "email" for User.email:String! + * @var bool $is_interface + * True if this type is an interface. + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Contains (eg) "email" for User.email:String! + * + * Generated from protobuf field repeated string field_names = 1 [json_name = "fieldNames"]; + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getFieldNames() + { + return $this->field_names; + } + + /** + * Contains (eg) "email" for User.email:String! + * + * Generated from protobuf field repeated string field_names = 1 [json_name = "fieldNames"]; + * + * @param array|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setFieldNames($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING); + $this->field_names = $arr; + + return $this; + } + + /** + * True if this type is an interface. + * + * Generated from protobuf field bool is_interface = 2 [json_name = "isInterface"]; + * + * @return bool + */ + public function getIsInterface() + { + return $this->is_interface; + } + + /** + * True if this type is an interface. + * + * Generated from protobuf field bool is_interface = 2 [json_name = "isInterface"]; + * + * @param bool $var + * + * @return $this + */ + public function setIsInterface($var) + { + GPBUtil::checkBool($var); + $this->is_interface = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/Report.php b/src/Tracing/FederatedTracing/Proto/Report.php new file mode 100644 index 0000000000..cb41147138 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Report.php @@ -0,0 +1,323 @@ +Report + */ +class Report extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field .ReportHeader header = 1 [json_name = "header"]; */ + protected $header; + + /** + * If QueryMetadata isn't provided, this key should be a statsReportKey (# operationName\nsignature). If the operation + * name, signature, and persisted query IDs are provided in the QueryMetadata, and this operation was requested via a + * persisted query, this key can be "pq# " instead of the signature and operation. + * + * Generated from protobuf field map traces_per_query = 5 [json_name = "tracesPerQuery"]; + */ + private $traces_per_query; + + /** + * This is the time that the requests in this trace are considered to have taken place + * If this field is not present the max of the end_time of each trace will be used instead. + * If there are no traces and no end_time present the report will not be able to be processed. + * Note: This will override the end_time from traces. + * + * Generated from protobuf field .google.protobuf.Timestamp end_time = 2 [json_name = "endTime"]; + */ + protected $end_time; + + /** + * Total number of operations processed during this period. This includes all operations, even if they are sampled + * and not included in the query latency stats. + * + * Generated from protobuf field uint64 operation_count = 6 [json_name = "operationCount"]; + */ + protected $operation_count = 0; + + /** + * Total number of operations broken up by operation type and operation subtype. + * Only either this or operation_count should be populated, but if both are present, the total across all types and + * subtypes should match the overall operation_count. + * + * Generated from protobuf field repeated .Report.OperationCountByType operation_count_by_type = 8 [json_name = "operationCountByType"]; + */ + private $operation_count_by_type; + + /** + * If this is set to true, the stats in TracesWithStats.stats_with_context + * represent all of the operations described from this report, and the + * traces in TracesWithStats.trace are a sampling of some of the same + * operations. If this is false, each operation is described in precisely + * one of those two fields. + * + * Generated from protobuf field bool traces_pre_aggregated = 7 [json_name = "tracesPreAggregated"]; + */ + protected $traces_pre_aggregated = false; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\ReportHeader $header + * @var array|\Google\Protobuf\Internal\MapField $traces_per_query + * If QueryMetadata isn't provided, this key should be a statsReportKey (# operationName\nsignature). If the operation + * name, signature, and persisted query IDs are provided in the QueryMetadata, and this operation was requested via a + * persisted query, this key can be "pq# " instead of the signature and operation. + * @var \Google\Protobuf\Timestamp $end_time + * This is the time that the requests in this trace are considered to have taken place + * If this field is not present the max of the end_time of each trace will be used instead. + * If there are no traces and no end_time present the report will not be able to be processed. + * Note: This will override the end_time from traces. + * @var int|string $operation_count + * Total number of operations processed during this period. This includes all operations, even if they are sampled + * and not included in the query latency stats. + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Report\OperationCountByType>|\Google\Protobuf\Internal\RepeatedField $operation_count_by_type + * Total number of operations broken up by operation type and operation subtype. + * Only either this or operation_count should be populated, but if both are present, the total across all types and + * subtypes should match the overall operation_count. + * @var bool $traces_pre_aggregated + * If this is set to true, the stats in TracesWithStats.stats_with_context + * represent all of the operations described from this report, and the + * traces in TracesWithStats.trace are a sampling of some of the same + * operations. If this is false, each operation is described in precisely + * one of those two fields. + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field .ReportHeader header = 1 [json_name = "header"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\ReportHeader|null + */ + public function getHeader() + { + return $this->header; + } + + public function hasHeader() + { + return isset($this->header); + } + + public function clearHeader() + { + unset($this->header); + } + + /** + * Generated from protobuf field .ReportHeader header = 1 [json_name = "header"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\ReportHeader $var + * + * @return $this + */ + public function setHeader($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\ReportHeader::class); + $this->header = $var; + + return $this; + } + + /** + * If QueryMetadata isn't provided, this key should be a statsReportKey (# operationName\nsignature). If the operation + * name, signature, and persisted query IDs are provided in the QueryMetadata, and this operation was requested via a + * persisted query, this key can be "pq# " instead of the signature and operation. + * + * Generated from protobuf field map traces_per_query = 5 [json_name = "tracesPerQuery"]; + * + * @return \Google\Protobuf\Internal\MapField + */ + public function getTracesPerQuery() + { + return $this->traces_per_query; + } + + /** + * If QueryMetadata isn't provided, this key should be a statsReportKey (# operationName\nsignature). If the operation + * name, signature, and persisted query IDs are provided in the QueryMetadata, and this operation was requested via a + * persisted query, this key can be "pq# " instead of the signature and operation. + * + * Generated from protobuf field map traces_per_query = 5 [json_name = "tracesPerQuery"]; + * + * @param array|\Google\Protobuf\Internal\MapField $var + * + * @return $this + */ + public function setTracesPerQuery($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\TracesAndStats::class); + $this->traces_per_query = $arr; + + return $this; + } + + /** + * This is the time that the requests in this trace are considered to have taken place + * If this field is not present the max of the end_time of each trace will be used instead. + * If there are no traces and no end_time present the report will not be able to be processed. + * Note: This will override the end_time from traces. + * + * Generated from protobuf field .google.protobuf.Timestamp end_time = 2 [json_name = "endTime"]; + * + * @return \Google\Protobuf\Timestamp|null + */ + public function getEndTime() + { + return $this->end_time; + } + + public function hasEndTime() + { + return isset($this->end_time); + } + + public function clearEndTime() + { + unset($this->end_time); + } + + /** + * This is the time that the requests in this trace are considered to have taken place + * If this field is not present the max of the end_time of each trace will be used instead. + * If there are no traces and no end_time present the report will not be able to be processed. + * Note: This will override the end_time from traces. + * + * Generated from protobuf field .google.protobuf.Timestamp end_time = 2 [json_name = "endTime"]; + * + * @param \Google\Protobuf\Timestamp $var + * + * @return $this + */ + public function setEndTime($var) + { + GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class); + $this->end_time = $var; + + return $this; + } + + /** + * Total number of operations processed during this period. This includes all operations, even if they are sampled + * and not included in the query latency stats. + * + * Generated from protobuf field uint64 operation_count = 6 [json_name = "operationCount"]; + * + * @return int|string + */ + public function getOperationCount() + { + return $this->operation_count; + } + + /** + * Total number of operations processed during this period. This includes all operations, even if they are sampled + * and not included in the query latency stats. + * + * Generated from protobuf field uint64 operation_count = 6 [json_name = "operationCount"]; + * + * @param int|string $var + * + * @return $this + */ + public function setOperationCount($var) + { + GPBUtil::checkUint64($var); + $this->operation_count = $var; + + return $this; + } + + /** + * Total number of operations broken up by operation type and operation subtype. + * Only either this or operation_count should be populated, but if both are present, the total across all types and + * subtypes should match the overall operation_count. + * + * Generated from protobuf field repeated .Report.OperationCountByType operation_count_by_type = 8 [json_name = "operationCountByType"]; + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getOperationCountByType() + { + return $this->operation_count_by_type; + } + + /** + * Total number of operations broken up by operation type and operation subtype. + * Only either this or operation_count should be populated, but if both are present, the total across all types and + * subtypes should match the overall operation_count. + * + * Generated from protobuf field repeated .Report.OperationCountByType operation_count_by_type = 8 [json_name = "operationCountByType"]; + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Report\OperationCountByType>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setOperationCountByType($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Report\OperationCountByType::class); + $this->operation_count_by_type = $arr; + + return $this; + } + + /** + * If this is set to true, the stats in TracesWithStats.stats_with_context + * represent all of the operations described from this report, and the + * traces in TracesWithStats.trace are a sampling of some of the same + * operations. If this is false, each operation is described in precisely + * one of those two fields. + * + * Generated from protobuf field bool traces_pre_aggregated = 7 [json_name = "tracesPreAggregated"]; + * + * @return bool + */ + public function getTracesPreAggregated() + { + return $this->traces_pre_aggregated; + } + + /** + * If this is set to true, the stats in TracesWithStats.stats_with_context + * represent all of the operations described from this report, and the + * traces in TracesWithStats.trace are a sampling of some of the same + * operations. If this is false, each operation is described in precisely + * one of those two fields. + * + * Generated from protobuf field bool traces_pre_aggregated = 7 [json_name = "tracesPreAggregated"]; + * + * @param bool $var + * + * @return $this + */ + public function setTracesPreAggregated($var) + { + GPBUtil::checkBool($var); + $this->traces_pre_aggregated = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/Report/OperationCountByType.php b/src/Tracing/FederatedTracing/Proto/Report/OperationCountByType.php new file mode 100644 index 0000000000..813b30cc24 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Report/OperationCountByType.php @@ -0,0 +1,117 @@ +Report.OperationCountByType. + */ +class OperationCountByType extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field string type = 1 [json_name = "type"]; */ + protected $type = ''; + + /** Generated from protobuf field string subtype = 2 [json_name = "subtype"]; */ + protected $subtype = ''; + + /** Generated from protobuf field uint64 operation_count = 3 [json_name = "operationCount"]; */ + protected $operation_count = 0; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $type + * @var string $subtype + * @var int|string $operation_count + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field string type = 1 [json_name = "type"];. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Generated from protobuf field string type = 1 [json_name = "type"];. + * + * @param string $var + * + * @return $this + */ + public function setType($var) + { + GPBUtil::checkString($var, true); + $this->type = $var; + + return $this; + } + + /** + * Generated from protobuf field string subtype = 2 [json_name = "subtype"];. + * + * @return string + */ + public function getSubtype() + { + return $this->subtype; + } + + /** + * Generated from protobuf field string subtype = 2 [json_name = "subtype"];. + * + * @param string $var + * + * @return $this + */ + public function setSubtype($var) + { + GPBUtil::checkString($var, true); + $this->subtype = $var; + + return $this; + } + + /** + * Generated from protobuf field uint64 operation_count = 3 [json_name = "operationCount"];. + * + * @return int|string + */ + public function getOperationCount() + { + return $this->operation_count; + } + + /** + * Generated from protobuf field uint64 operation_count = 3 [json_name = "operationCount"];. + * + * @param int|string $var + * + * @return $this + */ + public function setOperationCount($var) + { + GPBUtil::checkUint64($var); + $this->operation_count = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(OperationCountByType::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Report_OperationCountByType::class); diff --git a/src/Tracing/FederatedTracing/Proto/ReportHeader.php b/src/Tracing/FederatedTracing/Proto/ReportHeader.php new file mode 100644 index 0000000000..be85982303 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/ReportHeader.php @@ -0,0 +1,309 @@ +ReportHeader + */ +class ReportHeader extends \Google\Protobuf\Internal\Message +{ + /** + * eg "mygraph@myvariant". + * + * Generated from protobuf field string graph_ref = 12 [json_name = "graphRef"]; + */ + protected $graph_ref = ''; + + /** + * eg "host-01.example.com". + * + * Generated from protobuf field string hostname = 5 [json_name = "hostname"]; + */ + protected $hostname = ''; + + /** + * eg "engineproxy 0.1.0". + * + * Generated from protobuf field string agent_version = 6 [json_name = "agentVersion"]; + */ + protected $agent_version = ''; + + /** + * eg "prod-4279-20160804T065423Z-5-g3cf0aa8" (taken from `git describe --tags`). + * + * Generated from protobuf field string service_version = 7 [json_name = "serviceVersion"]; + */ + protected $service_version = ''; + + /** + * eg "node v4.6.0". + * + * Generated from protobuf field string runtime_version = 8 [json_name = "runtimeVersion"]; + */ + protected $runtime_version = ''; + + /** + * eg "Linux box 4.6.5-1-ec2 #1 SMP Mon Aug 1 02:31:38 PDT 2016 x86_64 GNU/Linux". + * + * Generated from protobuf field string uname = 9 [json_name = "uname"]; + */ + protected $uname = ''; + + /** + * An id that is used to represent the schema to Apollo Graph Manager + * Using this in place of what used to be schema_hash, since that is no longer + * attached to a schema in the backend. + * + * Generated from protobuf field string executable_schema_id = 11 [json_name = "executableSchemaId"]; + */ + protected $executable_schema_id = ''; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $graph_ref + * eg "mygraph@myvariant" + * @var string $hostname + * eg "host-01.example.com" + * @var string $agent_version + * eg "engineproxy 0.1.0" + * @var string $service_version + * eg "prod-4279-20160804T065423Z-5-g3cf0aa8" (taken from `git describe --tags`) + * @var string $runtime_version + * eg "node v4.6.0" + * @var string $uname + * eg "Linux box 4.6.5-1-ec2 #1 SMP Mon Aug 1 02:31:38 PDT 2016 x86_64 GNU/Linux" + * @var string $executable_schema_id + * An id that is used to represent the schema to Apollo Graph Manager + * Using this in place of what used to be schema_hash, since that is no longer + * attached to a schema in the backend. + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * eg "mygraph@myvariant". + * + * Generated from protobuf field string graph_ref = 12 [json_name = "graphRef"]; + * + * @return string + */ + public function getGraphRef() + { + return $this->graph_ref; + } + + /** + * eg "mygraph@myvariant". + * + * Generated from protobuf field string graph_ref = 12 [json_name = "graphRef"]; + * + * @param string $var + * + * @return $this + */ + public function setGraphRef($var) + { + GPBUtil::checkString($var, true); + $this->graph_ref = $var; + + return $this; + } + + /** + * eg "host-01.example.com". + * + * Generated from protobuf field string hostname = 5 [json_name = "hostname"]; + * + * @return string + */ + public function getHostname() + { + return $this->hostname; + } + + /** + * eg "host-01.example.com". + * + * Generated from protobuf field string hostname = 5 [json_name = "hostname"]; + * + * @param string $var + * + * @return $this + */ + public function setHostname($var) + { + GPBUtil::checkString($var, true); + $this->hostname = $var; + + return $this; + } + + /** + * eg "engineproxy 0.1.0". + * + * Generated from protobuf field string agent_version = 6 [json_name = "agentVersion"]; + * + * @return string + */ + public function getAgentVersion() + { + return $this->agent_version; + } + + /** + * eg "engineproxy 0.1.0". + * + * Generated from protobuf field string agent_version = 6 [json_name = "agentVersion"]; + * + * @param string $var + * + * @return $this + */ + public function setAgentVersion($var) + { + GPBUtil::checkString($var, true); + $this->agent_version = $var; + + return $this; + } + + /** + * eg "prod-4279-20160804T065423Z-5-g3cf0aa8" (taken from `git describe --tags`). + * + * Generated from protobuf field string service_version = 7 [json_name = "serviceVersion"]; + * + * @return string + */ + public function getServiceVersion() + { + return $this->service_version; + } + + /** + * eg "prod-4279-20160804T065423Z-5-g3cf0aa8" (taken from `git describe --tags`). + * + * Generated from protobuf field string service_version = 7 [json_name = "serviceVersion"]; + * + * @param string $var + * + * @return $this + */ + public function setServiceVersion($var) + { + GPBUtil::checkString($var, true); + $this->service_version = $var; + + return $this; + } + + /** + * eg "node v4.6.0". + * + * Generated from protobuf field string runtime_version = 8 [json_name = "runtimeVersion"]; + * + * @return string + */ + public function getRuntimeVersion() + { + return $this->runtime_version; + } + + /** + * eg "node v4.6.0". + * + * Generated from protobuf field string runtime_version = 8 [json_name = "runtimeVersion"]; + * + * @param string $var + * + * @return $this + */ + public function setRuntimeVersion($var) + { + GPBUtil::checkString($var, true); + $this->runtime_version = $var; + + return $this; + } + + /** + * eg "Linux box 4.6.5-1-ec2 #1 SMP Mon Aug 1 02:31:38 PDT 2016 x86_64 GNU/Linux". + * + * Generated from protobuf field string uname = 9 [json_name = "uname"]; + * + * @return string + */ + public function getUname() + { + return $this->uname; + } + + /** + * eg "Linux box 4.6.5-1-ec2 #1 SMP Mon Aug 1 02:31:38 PDT 2016 x86_64 GNU/Linux". + * + * Generated from protobuf field string uname = 9 [json_name = "uname"]; + * + * @param string $var + * + * @return $this + */ + public function setUname($var) + { + GPBUtil::checkString($var, true); + $this->uname = $var; + + return $this; + } + + /** + * An id that is used to represent the schema to Apollo Graph Manager + * Using this in place of what used to be schema_hash, since that is no longer + * attached to a schema in the backend. + * + * Generated from protobuf field string executable_schema_id = 11 [json_name = "executableSchemaId"]; + * + * @return string + */ + public function getExecutableSchemaId() + { + return $this->executable_schema_id; + } + + /** + * An id that is used to represent the schema to Apollo Graph Manager + * Using this in place of what used to be schema_hash, since that is no longer + * attached to a schema in the backend. + * + * Generated from protobuf field string executable_schema_id = 11 [json_name = "executableSchemaId"]; + * + * @param string $var + * + * @return $this + */ + public function setExecutableSchemaId($var) + { + GPBUtil::checkString($var, true); + $this->executable_schema_id = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/StatsContext.php b/src/Tracing/FederatedTracing/Proto/StatsContext.php new file mode 100644 index 0000000000..b619a085db --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/StatsContext.php @@ -0,0 +1,146 @@ +StatsContext + */ +class StatsContext extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field string client_name = 2 [json_name = "clientName"]; */ + protected $client_name = ''; + + /** Generated from protobuf field string client_version = 3 [json_name = "clientVersion"]; */ + protected $client_version = ''; + + /** Generated from protobuf field string operation_type = 4 [json_name = "operationType"]; */ + protected $operation_type = ''; + + /** Generated from protobuf field string operation_subtype = 5 [json_name = "operationSubtype"]; */ + protected $operation_subtype = ''; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $client_name + * @var string $client_version + * @var string $operation_type + * @var string $operation_subtype + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field string client_name = 2 [json_name = "clientName"];. + * + * @return string + */ + public function getClientName() + { + return $this->client_name; + } + + /** + * Generated from protobuf field string client_name = 2 [json_name = "clientName"];. + * + * @param string $var + * + * @return $this + */ + public function setClientName($var) + { + GPBUtil::checkString($var, true); + $this->client_name = $var; + + return $this; + } + + /** + * Generated from protobuf field string client_version = 3 [json_name = "clientVersion"];. + * + * @return string + */ + public function getClientVersion() + { + return $this->client_version; + } + + /** + * Generated from protobuf field string client_version = 3 [json_name = "clientVersion"];. + * + * @param string $var + * + * @return $this + */ + public function setClientVersion($var) + { + GPBUtil::checkString($var, true); + $this->client_version = $var; + + return $this; + } + + /** + * Generated from protobuf field string operation_type = 4 [json_name = "operationType"];. + * + * @return string + */ + public function getOperationType() + { + return $this->operation_type; + } + + /** + * Generated from protobuf field string operation_type = 4 [json_name = "operationType"];. + * + * @param string $var + * + * @return $this + */ + public function setOperationType($var) + { + GPBUtil::checkString($var, true); + $this->operation_type = $var; + + return $this; + } + + /** + * Generated from protobuf field string operation_subtype = 5 [json_name = "operationSubtype"];. + * + * @return string + */ + public function getOperationSubtype() + { + return $this->operation_subtype; + } + + /** + * Generated from protobuf field string operation_subtype = 5 [json_name = "operationSubtype"];. + * + * @param string $var + * + * @return $this + */ + public function setOperationSubtype($var) + { + GPBUtil::checkString($var, true); + $this->operation_subtype = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/Trace.php b/src/Tracing/FederatedTracing/Proto/Trace.php new file mode 100644 index 0000000000..a8fab65164 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace.php @@ -0,0 +1,981 @@ +Trace. + */ +class Trace extends \Google\Protobuf\Internal\Message +{ + /** + * Wallclock time when the trace began. + * + * Generated from protobuf field .google.protobuf.Timestamp start_time = 4 [json_name = "startTime"]; + */ + protected $start_time; + + /** + * Wallclock time when the trace ended. + * + * Generated from protobuf field .google.protobuf.Timestamp end_time = 3 [json_name = "endTime"]; + */ + protected $end_time; + + /** + * High precision duration of the trace; may not equal end_time-start_time + * (eg, if your machine's clock changed during the trace). + * + * Generated from protobuf field uint64 duration_ns = 11 [json_name = "durationNs"]; + */ + protected $duration_ns = 0; + + /** + * A tree containing information about all resolvers run directly by this + * service, including errors. + * + * Generated from protobuf field .Trace.Node root = 14 [json_name = "root"]; + */ + protected $root; + + /** + * If this is true, the trace is potentially missing some nodes that were + * present on the query plan. This can happen if the trace span buffer used + * in the Router fills up and some spans have to be dropped. In these cases + * the overall trace timing will still be correct, but the trace data could + * be missing some referenced or executed fields, and some nodes may be + * missing. If this is true we should display a warning to the user when they + * view the trace in Explorer. + * + * Generated from protobuf field bool is_incomplete = 33 [json_name = "isIncomplete"]; + */ + protected $is_incomplete = false; + + /** + * In addition to details.raw_query, we include a "signature" of the query, + * which can be normalized: for example, you may want to discard aliases, drop + * unused operations and fragments, sort fields, etc. The most important thing + * here is that the signature match the signature in StatsReports. In + * StatsReports signatures show up as the key in the per_query map (with the + * operation name prepended). The signature should be a valid GraphQL query. + * All traces must have a signature; if this Trace is in a FullTracesReport + * that signature is in the key of traces_per_query rather than in this field. + * Engineproxy provides the signature in legacy_signature_needs_resigning + * instead. + * + * Generated from protobuf field string signature = 19 [json_name = "signature"]; + */ + protected $signature = ''; + + /** + * Optional: when GraphQL parsing or validation against the GraphQL schema fails, these fields + * can include reference to the operation being sent for users to dig into the set of operations + * that are failing validation. + * + * Generated from protobuf field string unexecutedOperationBody = 27 [json_name = "unexecutedOperationBody"]; + */ + protected $unexecutedOperationBody = ''; + + /** Generated from protobuf field string unexecutedOperationName = 28 [json_name = "unexecutedOperationName"]; */ + protected $unexecutedOperationName = ''; + + /** Generated from protobuf field .Trace.Details details = 6 [json_name = "details"]; */ + protected $details; + + /** Generated from protobuf field string client_name = 7 [json_name = "clientName"]; */ + protected $client_name = ''; + + /** Generated from protobuf field string client_version = 8 [json_name = "clientVersion"]; */ + protected $client_version = ''; + + /** Generated from protobuf field string operation_type = 35 [json_name = "operationType"]; */ + protected $operation_type = ''; + + /** Generated from protobuf field string operation_subtype = 36 [json_name = "operationSubtype"]; */ + protected $operation_subtype = ''; + + /** Generated from protobuf field .Trace.HTTP http = 10 [json_name = "http"]; */ + protected $http; + + /** Generated from protobuf field .Trace.CachePolicy cache_policy = 18 [json_name = "cachePolicy"]; */ + protected $cache_policy; + + /** + * If this Trace was created by a Router/Gateway, this is the query plan, including + * sub-Traces for subgraphs. Note that the 'root' tree on the + * top-level Trace won't contain any resolvers (though it could contain errors + * that occurred in the Router/Gateway itself). + * + * Generated from protobuf field .Trace.QueryPlanNode query_plan = 26 [json_name = "queryPlan"]; + */ + protected $query_plan; + + /** + * Was this response served from a full query response cache? (In that case + * the node tree will have no resolvers.). + * + * Generated from protobuf field bool full_query_cache_hit = 20 [json_name = "fullQueryCacheHit"]; + */ + protected $full_query_cache_hit = false; + + /** + * Was this query specified successfully as a persisted query hash? + * + * Generated from protobuf field bool persisted_query_hit = 21 [json_name = "persistedQueryHit"]; + */ + protected $persisted_query_hit = false; + + /** + * Did this query contain both a full query string and a persisted query hash? + * (This typically means that a previous request was rejected as an unknown + * persisted query.). + * + * Generated from protobuf field bool persisted_query_register = 22 [json_name = "persistedQueryRegister"]; + */ + protected $persisted_query_register = false; + + /** + * Was this operation registered and a part of the safelist? + * + * Generated from protobuf field bool registered_operation = 24 [json_name = "registeredOperation"]; + */ + protected $registered_operation = false; + + /** + * Was this operation forbidden due to lack of safelisting? + * + * Generated from protobuf field bool forbidden_operation = 25 [json_name = "forbiddenOperation"]; + */ + protected $forbidden_operation = false; + + /** + * Some servers don't do field-level instrumentation for every request and assign + * each request a "weight" for each request that they do instrument. When this + * trace is aggregated into field usage stats, it should count as this value + * towards the estimated_execution_count rather than just 1. This value should + * typically be at least 1. + * 0 is treated as 1 for backwards compatibility. + * + * Generated from protobuf field double field_execution_weight = 31 [json_name = "fieldExecutionWeight"]; + */ + protected $field_execution_weight = 0.0; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var \Google\Protobuf\Timestamp $start_time + * Wallclock time when the trace began. + * @var \Google\Protobuf\Timestamp $end_time + * Wallclock time when the trace ended. + * @var int|string $duration_ns + * High precision duration of the trace; may not equal end_time-start_time + * (eg, if your machine's clock changed during the trace). + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Node $root + * A tree containing information about all resolvers run directly by this + * service, including errors. + * @var bool $is_incomplete + * If this is true, the trace is potentially missing some nodes that were + * present on the query plan. This can happen if the trace span buffer used + * in the Router fills up and some spans have to be dropped. In these cases + * the overall trace timing will still be correct, but the trace data could + * be missing some referenced or executed fields, and some nodes may be + * missing. If this is true we should display a warning to the user when they + * view the trace in Explorer. + * @var string $signature + * In addition to details.raw_query, we include a "signature" of the query, + * which can be normalized: for example, you may want to discard aliases, drop + * unused operations and fragments, sort fields, etc. The most important thing + * here is that the signature match the signature in StatsReports. In + * StatsReports signatures show up as the key in the per_query map (with the + * operation name prepended). The signature should be a valid GraphQL query. + * All traces must have a signature; if this Trace is in a FullTracesReport + * that signature is in the key of traces_per_query rather than in this field. + * Engineproxy provides the signature in legacy_signature_needs_resigning + * instead. + * @var string $unexecutedOperationBody + * Optional: when GraphQL parsing or validation against the GraphQL schema fails, these fields + * can include reference to the operation being sent for users to dig into the set of operations + * that are failing validation. + * @var string $unexecutedOperationName + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Details $details + * @var string $client_name + * @var string $client_version + * @var string $operation_type + * @var string $operation_subtype + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\HTTP $http + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\CachePolicy $cache_policy + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $query_plan + * If this Trace was created by a Router/Gateway, this is the query plan, including + * sub-Traces for subgraphs. Note that the 'root' tree on the + * top-level Trace won't contain any resolvers (though it could contain errors + * that occurred in the Router/Gateway itself). + * @var bool $full_query_cache_hit + * Was this response served from a full query response cache? (In that case + * the node tree will have no resolvers.) + * @var bool $persisted_query_hit + * Was this query specified successfully as a persisted query hash? + * @var bool $persisted_query_register + * Did this query contain both a full query string and a persisted query hash? + * (This typically means that a previous request was rejected as an unknown + * persisted query.) + * @var bool $registered_operation + * Was this operation registered and a part of the safelist? + * @var bool $forbidden_operation + * Was this operation forbidden due to lack of safelisting? + * @var float $field_execution_weight + * Some servers don't do field-level instrumentation for every request and assign + * each request a "weight" for each request that they do instrument. When this + * trace is aggregated into field usage stats, it should count as this value + * towards the estimated_execution_count rather than just 1. This value should + * typically be at least 1. + * 0 is treated as 1 for backwards compatibility. + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Wallclock time when the trace began. + * + * Generated from protobuf field .google.protobuf.Timestamp start_time = 4 [json_name = "startTime"]; + * + * @return \Google\Protobuf\Timestamp|null + */ + public function getStartTime() + { + return $this->start_time; + } + + public function hasStartTime() + { + return isset($this->start_time); + } + + public function clearStartTime() + { + unset($this->start_time); + } + + /** + * Wallclock time when the trace began. + * + * Generated from protobuf field .google.protobuf.Timestamp start_time = 4 [json_name = "startTime"]; + * + * @param \Google\Protobuf\Timestamp $var + * + * @return $this + */ + public function setStartTime($var) + { + GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class); + $this->start_time = $var; + + return $this; + } + + /** + * Wallclock time when the trace ended. + * + * Generated from protobuf field .google.protobuf.Timestamp end_time = 3 [json_name = "endTime"]; + * + * @return \Google\Protobuf\Timestamp|null + */ + public function getEndTime() + { + return $this->end_time; + } + + public function hasEndTime() + { + return isset($this->end_time); + } + + public function clearEndTime() + { + unset($this->end_time); + } + + /** + * Wallclock time when the trace ended. + * + * Generated from protobuf field .google.protobuf.Timestamp end_time = 3 [json_name = "endTime"]; + * + * @param \Google\Protobuf\Timestamp $var + * + * @return $this + */ + public function setEndTime($var) + { + GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class); + $this->end_time = $var; + + return $this; + } + + /** + * High precision duration of the trace; may not equal end_time-start_time + * (eg, if your machine's clock changed during the trace). + * + * Generated from protobuf field uint64 duration_ns = 11 [json_name = "durationNs"]; + * + * @return int|string + */ + public function getDurationNs() + { + return $this->duration_ns; + } + + /** + * High precision duration of the trace; may not equal end_time-start_time + * (eg, if your machine's clock changed during the trace). + * + * Generated from protobuf field uint64 duration_ns = 11 [json_name = "durationNs"]; + * + * @param int|string $var + * + * @return $this + */ + public function setDurationNs($var) + { + GPBUtil::checkUint64($var); + $this->duration_ns = $var; + + return $this; + } + + /** + * A tree containing information about all resolvers run directly by this + * service, including errors. + * + * Generated from protobuf field .Trace.Node root = 14 [json_name = "root"]; + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Node|null + */ + public function getRoot() + { + return $this->root; + } + + public function hasRoot() + { + return isset($this->root); + } + + public function clearRoot() + { + unset($this->root); + } + + /** + * A tree containing information about all resolvers run directly by this + * service, including errors. + * + * Generated from protobuf field .Trace.Node root = 14 [json_name = "root"]; + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Node $var + * + * @return $this + */ + public function setRoot($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Node::class); + $this->root = $var; + + return $this; + } + + /** + * If this is true, the trace is potentially missing some nodes that were + * present on the query plan. This can happen if the trace span buffer used + * in the Router fills up and some spans have to be dropped. In these cases + * the overall trace timing will still be correct, but the trace data could + * be missing some referenced or executed fields, and some nodes may be + * missing. If this is true we should display a warning to the user when they + * view the trace in Explorer. + * + * Generated from protobuf field bool is_incomplete = 33 [json_name = "isIncomplete"]; + * + * @return bool + */ + public function getIsIncomplete() + { + return $this->is_incomplete; + } + + /** + * If this is true, the trace is potentially missing some nodes that were + * present on the query plan. This can happen if the trace span buffer used + * in the Router fills up and some spans have to be dropped. In these cases + * the overall trace timing will still be correct, but the trace data could + * be missing some referenced or executed fields, and some nodes may be + * missing. If this is true we should display a warning to the user when they + * view the trace in Explorer. + * + * Generated from protobuf field bool is_incomplete = 33 [json_name = "isIncomplete"]; + * + * @param bool $var + * + * @return $this + */ + public function setIsIncomplete($var) + { + GPBUtil::checkBool($var); + $this->is_incomplete = $var; + + return $this; + } + + /** + * In addition to details.raw_query, we include a "signature" of the query, + * which can be normalized: for example, you may want to discard aliases, drop + * unused operations and fragments, sort fields, etc. The most important thing + * here is that the signature match the signature in StatsReports. In + * StatsReports signatures show up as the key in the per_query map (with the + * operation name prepended). The signature should be a valid GraphQL query. + * All traces must have a signature; if this Trace is in a FullTracesReport + * that signature is in the key of traces_per_query rather than in this field. + * Engineproxy provides the signature in legacy_signature_needs_resigning + * instead. + * + * Generated from protobuf field string signature = 19 [json_name = "signature"]; + * + * @return string + */ + public function getSignature() + { + return $this->signature; + } + + /** + * In addition to details.raw_query, we include a "signature" of the query, + * which can be normalized: for example, you may want to discard aliases, drop + * unused operations and fragments, sort fields, etc. The most important thing + * here is that the signature match the signature in StatsReports. In + * StatsReports signatures show up as the key in the per_query map (with the + * operation name prepended). The signature should be a valid GraphQL query. + * All traces must have a signature; if this Trace is in a FullTracesReport + * that signature is in the key of traces_per_query rather than in this field. + * Engineproxy provides the signature in legacy_signature_needs_resigning + * instead. + * + * Generated from protobuf field string signature = 19 [json_name = "signature"]; + * + * @param string $var + * + * @return $this + */ + public function setSignature($var) + { + GPBUtil::checkString($var, true); + $this->signature = $var; + + return $this; + } + + /** + * Optional: when GraphQL parsing or validation against the GraphQL schema fails, these fields + * can include reference to the operation being sent for users to dig into the set of operations + * that are failing validation. + * + * Generated from protobuf field string unexecutedOperationBody = 27 [json_name = "unexecutedOperationBody"]; + * + * @return string + */ + public function getUnexecutedOperationBody() + { + return $this->unexecutedOperationBody; + } + + /** + * Optional: when GraphQL parsing or validation against the GraphQL schema fails, these fields + * can include reference to the operation being sent for users to dig into the set of operations + * that are failing validation. + * + * Generated from protobuf field string unexecutedOperationBody = 27 [json_name = "unexecutedOperationBody"]; + * + * @param string $var + * + * @return $this + */ + public function setUnexecutedOperationBody($var) + { + GPBUtil::checkString($var, true); + $this->unexecutedOperationBody = $var; + + return $this; + } + + /** + * Generated from protobuf field string unexecutedOperationName = 28 [json_name = "unexecutedOperationName"];. + * + * @return string + */ + public function getUnexecutedOperationName() + { + return $this->unexecutedOperationName; + } + + /** + * Generated from protobuf field string unexecutedOperationName = 28 [json_name = "unexecutedOperationName"];. + * + * @param string $var + * + * @return $this + */ + public function setUnexecutedOperationName($var) + { + GPBUtil::checkString($var, true); + $this->unexecutedOperationName = $var; + + return $this; + } + + /** + * Generated from protobuf field .Trace.Details details = 6 [json_name = "details"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Details|null + */ + public function getDetails() + { + return $this->details; + } + + public function hasDetails() + { + return isset($this->details); + } + + public function clearDetails() + { + unset($this->details); + } + + /** + * Generated from protobuf field .Trace.Details details = 6 [json_name = "details"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Details $var + * + * @return $this + */ + public function setDetails($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Details::class); + $this->details = $var; + + return $this; + } + + /** + * Generated from protobuf field string client_name = 7 [json_name = "clientName"];. + * + * @return string + */ + public function getClientName() + { + return $this->client_name; + } + + /** + * Generated from protobuf field string client_name = 7 [json_name = "clientName"];. + * + * @param string $var + * + * @return $this + */ + public function setClientName($var) + { + GPBUtil::checkString($var, true); + $this->client_name = $var; + + return $this; + } + + /** + * Generated from protobuf field string client_version = 8 [json_name = "clientVersion"];. + * + * @return string + */ + public function getClientVersion() + { + return $this->client_version; + } + + /** + * Generated from protobuf field string client_version = 8 [json_name = "clientVersion"];. + * + * @param string $var + * + * @return $this + */ + public function setClientVersion($var) + { + GPBUtil::checkString($var, true); + $this->client_version = $var; + + return $this; + } + + /** + * Generated from protobuf field string operation_type = 35 [json_name = "operationType"];. + * + * @return string + */ + public function getOperationType() + { + return $this->operation_type; + } + + /** + * Generated from protobuf field string operation_type = 35 [json_name = "operationType"];. + * + * @param string $var + * + * @return $this + */ + public function setOperationType($var) + { + GPBUtil::checkString($var, true); + $this->operation_type = $var; + + return $this; + } + + /** + * Generated from protobuf field string operation_subtype = 36 [json_name = "operationSubtype"];. + * + * @return string + */ + public function getOperationSubtype() + { + return $this->operation_subtype; + } + + /** + * Generated from protobuf field string operation_subtype = 36 [json_name = "operationSubtype"];. + * + * @param string $var + * + * @return $this + */ + public function setOperationSubtype($var) + { + GPBUtil::checkString($var, true); + $this->operation_subtype = $var; + + return $this; + } + + /** + * Generated from protobuf field .Trace.HTTP http = 10 [json_name = "http"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\HTTP|null + */ + public function getHttp() + { + return $this->http; + } + + public function hasHttp() + { + return isset($this->http); + } + + public function clearHttp() + { + unset($this->http); + } + + /** + * Generated from protobuf field .Trace.HTTP http = 10 [json_name = "http"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\HTTP $var + * + * @return $this + */ + public function setHttp($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\HTTP::class); + $this->http = $var; + + return $this; + } + + /** + * Generated from protobuf field .Trace.CachePolicy cache_policy = 18 [json_name = "cachePolicy"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\CachePolicy|null + */ + public function getCachePolicy() + { + return $this->cache_policy; + } + + public function hasCachePolicy() + { + return isset($this->cache_policy); + } + + public function clearCachePolicy() + { + unset($this->cache_policy); + } + + /** + * Generated from protobuf field .Trace.CachePolicy cache_policy = 18 [json_name = "cachePolicy"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\CachePolicy $var + * + * @return $this + */ + public function setCachePolicy($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\CachePolicy::class); + $this->cache_policy = $var; + + return $this; + } + + /** + * If this Trace was created by a Router/Gateway, this is the query plan, including + * sub-Traces for subgraphs. Note that the 'root' tree on the + * top-level Trace won't contain any resolvers (though it could contain errors + * that occurred in the Router/Gateway itself). + * + * Generated from protobuf field .Trace.QueryPlanNode query_plan = 26 [json_name = "queryPlan"]; + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode|null + */ + public function getQueryPlan() + { + return $this->query_plan; + } + + public function hasQueryPlan() + { + return isset($this->query_plan); + } + + public function clearQueryPlan() + { + unset($this->query_plan); + } + + /** + * If this Trace was created by a Router/Gateway, this is the query plan, including + * sub-Traces for subgraphs. Note that the 'root' tree on the + * top-level Trace won't contain any resolvers (though it could contain errors + * that occurred in the Router/Gateway itself). + * + * Generated from protobuf field .Trace.QueryPlanNode query_plan = 26 [json_name = "queryPlan"]; + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $var + * + * @return $this + */ + public function setQueryPlan($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode::class); + $this->query_plan = $var; + + return $this; + } + + /** + * Was this response served from a full query response cache? (In that case + * the node tree will have no resolvers.). + * + * Generated from protobuf field bool full_query_cache_hit = 20 [json_name = "fullQueryCacheHit"]; + * + * @return bool + */ + public function getFullQueryCacheHit() + { + return $this->full_query_cache_hit; + } + + /** + * Was this response served from a full query response cache? (In that case + * the node tree will have no resolvers.). + * + * Generated from protobuf field bool full_query_cache_hit = 20 [json_name = "fullQueryCacheHit"]; + * + * @param bool $var + * + * @return $this + */ + public function setFullQueryCacheHit($var) + { + GPBUtil::checkBool($var); + $this->full_query_cache_hit = $var; + + return $this; + } + + /** + * Was this query specified successfully as a persisted query hash? + * + * Generated from protobuf field bool persisted_query_hit = 21 [json_name = "persistedQueryHit"]; + * + * @return bool + */ + public function getPersistedQueryHit() + { + return $this->persisted_query_hit; + } + + /** + * Was this query specified successfully as a persisted query hash? + * + * Generated from protobuf field bool persisted_query_hit = 21 [json_name = "persistedQueryHit"]; + * + * @param bool $var + * + * @return $this + */ + public function setPersistedQueryHit($var) + { + GPBUtil::checkBool($var); + $this->persisted_query_hit = $var; + + return $this; + } + + /** + * Did this query contain both a full query string and a persisted query hash? + * (This typically means that a previous request was rejected as an unknown + * persisted query.). + * + * Generated from protobuf field bool persisted_query_register = 22 [json_name = "persistedQueryRegister"]; + * + * @return bool + */ + public function getPersistedQueryRegister() + { + return $this->persisted_query_register; + } + + /** + * Did this query contain both a full query string and a persisted query hash? + * (This typically means that a previous request was rejected as an unknown + * persisted query.). + * + * Generated from protobuf field bool persisted_query_register = 22 [json_name = "persistedQueryRegister"]; + * + * @param bool $var + * + * @return $this + */ + public function setPersistedQueryRegister($var) + { + GPBUtil::checkBool($var); + $this->persisted_query_register = $var; + + return $this; + } + + /** + * Was this operation registered and a part of the safelist? + * + * Generated from protobuf field bool registered_operation = 24 [json_name = "registeredOperation"]; + * + * @return bool + */ + public function getRegisteredOperation() + { + return $this->registered_operation; + } + + /** + * Was this operation registered and a part of the safelist? + * + * Generated from protobuf field bool registered_operation = 24 [json_name = "registeredOperation"]; + * + * @param bool $var + * + * @return $this + */ + public function setRegisteredOperation($var) + { + GPBUtil::checkBool($var); + $this->registered_operation = $var; + + return $this; + } + + /** + * Was this operation forbidden due to lack of safelisting? + * + * Generated from protobuf field bool forbidden_operation = 25 [json_name = "forbiddenOperation"]; + * + * @return bool + */ + public function getForbiddenOperation() + { + return $this->forbidden_operation; + } + + /** + * Was this operation forbidden due to lack of safelisting? + * + * Generated from protobuf field bool forbidden_operation = 25 [json_name = "forbiddenOperation"]; + * + * @param bool $var + * + * @return $this + */ + public function setForbiddenOperation($var) + { + GPBUtil::checkBool($var); + $this->forbidden_operation = $var; + + return $this; + } + + /** + * Some servers don't do field-level instrumentation for every request and assign + * each request a "weight" for each request that they do instrument. When this + * trace is aggregated into field usage stats, it should count as this value + * towards the estimated_execution_count rather than just 1. This value should + * typically be at least 1. + * 0 is treated as 1 for backwards compatibility. + * + * Generated from protobuf field double field_execution_weight = 31 [json_name = "fieldExecutionWeight"]; + * + * @return float + */ + public function getFieldExecutionWeight() + { + return $this->field_execution_weight; + } + + /** + * Some servers don't do field-level instrumentation for every request and assign + * each request a "weight" for each request that they do instrument. When this + * trace is aggregated into field usage stats, it should count as this value + * towards the estimated_execution_count rather than just 1. This value should + * typically be at least 1. + * 0 is treated as 1 for backwards compatibility. + * + * Generated from protobuf field double field_execution_weight = 31 [json_name = "fieldExecutionWeight"]; + * + * @param float $var + * + * @return $this + */ + public function setFieldExecutionWeight($var) + { + GPBUtil::checkDouble($var); + $this->field_execution_weight = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/Trace/CachePolicy.php b/src/Tracing/FederatedTracing/Proto/Trace/CachePolicy.php new file mode 100644 index 0000000000..cd5def6faa --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/CachePolicy.php @@ -0,0 +1,97 @@ +Trace.CachePolicy. + */ +class CachePolicy extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field .Trace.CachePolicy.Scope scope = 1 [json_name = "scope"]; */ + protected $scope = 0; + + /** + * use 0 for absent, -1 for 0. + * + * Generated from protobuf field int64 max_age_ns = 2 [json_name = "maxAgeNs"]; + */ + protected $max_age_ns = 0; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var int $scope + * @var int|string $max_age_ns + * use 0 for absent, -1 for 0 + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field .Trace.CachePolicy.Scope scope = 1 [json_name = "scope"];. + * + * @return int + */ + public function getScope() + { + return $this->scope; + } + + /** + * Generated from protobuf field .Trace.CachePolicy.Scope scope = 1 [json_name = "scope"];. + * + * @param int $var + * + * @return $this + */ + public function setScope($var) + { + GPBUtil::checkEnum($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\CachePolicy\Scope::class); + $this->scope = $var; + + return $this; + } + + /** + * use 0 for absent, -1 for 0. + * + * Generated from protobuf field int64 max_age_ns = 2 [json_name = "maxAgeNs"]; + * + * @return int|string + */ + public function getMaxAgeNs() + { + return $this->max_age_ns; + } + + /** + * use 0 for absent, -1 for 0. + * + * Generated from protobuf field int64 max_age_ns = 2 [json_name = "maxAgeNs"]; + * + * @param int|string $var + * + * @return $this + */ + public function setMaxAgeNs($var) + { + GPBUtil::checkInt64($var); + $this->max_age_ns = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(CachePolicy::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_CachePolicy::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/CachePolicy/Scope.php b/src/Tracing/FederatedTracing/Proto/Trace/CachePolicy/Scope.php new file mode 100644 index 0000000000..380232793f --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/CachePolicy/Scope.php @@ -0,0 +1,59 @@ +Trace.CachePolicy.Scope. + */ +class Scope +{ + /** Generated from protobuf enum UNKNOWN = 0; */ + public const UNKNOWN = 0; + /** Generated from protobuf enum PUBLIC = 1; */ + public const PBPUBLIC = 1; + /** Generated from protobuf enum PRIVATE = 2; */ + public const PBPRIVATE = 2; + + private static $valueToName = [ + self::UNKNOWN => 'UNKNOWN', + self::PBPUBLIC => 'PUBLIC', + self::PBPRIVATE => 'PRIVATE', + ]; + + public static function name($value) + { + if (! isset(self::$valueToName[$value])) { + throw new \UnexpectedValueException(sprintf( + 'Enum %s has no name defined for value %s', + __CLASS__, + $value, + )); + } + + return self::$valueToName[$value]; + } + + public static function value($name) + { + $const = __CLASS__ . '::' . strtoupper($name); + if (! defined($const)) { + $pbconst = __CLASS__ . '::PB' . strtoupper($name); + if (! defined($pbconst)) { + throw new \UnexpectedValueException(sprintf( + 'Enum %s has no value defined for name %s', + __CLASS__, + $name, + )); + } + + return constant($pbconst); + } + + return constant($const); + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(Scope::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_CachePolicy_Scope::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/Details.php b/src/Tracing/FederatedTracing/Proto/Trace/Details.php new file mode 100644 index 0000000000..17f52859ec --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/Details.php @@ -0,0 +1,126 @@ +Trace.Details. + */ +class Details extends \Google\Protobuf\Internal\Message +{ + /** + * The variables associated with this query (unless the reporting agent is + * configured to keep them all private). Values are JSON: ie, strings are + * enclosed in double quotes, etc. The value of a private variable is + * the empty string. + * + * Generated from protobuf field map variables_json = 4 [json_name = "variablesJson"]; + */ + private $variables_json; + + /** + * This is deprecated and only used for legacy applications + * don't include this in traces inside a FullTracesReport; the operation + * name for these traces comes from the key of the traces_per_query map. + * + * Generated from protobuf field string operation_name = 3 [json_name = "operationName"]; + */ + protected $operation_name = ''; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array|\Google\Protobuf\Internal\MapField $variables_json + * The variables associated with this query (unless the reporting agent is + * configured to keep them all private). Values are JSON: ie, strings are + * enclosed in double quotes, etc. The value of a private variable is + * the empty string. + * @var string $operation_name + * This is deprecated and only used for legacy applications + * don't include this in traces inside a FullTracesReport; the operation + * name for these traces comes from the key of the traces_per_query map. + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * The variables associated with this query (unless the reporting agent is + * configured to keep them all private). Values are JSON: ie, strings are + * enclosed in double quotes, etc. The value of a private variable is + * the empty string. + * + * Generated from protobuf field map variables_json = 4 [json_name = "variablesJson"]; + * + * @return \Google\Protobuf\Internal\MapField + */ + public function getVariablesJson() + { + return $this->variables_json; + } + + /** + * The variables associated with this query (unless the reporting agent is + * configured to keep them all private). Values are JSON: ie, strings are + * enclosed in double quotes, etc. The value of a private variable is + * the empty string. + * + * Generated from protobuf field map variables_json = 4 [json_name = "variablesJson"]; + * + * @param array|\Google\Protobuf\Internal\MapField $var + * + * @return $this + */ + public function setVariablesJson($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::STRING); + $this->variables_json = $arr; + + return $this; + } + + /** + * This is deprecated and only used for legacy applications + * don't include this in traces inside a FullTracesReport; the operation + * name for these traces comes from the key of the traces_per_query map. + * + * Generated from protobuf field string operation_name = 3 [json_name = "operationName"]; + * + * @return string + */ + public function getOperationName() + { + return $this->operation_name; + } + + /** + * This is deprecated and only used for legacy applications + * don't include this in traces inside a FullTracesReport; the operation + * name for these traces comes from the key of the traces_per_query map. + * + * Generated from protobuf field string operation_name = 3 [json_name = "operationName"]; + * + * @param string $var + * + * @return $this + */ + public function setOperationName($var) + { + GPBUtil::checkString($var, true); + $this->operation_name = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(Details::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_Details::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/Error.php b/src/Tracing/FederatedTracing/Proto/Trace/Error.php new file mode 100644 index 0000000000..ec45247b3f --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/Error.php @@ -0,0 +1,155 @@ +Trace.Error. + */ +class Error extends \Google\Protobuf\Internal\Message +{ + /** + * required. + * + * Generated from protobuf field string message = 1 [json_name = "message"]; + */ + protected $message = ''; + + /** Generated from protobuf field repeated .Trace.Location location = 2 [json_name = "location"]; */ + private $location; + + /** Generated from protobuf field uint64 time_ns = 3 [json_name = "timeNs"]; */ + protected $time_ns = 0; + + /** Generated from protobuf field string json = 4 [json_name = "json"]; */ + protected $json = ''; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $message + * required + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Location>|\Google\Protobuf\Internal\RepeatedField $location + * @var int|string $time_ns + * @var string $json + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * required. + * + * Generated from protobuf field string message = 1 [json_name = "message"]; + * + * @return string + */ + public function getMessage() + { + return $this->message; + } + + /** + * required. + * + * Generated from protobuf field string message = 1 [json_name = "message"]; + * + * @param string $var + * + * @return $this + */ + public function setMessage($var) + { + GPBUtil::checkString($var, true); + $this->message = $var; + + return $this; + } + + /** + * Generated from protobuf field repeated .Trace.Location location = 2 [json_name = "location"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getLocation() + { + return $this->location; + } + + /** + * Generated from protobuf field repeated .Trace.Location location = 2 [json_name = "location"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Location>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setLocation($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Location::class); + $this->location = $arr; + + return $this; + } + + /** + * Generated from protobuf field uint64 time_ns = 3 [json_name = "timeNs"];. + * + * @return int|string + */ + public function getTimeNs() + { + return $this->time_ns; + } + + /** + * Generated from protobuf field uint64 time_ns = 3 [json_name = "timeNs"];. + * + * @param int|string $var + * + * @return $this + */ + public function setTimeNs($var) + { + GPBUtil::checkUint64($var); + $this->time_ns = $var; + + return $this; + } + + /** + * Generated from protobuf field string json = 4 [json_name = "json"];. + * + * @return string + */ + public function getJson() + { + return $this->json; + } + + /** + * Generated from protobuf field string json = 4 [json_name = "json"];. + * + * @param string $var + * + * @return $this + */ + public function setJson($var) + { + GPBUtil::checkString($var, true); + $this->json = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(Error::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_Error::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/HTTP.php b/src/Tracing/FederatedTracing/Proto/Trace/HTTP.php new file mode 100644 index 0000000000..4090cac21c --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/HTTP.php @@ -0,0 +1,155 @@ +Trace.HTTP. + */ +class HTTP extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field .Trace.HTTP.Method method = 1 [json_name = "method"]; */ + protected $method = 0; + + /** + * Should exclude manual blacklist ("Auth" by default). + * + * Generated from protobuf field map request_headers = 4 [json_name = "requestHeaders"]; + */ + private $request_headers; + + /** Generated from protobuf field map response_headers = 5 [json_name = "responseHeaders"]; */ + private $response_headers; + + /** Generated from protobuf field uint32 status_code = 6 [json_name = "statusCode"]; */ + protected $status_code = 0; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var int $method + * @var array|\Google\Protobuf\Internal\MapField $request_headers + * Should exclude manual blacklist ("Auth" by default) + * @var array|\Google\Protobuf\Internal\MapField $response_headers + * @var int $status_code + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field .Trace.HTTP.Method method = 1 [json_name = "method"];. + * + * @return int + */ + public function getMethod() + { + return $this->method; + } + + /** + * Generated from protobuf field .Trace.HTTP.Method method = 1 [json_name = "method"];. + * + * @param int $var + * + * @return $this + */ + public function setMethod($var) + { + GPBUtil::checkEnum($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\HTTP\Method::class); + $this->method = $var; + + return $this; + } + + /** + * Should exclude manual blacklist ("Auth" by default). + * + * Generated from protobuf field map request_headers = 4 [json_name = "requestHeaders"]; + * + * @return \Google\Protobuf\Internal\MapField + */ + public function getRequestHeaders() + { + return $this->request_headers; + } + + /** + * Should exclude manual blacklist ("Auth" by default). + * + * Generated from protobuf field map request_headers = 4 [json_name = "requestHeaders"]; + * + * @param array|\Google\Protobuf\Internal\MapField $var + * + * @return $this + */ + public function setRequestHeaders($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\HTTP\Values::class); + $this->request_headers = $arr; + + return $this; + } + + /** + * Generated from protobuf field map response_headers = 5 [json_name = "responseHeaders"];. + * + * @return \Google\Protobuf\Internal\MapField + */ + public function getResponseHeaders() + { + return $this->response_headers; + } + + /** + * Generated from protobuf field map response_headers = 5 [json_name = "responseHeaders"];. + * + * @param array|\Google\Protobuf\Internal\MapField $var + * + * @return $this + */ + public function setResponseHeaders($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\HTTP\Values::class); + $this->response_headers = $arr; + + return $this; + } + + /** + * Generated from protobuf field uint32 status_code = 6 [json_name = "statusCode"];. + * + * @return int + */ + public function getStatusCode() + { + return $this->status_code; + } + + /** + * Generated from protobuf field uint32 status_code = 6 [json_name = "statusCode"];. + * + * @param int $var + * + * @return $this + */ + public function setStatusCode($var) + { + GPBUtil::checkUint32($var); + $this->status_code = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(HTTP::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_HTTP::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/HTTP/Method.php b/src/Tracing/FederatedTracing/Proto/Trace/HTTP/Method.php new file mode 100644 index 0000000000..5961ff7ff4 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/HTTP/Method.php @@ -0,0 +1,75 @@ +Trace.HTTP.Method. + */ +class Method +{ + /** Generated from protobuf enum UNKNOWN = 0; */ + public const UNKNOWN = 0; + /** Generated from protobuf enum OPTIONS = 1; */ + public const OPTIONS = 1; + /** Generated from protobuf enum GET = 2; */ + public const GET = 2; + /** Generated from protobuf enum HEAD = 3; */ + public const HEAD = 3; + /** Generated from protobuf enum POST = 4; */ + public const POST = 4; + /** Generated from protobuf enum PUT = 5; */ + public const PUT = 5; + /** Generated from protobuf enum DELETE = 6; */ + public const DELETE = 6; + /** Generated from protobuf enum TRACE = 7; */ + public const TRACE = 7; + /** Generated from protobuf enum CONNECT = 8; */ + public const CONNECT = 8; + /** Generated from protobuf enum PATCH = 9; */ + public const PATCH = 9; + + private static $valueToName = [ + self::UNKNOWN => 'UNKNOWN', + self::OPTIONS => 'OPTIONS', + self::GET => 'GET', + self::HEAD => 'HEAD', + self::POST => 'POST', + self::PUT => 'PUT', + self::DELETE => 'DELETE', + self::TRACE => 'TRACE', + self::CONNECT => 'CONNECT', + self::PATCH => 'PATCH', + ]; + + public static function name($value) + { + if (! isset(self::$valueToName[$value])) { + throw new \UnexpectedValueException(sprintf( + 'Enum %s has no name defined for value %s', + __CLASS__, + $value, + )); + } + + return self::$valueToName[$value]; + } + + public static function value($name) + { + $const = __CLASS__ . '::' . strtoupper($name); + if (! defined($const)) { + throw new \UnexpectedValueException(sprintf( + 'Enum %s has no value defined for name %s', + __CLASS__, + $name, + )); + } + + return constant($const); + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(Method::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_HTTP_Method::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/HTTP/Values.php b/src/Tracing/FederatedTracing/Proto/Trace/HTTP/Values.php new file mode 100644 index 0000000000..bfef7f64fa --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/HTTP/Values.php @@ -0,0 +1,59 @@ +Trace.HTTP.Values. + */ +class Values extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field repeated string value = 1 [json_name = "value"]; */ + private $value; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array|\Google\Protobuf\Internal\RepeatedField $value + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field repeated string value = 1 [json_name = "value"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getValue() + { + return $this->value; + } + + /** + * Generated from protobuf field repeated string value = 1 [json_name = "value"];. + * + * @param array|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setValue($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::STRING); + $this->value = $arr; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(Values::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_HTTP_Values::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/Location.php b/src/Tracing/FederatedTracing/Proto/Trace/Location.php new file mode 100644 index 0000000000..35753dad11 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/Location.php @@ -0,0 +1,88 @@ +Trace.Location. + */ +class Location extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field uint32 line = 1 [json_name = "line"]; */ + protected $line = 0; + + /** Generated from protobuf field uint32 column = 2 [json_name = "column"]; */ + protected $column = 0; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var int $line + * @var int $column + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field uint32 line = 1 [json_name = "line"];. + * + * @return int + */ + public function getLine() + { + return $this->line; + } + + /** + * Generated from protobuf field uint32 line = 1 [json_name = "line"];. + * + * @param int $var + * + * @return $this + */ + public function setLine($var) + { + GPBUtil::checkUint32($var); + $this->line = $var; + + return $this; + } + + /** + * Generated from protobuf field uint32 column = 2 [json_name = "column"];. + * + * @return int + */ + public function getColumn() + { + return $this->column; + } + + /** + * Generated from protobuf field uint32 column = 2 [json_name = "column"];. + * + * @param int $var + * + * @return $this + */ + public function setColumn($var) + { + GPBUtil::checkUint32($var); + $this->column = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(Location::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_Location::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/Node.php b/src/Tracing/FederatedTracing/Proto/Trace/Node.php new file mode 100644 index 0000000000..eeacbe9dff --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/Node.php @@ -0,0 +1,384 @@ +Trace.Node + */ +class Node extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field string original_field_name = 14 [json_name = "originalFieldName"]; */ + protected $original_field_name = ''; + + /** + * The field's return type; e.g. "String!" for User.email:String! + * + * Generated from protobuf field string type = 3 [json_name = "type"]; + */ + protected $type = ''; + + /** + * The field's parent type; e.g. "User" for User.email:String! + * + * Generated from protobuf field string parent_type = 13 [json_name = "parentType"]; + */ + protected $parent_type = ''; + + /** Generated from protobuf field .Trace.CachePolicy cache_policy = 5 [json_name = "cachePolicy"]; */ + protected $cache_policy; + + /** + * relative to the trace's start_time, in ns. + * + * Generated from protobuf field uint64 start_time = 8 [json_name = "startTime"]; + */ + protected $start_time = 0; + + /** + * relative to the trace's start_time, in ns. + * + * Generated from protobuf field uint64 end_time = 9 [json_name = "endTime"]; + */ + protected $end_time = 0; + + /** Generated from protobuf field repeated .Trace.Error error = 11 [json_name = "error"]; */ + private $error; + + /** Generated from protobuf field repeated .Trace.Node child = 12 [json_name = "child"]; */ + private $child; + + protected $id; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $response_name + * @var int $index + * @var string $original_field_name + * @var string $type + * The field's return type; e.g. "String!" for User.email:String! + * @var string $parent_type + * The field's parent type; e.g. "User" for User.email:String! + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\CachePolicy $cache_policy + * @var int|string $start_time + * relative to the trace's start_time, in ns + * @var int|string $end_time + * relative to the trace's start_time, in ns + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Error>|\Google\Protobuf\Internal\RepeatedField $error + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Node>|\Google\Protobuf\Internal\RepeatedField $child + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field string response_name = 1 [json_name = "responseName"];. + * + * @return string + */ + public function getResponseName() + { + return $this->readOneof(1); + } + + public function hasResponseName() + { + return $this->hasOneof(1); + } + + /** + * Generated from protobuf field string response_name = 1 [json_name = "responseName"];. + * + * @param string $var + * + * @return $this + */ + public function setResponseName($var) + { + GPBUtil::checkString($var, true); + $this->writeOneof(1, $var); + + return $this; + } + + /** + * Generated from protobuf field uint32 index = 2 [json_name = "index"];. + * + * @return int + */ + public function getIndex() + { + return $this->readOneof(2); + } + + public function hasIndex() + { + return $this->hasOneof(2); + } + + /** + * Generated from protobuf field uint32 index = 2 [json_name = "index"];. + * + * @param int $var + * + * @return $this + */ + public function setIndex($var) + { + GPBUtil::checkUint32($var); + $this->writeOneof(2, $var); + + return $this; + } + + /** + * Generated from protobuf field string original_field_name = 14 [json_name = "originalFieldName"];. + * + * @return string + */ + public function getOriginalFieldName() + { + return $this->original_field_name; + } + + /** + * Generated from protobuf field string original_field_name = 14 [json_name = "originalFieldName"];. + * + * @param string $var + * + * @return $this + */ + public function setOriginalFieldName($var) + { + GPBUtil::checkString($var, true); + $this->original_field_name = $var; + + return $this; + } + + /** + * The field's return type; e.g. "String!" for User.email:String! + * + * Generated from protobuf field string type = 3 [json_name = "type"]; + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * The field's return type; e.g. "String!" for User.email:String! + * + * Generated from protobuf field string type = 3 [json_name = "type"]; + * + * @param string $var + * + * @return $this + */ + public function setType($var) + { + GPBUtil::checkString($var, true); + $this->type = $var; + + return $this; + } + + /** + * The field's parent type; e.g. "User" for User.email:String! + * + * Generated from protobuf field string parent_type = 13 [json_name = "parentType"]; + * + * @return string + */ + public function getParentType() + { + return $this->parent_type; + } + + /** + * The field's parent type; e.g. "User" for User.email:String! + * + * Generated from protobuf field string parent_type = 13 [json_name = "parentType"]; + * + * @param string $var + * + * @return $this + */ + public function setParentType($var) + { + GPBUtil::checkString($var, true); + $this->parent_type = $var; + + return $this; + } + + /** + * Generated from protobuf field .Trace.CachePolicy cache_policy = 5 [json_name = "cachePolicy"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\CachePolicy|null + */ + public function getCachePolicy() + { + return $this->cache_policy; + } + + public function hasCachePolicy() + { + return isset($this->cache_policy); + } + + public function clearCachePolicy() + { + unset($this->cache_policy); + } + + /** + * Generated from protobuf field .Trace.CachePolicy cache_policy = 5 [json_name = "cachePolicy"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\CachePolicy $var + * + * @return $this + */ + public function setCachePolicy($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\CachePolicy::class); + $this->cache_policy = $var; + + return $this; + } + + /** + * relative to the trace's start_time, in ns. + * + * Generated from protobuf field uint64 start_time = 8 [json_name = "startTime"]; + * + * @return int|string + */ + public function getStartTime() + { + return $this->start_time; + } + + /** + * relative to the trace's start_time, in ns. + * + * Generated from protobuf field uint64 start_time = 8 [json_name = "startTime"]; + * + * @param int|string $var + * + * @return $this + */ + public function setStartTime($var) + { + GPBUtil::checkUint64($var); + $this->start_time = $var; + + return $this; + } + + /** + * relative to the trace's start_time, in ns. + * + * Generated from protobuf field uint64 end_time = 9 [json_name = "endTime"]; + * + * @return int|string + */ + public function getEndTime() + { + return $this->end_time; + } + + /** + * relative to the trace's start_time, in ns. + * + * Generated from protobuf field uint64 end_time = 9 [json_name = "endTime"]; + * + * @param int|string $var + * + * @return $this + */ + public function setEndTime($var) + { + GPBUtil::checkUint64($var); + $this->end_time = $var; + + return $this; + } + + /** + * Generated from protobuf field repeated .Trace.Error error = 11 [json_name = "error"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getError() + { + return $this->error; + } + + /** + * Generated from protobuf field repeated .Trace.Error error = 11 [json_name = "error"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Error>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setError($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Error::class); + $this->error = $arr; + + return $this; + } + + /** + * Generated from protobuf field repeated .Trace.Node child = 12 [json_name = "child"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getChild() + { + return $this->child; + } + + /** + * Generated from protobuf field repeated .Trace.Node child = 12 [json_name = "child"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Node>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setChild($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Node::class); + $this->child = $arr; + + return $this; + } + + /** @return string */ + public function getId() + { + return $this->whichOneof('id'); + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(Node::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_Node::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode.php new file mode 100644 index 0000000000..7741d37a92 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode.php @@ -0,0 +1,229 @@ +Trace.QueryPlanNode + */ +class QueryPlanNode extends \Google\Protobuf\Internal\Message +{ + protected $node; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\SequenceNode $sequence + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ParallelNode $parallel + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\FetchNode $fetch + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\FlattenNode $flatten + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferNode $defer + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ConditionNode $condition + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.SequenceNode sequence = 1 [json_name = "sequence"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\SequenceNode|null + */ + public function getSequence() + { + return $this->readOneof(1); + } + + public function hasSequence() + { + return $this->hasOneof(1); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.SequenceNode sequence = 1 [json_name = "sequence"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\SequenceNode $var + * + * @return $this + */ + public function setSequence($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\SequenceNode::class); + $this->writeOneof(1, $var); + + return $this; + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.ParallelNode parallel = 2 [json_name = "parallel"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ParallelNode|null + */ + public function getParallel() + { + return $this->readOneof(2); + } + + public function hasParallel() + { + return $this->hasOneof(2); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.ParallelNode parallel = 2 [json_name = "parallel"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ParallelNode $var + * + * @return $this + */ + public function setParallel($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ParallelNode::class); + $this->writeOneof(2, $var); + + return $this; + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.FetchNode fetch = 3 [json_name = "fetch"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\FetchNode|null + */ + public function getFetch() + { + return $this->readOneof(3); + } + + public function hasFetch() + { + return $this->hasOneof(3); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.FetchNode fetch = 3 [json_name = "fetch"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\FetchNode $var + * + * @return $this + */ + public function setFetch($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\FetchNode::class); + $this->writeOneof(3, $var); + + return $this; + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.FlattenNode flatten = 4 [json_name = "flatten"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\FlattenNode|null + */ + public function getFlatten() + { + return $this->readOneof(4); + } + + public function hasFlatten() + { + return $this->hasOneof(4); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.FlattenNode flatten = 4 [json_name = "flatten"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\FlattenNode $var + * + * @return $this + */ + public function setFlatten($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\FlattenNode::class); + $this->writeOneof(4, $var); + + return $this; + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.DeferNode defer = 5 [json_name = "defer"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferNode|null + */ + public function getDefer() + { + return $this->readOneof(5); + } + + public function hasDefer() + { + return $this->hasOneof(5); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.DeferNode defer = 5 [json_name = "defer"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferNode $var + * + * @return $this + */ + public function setDefer($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferNode::class); + $this->writeOneof(5, $var); + + return $this; + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.ConditionNode condition = 6 [json_name = "condition"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ConditionNode|null + */ + public function getCondition() + { + return $this->readOneof(6); + } + + public function hasCondition() + { + return $this->hasOneof(6); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.ConditionNode condition = 6 [json_name = "condition"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ConditionNode $var + * + * @return $this + */ + public function setCondition($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ConditionNode::class); + $this->writeOneof(6, $var); + + return $this; + } + + /** @return string */ + public function getNode() + { + return $this->whichOneof('node'); + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(QueryPlanNode::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ConditionNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ConditionNode.php new file mode 100644 index 0000000000..3ff888ec70 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ConditionNode.php @@ -0,0 +1,137 @@ +Trace.QueryPlanNode.ConditionNode. + */ +class ConditionNode extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field string condition = 1 [json_name = "condition"]; */ + protected $condition = ''; + + /** Generated from protobuf field .Trace.QueryPlanNode if_clause = 2 [json_name = "ifClause"]; */ + protected $if_clause; + + /** Generated from protobuf field .Trace.QueryPlanNode else_clause = 3 [json_name = "elseClause"]; */ + protected $else_clause; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $condition + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $if_clause + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $else_clause + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field string condition = 1 [json_name = "condition"];. + * + * @return string + */ + public function getCondition() + { + return $this->condition; + } + + /** + * Generated from protobuf field string condition = 1 [json_name = "condition"];. + * + * @param string $var + * + * @return $this + */ + public function setCondition($var) + { + GPBUtil::checkString($var, true); + $this->condition = $var; + + return $this; + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode if_clause = 2 [json_name = "ifClause"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode|null + */ + public function getIfClause() + { + return $this->if_clause; + } + + public function hasIfClause() + { + return isset($this->if_clause); + } + + public function clearIfClause() + { + unset($this->if_clause); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode if_clause = 2 [json_name = "ifClause"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $var + * + * @return $this + */ + public function setIfClause($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode::class); + $this->if_clause = $var; + + return $this; + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode else_clause = 3 [json_name = "elseClause"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode|null + */ + public function getElseClause() + { + return $this->else_clause; + } + + public function hasElseClause() + { + return isset($this->else_clause); + } + + public function clearElseClause() + { + unset($this->else_clause); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode else_clause = 3 [json_name = "elseClause"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $var + * + * @return $this + */ + public function setElseClause($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode::class); + $this->else_clause = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(ConditionNode::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_ConditionNode::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferNode.php new file mode 100644 index 0000000000..002d8f40a0 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferNode.php @@ -0,0 +1,100 @@ +Trace.QueryPlanNode.DeferNode + */ +class DeferNode extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field .Trace.QueryPlanNode.DeferNodePrimary primary = 1 [json_name = "primary"]; */ + protected $primary; + + /** Generated from protobuf field repeated .Trace.QueryPlanNode.DeferredNode deferred = 2 [json_name = "deferred"]; */ + private $deferred; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferNodePrimary $primary + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferredNode>|\Google\Protobuf\Internal\RepeatedField $deferred + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.DeferNodePrimary primary = 1 [json_name = "primary"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferNodePrimary|null + */ + public function getPrimary() + { + return $this->primary; + } + + public function hasPrimary() + { + return isset($this->primary); + } + + public function clearPrimary() + { + unset($this->primary); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode.DeferNodePrimary primary = 1 [json_name = "primary"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferNodePrimary $var + * + * @return $this + */ + public function setPrimary($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferNodePrimary::class); + $this->primary = $var; + + return $this; + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode.DeferredNode deferred = 2 [json_name = "deferred"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getDeferred() + { + return $this->deferred; + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode.DeferredNode deferred = 2 [json_name = "deferred"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferredNode>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setDeferred($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferredNode::class); + $this->deferred = $arr; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(DeferNode::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_DeferNode::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferNodePrimary.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferNodePrimary.php new file mode 100644 index 0000000000..c074dcce65 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferNodePrimary.php @@ -0,0 +1,69 @@ +Trace.QueryPlanNode.DeferNodePrimary. + */ +class DeferNodePrimary extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field .Trace.QueryPlanNode node = 1 [json_name = "node"]; */ + protected $node; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $node + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode node = 1 [json_name = "node"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode|null + */ + public function getNode() + { + return $this->node; + } + + public function hasNode() + { + return isset($this->node); + } + + public function clearNode() + { + unset($this->node); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode node = 1 [json_name = "node"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $var + * + * @return $this + */ + public function setNode($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode::class); + $this->node = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(DeferNodePrimary::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_DeferNodePrimary::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferredNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferredNode.php new file mode 100644 index 0000000000..028a8f8e87 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferredNode.php @@ -0,0 +1,156 @@ +Trace.QueryPlanNode.DeferredNode. + */ +class DeferredNode extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field repeated .Trace.QueryPlanNode.DeferredNodeDepends depends = 1 [json_name = "depends"]; */ + private $depends; + + /** Generated from protobuf field string label = 2 [json_name = "label"]; */ + protected $label = ''; + + /** Generated from protobuf field repeated .Trace.QueryPlanNode.ResponsePathElement path = 3 [json_name = "path"]; */ + private $path; + + /** Generated from protobuf field .Trace.QueryPlanNode node = 4 [json_name = "node"]; */ + protected $node; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferredNodeDepends>|\Google\Protobuf\Internal\RepeatedField $depends + * @var string $label + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ResponsePathElement>|\Google\Protobuf\Internal\RepeatedField $path + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $node + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode.DeferredNodeDepends depends = 1 [json_name = "depends"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getDepends() + { + return $this->depends; + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode.DeferredNodeDepends depends = 1 [json_name = "depends"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferredNodeDepends>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setDepends($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\DeferredNodeDepends::class); + $this->depends = $arr; + + return $this; + } + + /** + * Generated from protobuf field string label = 2 [json_name = "label"];. + * + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * Generated from protobuf field string label = 2 [json_name = "label"];. + * + * @param string $var + * + * @return $this + */ + public function setLabel($var) + { + GPBUtil::checkString($var, true); + $this->label = $var; + + return $this; + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode.ResponsePathElement path = 3 [json_name = "path"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getPath() + { + return $this->path; + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode.ResponsePathElement path = 3 [json_name = "path"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ResponsePathElement>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setPath($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ResponsePathElement::class); + $this->path = $arr; + + return $this; + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode node = 4 [json_name = "node"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode|null + */ + public function getNode() + { + return $this->node; + } + + public function hasNode() + { + return isset($this->node); + } + + public function clearNode() + { + unset($this->node); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode node = 4 [json_name = "node"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $var + * + * @return $this + */ + public function setNode($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode::class); + $this->node = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(DeferredNode::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_DeferredNode::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferredNodeDepends.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferredNodeDepends.php new file mode 100644 index 0000000000..bec18c1bf6 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/DeferredNodeDepends.php @@ -0,0 +1,88 @@ +Trace.QueryPlanNode.DeferredNodeDepends. + */ +class DeferredNodeDepends extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field string id = 1 [json_name = "id"]; */ + protected $id = ''; + + /** Generated from protobuf field string defer_label = 2 [json_name = "deferLabel"]; */ + protected $defer_label = ''; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $id + * @var string $defer_label + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field string id = 1 [json_name = "id"];. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Generated from protobuf field string id = 1 [json_name = "id"];. + * + * @param string $var + * + * @return $this + */ + public function setId($var) + { + GPBUtil::checkString($var, true); + $this->id = $var; + + return $this; + } + + /** + * Generated from protobuf field string defer_label = 2 [json_name = "deferLabel"];. + * + * @return string + */ + public function getDeferLabel() + { + return $this->defer_label; + } + + /** + * Generated from protobuf field string defer_label = 2 [json_name = "deferLabel"];. + * + * @param string $var + * + * @return $this + */ + public function setDeferLabel($var) + { + GPBUtil::checkString($var, true); + $this->defer_label = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(DeferredNodeDepends::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_DeferredNodeDepends::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php new file mode 100644 index 0000000000..f1a4a029fa --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php @@ -0,0 +1,292 @@ +Trace.QueryPlanNode.FetchNode + */ +class FetchNode extends \Google\Protobuf\Internal\Message +{ + /** + * XXX When we want to include more details about the sub-operation that was + * executed against this service, we should include that here in each fetch node. + * This might include an operation signature, requires directive, reference resolutions, etc. + * + * Generated from protobuf field string service_name = 1 [json_name = "serviceName"]; + */ + protected $service_name = ''; + + /** Generated from protobuf field bool trace_parsing_failed = 2 [json_name = "traceParsingFailed"]; */ + protected $trace_parsing_failed = false; + + /** + * This Trace only contains start_time, end_time, duration_ns, and root; + * all timings were calculated **on the subgraph**, and clock skew + * will be handled by the ingress server. + * + * Generated from protobuf field .Trace trace = 3 [json_name = "trace"]; + */ + protected $trace; + + /** + * relative to the outer trace's start_time, in ns, measured in the Router/Gateway. + * + * Generated from protobuf field uint64 sent_time_offset = 4 [json_name = "sentTimeOffset"]; + */ + protected $sent_time_offset = 0; + + /** + * Wallclock times measured in the Router/Gateway for when this operation was + * sent and received. + * + * Generated from protobuf field .google.protobuf.Timestamp sent_time = 5 [json_name = "sentTime"]; + */ + protected $sent_time; + + /** Generated from protobuf field .google.protobuf.Timestamp received_time = 6 [json_name = "receivedTime"]; */ + protected $received_time; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $service_name + * XXX When we want to include more details about the sub-operation that was + * executed against this service, we should include that here in each fetch node. + * This might include an operation signature, requires directive, reference resolutions, etc. + * @var bool $trace_parsing_failed + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace $trace + * This Trace only contains start_time, end_time, duration_ns, and root; + * all timings were calculated **on the subgraph**, and clock skew + * will be handled by the ingress server. + * @var int|string $sent_time_offset + * relative to the outer trace's start_time, in ns, measured in the Router/Gateway. + * @var \Google\Protobuf\Timestamp $sent_time + * Wallclock times measured in the Router/Gateway for when this operation was + * sent and received. + * @var \Google\Protobuf\Timestamp $received_time + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * XXX When we want to include more details about the sub-operation that was + * executed against this service, we should include that here in each fetch node. + * This might include an operation signature, requires directive, reference resolutions, etc. + * + * Generated from protobuf field string service_name = 1 [json_name = "serviceName"]; + * + * @return string + */ + public function getServiceName() + { + return $this->service_name; + } + + /** + * XXX When we want to include more details about the sub-operation that was + * executed against this service, we should include that here in each fetch node. + * This might include an operation signature, requires directive, reference resolutions, etc. + * + * Generated from protobuf field string service_name = 1 [json_name = "serviceName"]; + * + * @param string $var + * + * @return $this + */ + public function setServiceName($var) + { + GPBUtil::checkString($var, true); + $this->service_name = $var; + + return $this; + } + + /** + * Generated from protobuf field bool trace_parsing_failed = 2 [json_name = "traceParsingFailed"];. + * + * @return bool + */ + public function getTraceParsingFailed() + { + return $this->trace_parsing_failed; + } + + /** + * Generated from protobuf field bool trace_parsing_failed = 2 [json_name = "traceParsingFailed"];. + * + * @param bool $var + * + * @return $this + */ + public function setTraceParsingFailed($var) + { + GPBUtil::checkBool($var); + $this->trace_parsing_failed = $var; + + return $this; + } + + /** + * This Trace only contains start_time, end_time, duration_ns, and root; + * all timings were calculated **on the subgraph**, and clock skew + * will be handled by the ingress server. + * + * Generated from protobuf field .Trace trace = 3 [json_name = "trace"]; + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace|null + */ + public function getTrace() + { + return $this->trace; + } + + public function hasTrace() + { + return isset($this->trace); + } + + public function clearTrace() + { + unset($this->trace); + } + + /** + * This Trace only contains start_time, end_time, duration_ns, and root; + * all timings were calculated **on the subgraph**, and clock skew + * will be handled by the ingress server. + * + * Generated from protobuf field .Trace trace = 3 [json_name = "trace"]; + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace $var + * + * @return $this + */ + public function setTrace($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace::class); + $this->trace = $var; + + return $this; + } + + /** + * relative to the outer trace's start_time, in ns, measured in the Router/Gateway. + * + * Generated from protobuf field uint64 sent_time_offset = 4 [json_name = "sentTimeOffset"]; + * + * @return int|string + */ + public function getSentTimeOffset() + { + return $this->sent_time_offset; + } + + /** + * relative to the outer trace's start_time, in ns, measured in the Router/Gateway. + * + * Generated from protobuf field uint64 sent_time_offset = 4 [json_name = "sentTimeOffset"]; + * + * @param int|string $var + * + * @return $this + */ + public function setSentTimeOffset($var) + { + GPBUtil::checkUint64($var); + $this->sent_time_offset = $var; + + return $this; + } + + /** + * Wallclock times measured in the Router/Gateway for when this operation was + * sent and received. + * + * Generated from protobuf field .google.protobuf.Timestamp sent_time = 5 [json_name = "sentTime"]; + * + * @return \Google\Protobuf\Timestamp|null + */ + public function getSentTime() + { + return $this->sent_time; + } + + public function hasSentTime() + { + return isset($this->sent_time); + } + + public function clearSentTime() + { + unset($this->sent_time); + } + + /** + * Wallclock times measured in the Router/Gateway for when this operation was + * sent and received. + * + * Generated from protobuf field .google.protobuf.Timestamp sent_time = 5 [json_name = "sentTime"]; + * + * @param \Google\Protobuf\Timestamp $var + * + * @return $this + */ + public function setSentTime($var) + { + GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class); + $this->sent_time = $var; + + return $this; + } + + /** + * Generated from protobuf field .google.protobuf.Timestamp received_time = 6 [json_name = "receivedTime"];. + * + * @return \Google\Protobuf\Timestamp|null + */ + public function getReceivedTime() + { + return $this->received_time; + } + + public function hasReceivedTime() + { + return isset($this->received_time); + } + + public function clearReceivedTime() + { + unset($this->received_time); + } + + /** + * Generated from protobuf field .google.protobuf.Timestamp received_time = 6 [json_name = "receivedTime"];. + * + * @param \Google\Protobuf\Timestamp $var + * + * @return $this + */ + public function setReceivedTime($var) + { + GPBUtil::checkMessage($var, \Google\Protobuf\Timestamp::class); + $this->received_time = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(FetchNode::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_FetchNode::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FlattenNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FlattenNode.php new file mode 100644 index 0000000000..c278527a8e --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FlattenNode.php @@ -0,0 +1,101 @@ +Trace.QueryPlanNode.FlattenNode + */ +class FlattenNode extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field repeated .Trace.QueryPlanNode.ResponsePathElement response_path = 1 [json_name = "responsePath"]; */ + private $response_path; + + /** Generated from protobuf field .Trace.QueryPlanNode node = 2 [json_name = "node"]; */ + protected $node; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ResponsePathElement>|\Google\Protobuf\Internal\RepeatedField $response_path + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $node + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode.ResponsePathElement response_path = 1 [json_name = "responsePath"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getResponsePath() + { + return $this->response_path; + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode.ResponsePathElement response_path = 1 [json_name = "responsePath"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ResponsePathElement>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setResponsePath($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode\ResponsePathElement::class); + $this->response_path = $arr; + + return $this; + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode node = 2 [json_name = "node"];. + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode|null + */ + public function getNode() + { + return $this->node; + } + + public function hasNode() + { + return isset($this->node); + } + + public function clearNode() + { + unset($this->node); + } + + /** + * Generated from protobuf field .Trace.QueryPlanNode node = 2 [json_name = "node"];. + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode $var + * + * @return $this + */ + public function setNode($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode::class); + $this->node = $var; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(FlattenNode::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_FlattenNode::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ParallelNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ParallelNode.php new file mode 100644 index 0000000000..6940b590ba --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ParallelNode.php @@ -0,0 +1,61 @@ +Trace.QueryPlanNode.ParallelNode + */ +class ParallelNode extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field repeated .Trace.QueryPlanNode nodes = 1 [json_name = "nodes"]; */ + private $nodes; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode>|\Google\Protobuf\Internal\RepeatedField $nodes + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode nodes = 1 [json_name = "nodes"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getNodes() + { + return $this->nodes; + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode nodes = 1 [json_name = "nodes"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setNodes($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode::class); + $this->nodes = $arr; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(ParallelNode::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_ParallelNode::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ResponsePathElement.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ResponsePathElement.php new file mode 100644 index 0000000000..e09c5b5b08 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/ResponsePathElement.php @@ -0,0 +1,100 @@ +Trace.QueryPlanNode.ResponsePathElement. + */ +class ResponsePathElement extends \Google\Protobuf\Internal\Message +{ + protected $id; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var string $field_name + * @var int $index + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field string field_name = 1 [json_name = "fieldName"];. + * + * @return string + */ + public function getFieldName() + { + return $this->readOneof(1); + } + + public function hasFieldName() + { + return $this->hasOneof(1); + } + + /** + * Generated from protobuf field string field_name = 1 [json_name = "fieldName"];. + * + * @param string $var + * + * @return $this + */ + public function setFieldName($var) + { + GPBUtil::checkString($var, true); + $this->writeOneof(1, $var); + + return $this; + } + + /** + * Generated from protobuf field uint32 index = 2 [json_name = "index"];. + * + * @return int + */ + public function getIndex() + { + return $this->readOneof(2); + } + + public function hasIndex() + { + return $this->hasOneof(2); + } + + /** + * Generated from protobuf field uint32 index = 2 [json_name = "index"];. + * + * @param int $var + * + * @return $this + */ + public function setIndex($var) + { + GPBUtil::checkUint32($var); + $this->writeOneof(2, $var); + + return $this; + } + + /** @return string */ + public function getId() + { + return $this->whichOneof('id'); + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(ResponsePathElement::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_ResponsePathElement::class); diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/SequenceNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/SequenceNode.php new file mode 100644 index 0000000000..8cd7208216 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/SequenceNode.php @@ -0,0 +1,61 @@ +Trace.QueryPlanNode.SequenceNode + */ +class SequenceNode extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field repeated .Trace.QueryPlanNode nodes = 1 [json_name = "nodes"]; */ + private $nodes; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode>|\Google\Protobuf\Internal\RepeatedField $nodes + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode nodes = 1 [json_name = "nodes"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getNodes() + { + return $this->nodes; + } + + /** + * Generated from protobuf field repeated .Trace.QueryPlanNode nodes = 1 [json_name = "nodes"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setNodes($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\QueryPlanNode::class); + $this->nodes = $arr; + + return $this; + } +} + +// Adding a class alias for backwards compatibility with the previous class name. +class_alias(SequenceNode::class, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace_QueryPlanNode_SequenceNode::class); diff --git a/src/Tracing/FederatedTracing/Proto/TracesAndStats.php b/src/Tracing/FederatedTracing/Proto/TracesAndStats.php new file mode 100644 index 0000000000..4caea7a294 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/TracesAndStats.php @@ -0,0 +1,247 @@ +TracesAndStats + */ +class TracesAndStats extends \Google\Protobuf\Internal\Message +{ + /** Generated from protobuf field repeated .Trace trace = 1 [json_name = "trace"]; */ + private $trace; + + /** Generated from protobuf field repeated .ContextualizedStats stats_with_context = 2 [json_name = "statsWithContext"]; */ + private $stats_with_context; + + /** + * This describes the fields referenced in the operation. Note that this may + * include fields that don't show up in FieldStats (due to being interface fields, + * being nested under null fields or empty lists or non-matching fragments or + * `@include` or `@skip`, etc). It also may be missing fields that show up in FieldStats + * (as FieldStats will include the concrete object type for fields referenced + * via an interface type). + * + * Generated from protobuf field map referenced_fields_by_type = 4 [json_name = "referencedFieldsByType"]; + */ + private $referenced_fields_by_type; + + /** + * This field is used to validate that the algorithm used to construct `stats_with_context` + * matches similar algorithms in Apollo's servers. It is otherwise ignored and should not + * be included in reports. + * + * Generated from protobuf field repeated .Trace internal_traces_contributing_to_stats = 3 [json_name = "internalTracesContributingToStats"]; + */ + private $internal_traces_contributing_to_stats; + + /** + * This is an optional field that is used to provide more context to the key of this object within the + * traces_per_query map. If it's omitted, we assume the key is a standard operation name and signature key. + * + * Generated from protobuf field .QueryMetadata query_metadata = 5 [json_name = "queryMetadata"]; + */ + protected $query_metadata; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace>|\Google\Protobuf\Internal\RepeatedField $trace + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\ContextualizedStats>|\Google\Protobuf\Internal\RepeatedField $stats_with_context + * @var array|\Google\Protobuf\Internal\MapField $referenced_fields_by_type + * This describes the fields referenced in the operation. Note that this may + * include fields that don't show up in FieldStats (due to being interface fields, + * being nested under null fields or empty lists or non-matching fragments or + * `@include` or `@skip`, etc). It also may be missing fields that show up in FieldStats + * (as FieldStats will include the concrete object type for fields referenced + * via an interface type). + * @var array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace>|\Google\Protobuf\Internal\RepeatedField $internal_traces_contributing_to_stats + * This field is used to validate that the algorithm used to construct `stats_with_context` + * matches similar algorithms in Apollo's servers. It is otherwise ignored and should not + * be included in reports. + * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryMetadata $query_metadata + * This is an optional field that is used to provide more context to the key of this object within the + * traces_per_query map. If it's omitted, we assume the key is a standard operation name and signature key. + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Generated from protobuf field repeated .Trace trace = 1 [json_name = "trace"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getTrace() + { + return $this->trace; + } + + /** + * Generated from protobuf field repeated .Trace trace = 1 [json_name = "trace"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setTrace($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace::class); + $this->trace = $arr; + + return $this; + } + + /** + * Generated from protobuf field repeated .ContextualizedStats stats_with_context = 2 [json_name = "statsWithContext"];. + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getStatsWithContext() + { + return $this->stats_with_context; + } + + /** + * Generated from protobuf field repeated .ContextualizedStats stats_with_context = 2 [json_name = "statsWithContext"];. + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\ContextualizedStats>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setStatsWithContext($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\ContextualizedStats::class); + $this->stats_with_context = $arr; + + return $this; + } + + /** + * This describes the fields referenced in the operation. Note that this may + * include fields that don't show up in FieldStats (due to being interface fields, + * being nested under null fields or empty lists or non-matching fragments or + * `@include` or `@skip`, etc). It also may be missing fields that show up in FieldStats + * (as FieldStats will include the concrete object type for fields referenced + * via an interface type). + * + * Generated from protobuf field map referenced_fields_by_type = 4 [json_name = "referencedFieldsByType"]; + * + * @return \Google\Protobuf\Internal\MapField + */ + public function getReferencedFieldsByType() + { + return $this->referenced_fields_by_type; + } + + /** + * This describes the fields referenced in the operation. Note that this may + * include fields that don't show up in FieldStats (due to being interface fields, + * being nested under null fields or empty lists or non-matching fragments or + * `@include` or `@skip`, etc). It also may be missing fields that show up in FieldStats + * (as FieldStats will include the concrete object type for fields referenced + * via an interface type). + * + * Generated from protobuf field map referenced_fields_by_type = 4 [json_name = "referencedFieldsByType"]; + * + * @param array|\Google\Protobuf\Internal\MapField $var + * + * @return $this + */ + public function setReferencedFieldsByType($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\ReferencedFieldsForType::class); + $this->referenced_fields_by_type = $arr; + + return $this; + } + + /** + * This field is used to validate that the algorithm used to construct `stats_with_context` + * matches similar algorithms in Apollo's servers. It is otherwise ignored and should not + * be included in reports. + * + * Generated from protobuf field repeated .Trace internal_traces_contributing_to_stats = 3 [json_name = "internalTracesContributingToStats"]; + * + * @return \Google\Protobuf\Internal\RepeatedField + */ + public function getInternalTracesContributingToStats() + { + return $this->internal_traces_contributing_to_stats; + } + + /** + * This field is used to validate that the algorithm used to construct `stats_with_context` + * matches similar algorithms in Apollo's servers. It is otherwise ignored and should not + * be included in reports. + * + * Generated from protobuf field repeated .Trace internal_traces_contributing_to_stats = 3 [json_name = "internalTracesContributingToStats"]; + * + * @param array<\Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace>|\Google\Protobuf\Internal\RepeatedField $var + * + * @return $this + */ + public function setInternalTracesContributingToStats($var) + { + $arr = GPBUtil::checkRepeatedField($var, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace::class); + $this->internal_traces_contributing_to_stats = $arr; + + return $this; + } + + /** + * This is an optional field that is used to provide more context to the key of this object within the + * traces_per_query map. If it's omitted, we assume the key is a standard operation name and signature key. + * + * Generated from protobuf field .QueryMetadata query_metadata = 5 [json_name = "queryMetadata"]; + * + * @return \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryMetadata|null + */ + public function getQueryMetadata() + { + return $this->query_metadata; + } + + public function hasQueryMetadata() + { + return isset($this->query_metadata); + } + + public function clearQueryMetadata() + { + unset($this->query_metadata); + } + + /** + * This is an optional field that is used to provide more context to the key of this object within the + * traces_per_query map. If it's omitted, we assume the key is a standard operation name and signature key. + * + * Generated from protobuf field .QueryMetadata query_metadata = 5 [json_name = "queryMetadata"]; + * + * @param \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryMetadata $var + * + * @return $this + */ + public function setQueryMetadata($var) + { + GPBUtil::checkMessage($var, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\QueryMetadata::class); + $this->query_metadata = $var; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/Proto/TypeStat.php b/src/Tracing/FederatedTracing/Proto/TypeStat.php new file mode 100644 index 0000000000..e9fcd63aa1 --- /dev/null +++ b/src/Tracing/FederatedTracing/Proto/TypeStat.php @@ -0,0 +1,65 @@ +TypeStat. + */ +class TypeStat extends \Google\Protobuf\Internal\Message +{ + /** + * Key is (eg) "email" for User.email:String! + * + * Generated from protobuf field map per_field_stat = 3 [json_name = "perFieldStat"]; + */ + private $per_field_stat; + + /** + * Constructor. + * + * @param array $data { + * Optional. Data for populating the Message object. + * + * @var array|\Google\Protobuf\Internal\MapField $per_field_stat + * Key is (eg) "email" for User.email:String! + * } + */ + public function __construct($data = null) + { + \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Metadata\Reports::initOnce(); + parent::__construct($data); + } + + /** + * Key is (eg) "email" for User.email:String! + * + * Generated from protobuf field map per_field_stat = 3 [json_name = "perFieldStat"]; + * + * @return \Google\Protobuf\Internal\MapField + */ + public function getPerFieldStat() + { + return $this->per_field_stat; + } + + /** + * Key is (eg) "email" for User.email:String! + * + * Generated from protobuf field map per_field_stat = 3 [json_name = "perFieldStat"]; + * + * @param array|\Google\Protobuf\Internal\MapField $var + * + * @return $this + */ + public function setPerFieldStat($var) + { + $arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\FieldStat::class); + $this->per_field_stat = $arr; + + return $this; + } +} diff --git a/src/Tracing/FederatedTracing/reports.proto b/src/Tracing/FederatedTracing/reports.proto new file mode 100644 index 0000000000..45446d7d1d --- /dev/null +++ b/src/Tracing/FederatedTracing/reports.proto @@ -0,0 +1,530 @@ +syntax = "proto3"; + +option php_namespace = "Nuwave\\Lighthouse\\Tracing\\FederatedTracing\\Proto"; +option php_metadata_namespace = "Nuwave\\Lighthouse\\Tracing\\FederatedTracing\\Proto\\Metadata"; + +// Note: The Apollo usage reporting API is subject to change. We strongly encourage developers to contact Apollo support +// at support@apollographql.com to discuss their use case prior to building their own reporting agent using this module. + +import "google/protobuf/timestamp.proto"; + +message Trace { + message CachePolicy { + enum Scope { + UNKNOWN = 0; + PUBLIC = 1; + PRIVATE = 2; + } + + Scope scope = 1; + int64 max_age_ns = 2; // use 0 for absent, -1 for 0 + } + + message Details { + // The variables associated with this query (unless the reporting agent is + // configured to keep them all private). Values are JSON: ie, strings are + // enclosed in double quotes, etc. The value of a private variable is + // the empty string. + map variables_json = 4; + + + // This is deprecated and only used for legacy applications + // don't include this in traces inside a FullTracesReport; the operation + // name for these traces comes from the key of the traces_per_query map. + string operation_name = 3; + } + + message Error { + string message = 1; // required + repeated Location location = 2; + uint64 time_ns = 3; + string json = 4; + } + + message HTTP { + message Values { + repeated string value = 1; + } + + enum Method { + UNKNOWN = 0; + OPTIONS = 1; + GET = 2; + HEAD = 3; + POST = 4; + PUT = 5; + DELETE = 6; + TRACE = 7; + CONNECT = 8; + PATCH = 9; + } + Method method = 1; + + // Should exclude manual blacklist ("Auth" by default) + map request_headers = 4; + map response_headers = 5; + + uint32 status_code = 6; + + reserved 2, 3, 8, 9; + } + + message Location { + uint32 line = 1; + uint32 column = 2; + } + + // We store information on each resolver execution as a Node on a tree. + // The structure of the tree corresponds to the structure of the GraphQL + // response; it does not indicate the order in which resolvers were + // invoked. Note that nodes representing indexes (and the root node) + // don't contain all Node fields (eg types and times). + message Node { + // The name of the field (for Nodes representing a resolver call) or the + // index in a list (for intermediate Nodes representing elements of a list). + // field_name is the name of the field as it appears in the GraphQL + // response: ie, it may be an alias. (In that case, the original_field_name + // field holds the actual field name from the schema.) In any context where + // we're building up a path, we use the response_name rather than the + // original_field_name. + oneof id { + string response_name = 1; + uint32 index = 2; + } + + string original_field_name = 14; + + // The field's return type; e.g. "String!" for User.email:String! + string type = 3; + + // The field's parent type; e.g. "User" for User.email:String! + string parent_type = 13; + + CachePolicy cache_policy = 5; + + // relative to the trace's start_time, in ns + uint64 start_time = 8; + // relative to the trace's start_time, in ns + uint64 end_time = 9; + + repeated Error error = 11; + repeated Node child = 12; + + reserved 4; + } + + // represents a node in the query plan, under which there is a trace tree for that service fetch. + // In particular, each fetch node represents a call to an implementing service, and calls to implementing + // services may not be unique. See https://github.com/apollographql/federation/blob/main/query-planner-js/src/QueryPlan.ts + // for more information and details. + message QueryPlanNode { + // This represents a set of nodes to be executed sequentially by the Router/Gateway executor + message SequenceNode { + repeated QueryPlanNode nodes = 1; + } + // This represents a set of nodes to be executed in parallel by the Router/Gateway executor + message ParallelNode { + repeated QueryPlanNode nodes = 1; + } + // This represents a node to send an operation to an implementing service + message FetchNode { + // XXX When we want to include more details about the sub-operation that was + // executed against this service, we should include that here in each fetch node. + // This might include an operation signature, requires directive, reference resolutions, etc. + string service_name = 1; + + bool trace_parsing_failed = 2; + + // This Trace only contains start_time, end_time, duration_ns, and root; + // all timings were calculated **on the subgraph**, and clock skew + // will be handled by the ingress server. + Trace trace = 3; + + // relative to the outer trace's start_time, in ns, measured in the Router/Gateway. + uint64 sent_time_offset = 4; + + // Wallclock times measured in the Router/Gateway for when this operation was + // sent and received. + google.protobuf.Timestamp sent_time = 5; + google.protobuf.Timestamp received_time = 6; + } + + // This node represents a way to reach into the response path and attach related entities. + // XXX Flatten is really not the right name and this node may be renamed in the query planner. + message FlattenNode { + repeated ResponsePathElement response_path = 1; + QueryPlanNode node = 2; + } + + // A `DeferNode` corresponds to one or more @defer at the same level of "nestedness" in the planned query. + message DeferNode { + DeferNodePrimary primary = 1; + repeated DeferredNode deferred = 2; + } + + message ConditionNode { + string condition = 1; + QueryPlanNode if_clause = 2; + QueryPlanNode else_clause = 3; + } + + message DeferNodePrimary { + QueryPlanNode node = 1; + } + message DeferredNode { + repeated DeferredNodeDepends depends = 1; + string label = 2; + repeated ResponsePathElement path = 3; + QueryPlanNode node = 4; + } + message DeferredNodeDepends { + string id = 1; + string defer_label = 2; + } + + message ResponsePathElement { + oneof id { + string field_name = 1; + uint32 index = 2; + } + } + oneof node { + SequenceNode sequence = 1; + ParallelNode parallel = 2; + FetchNode fetch = 3; + FlattenNode flatten = 4; + DeferNode defer = 5; + ConditionNode condition = 6; + } + } + + // Wallclock time when the trace began. + google.protobuf.Timestamp start_time = 4; // required + // Wallclock time when the trace ended. + google.protobuf.Timestamp end_time = 3; // required + // High precision duration of the trace; may not equal end_time-start_time + // (eg, if your machine's clock changed during the trace). + uint64 duration_ns = 11; // required + // A tree containing information about all resolvers run directly by this + // service, including errors. + Node root = 14; + + // If this is true, the trace is potentially missing some nodes that were + // present on the query plan. This can happen if the trace span buffer used + // in the Router fills up and some spans have to be dropped. In these cases + // the overall trace timing will still be correct, but the trace data could + // be missing some referenced or executed fields, and some nodes may be + // missing. If this is true we should display a warning to the user when they + // view the trace in Explorer. + bool is_incomplete = 33; + + // ------------------------------------------------------------------------- + // Fields below this line are *not* included in inline traces (the traces + // sent from subgraphs to the Router/Gateway). + + // In addition to details.raw_query, we include a "signature" of the query, + // which can be normalized: for example, you may want to discard aliases, drop + // unused operations and fragments, sort fields, etc. The most important thing + // here is that the signature match the signature in StatsReports. In + // StatsReports signatures show up as the key in the per_query map (with the + // operation name prepended). The signature should be a valid GraphQL query. + // All traces must have a signature; if this Trace is in a FullTracesReport + // that signature is in the key of traces_per_query rather than in this field. + // Engineproxy provides the signature in legacy_signature_needs_resigning + // instead. + string signature = 19; + + // Optional: when GraphQL parsing or validation against the GraphQL schema fails, these fields + // can include reference to the operation being sent for users to dig into the set of operations + // that are failing validation. + string unexecutedOperationBody = 27; + string unexecutedOperationName = 28; + + Details details = 6; + + string client_name = 7; + string client_version = 8; + + string operation_type = 35; + string operation_subtype = 36; + + + HTTP http = 10; + + CachePolicy cache_policy = 18; + + // If this Trace was created by a Router/Gateway, this is the query plan, including + // sub-Traces for subgraphs. Note that the 'root' tree on the + // top-level Trace won't contain any resolvers (though it could contain errors + // that occurred in the Router/Gateway itself). + QueryPlanNode query_plan = 26; + + // Was this response served from a full query response cache? (In that case + // the node tree will have no resolvers.) + bool full_query_cache_hit = 20; + + // Was this query specified successfully as a persisted query hash? + bool persisted_query_hit = 21; + // Did this query contain both a full query string and a persisted query hash? + // (This typically means that a previous request was rejected as an unknown + // persisted query.) + bool persisted_query_register = 22; + + // Was this operation registered and a part of the safelist? + bool registered_operation = 24; + + // Was this operation forbidden due to lack of safelisting? + bool forbidden_operation = 25; + + // Some servers don't do field-level instrumentation for every request and assign + // each request a "weight" for each request that they do instrument. When this + // trace is aggregated into field usage stats, it should count as this value + // towards the estimated_execution_count rather than just 1. This value should + // typically be at least 1. + // + // 0 is treated as 1 for backwards compatibility. + double field_execution_weight = 31; + + + + // removed: Node parse = 12; Node validate = 13; + // Id128 server_id = 1; Id128 client_id = 2; + // String client_reference_id = 23; String client_address = 9; + reserved 1, 2, 9, 12, 13, 23; +} + +// The `service` value embedded within the header key is not guaranteed to contain an actual service, +// and, in most cases, the service information is trusted to come from upstream processing. If the +// service _is_ specified in this header, then it is checked to match the context that is reporting it. +// Otherwise, the service information is deduced from the token context of the reporter and then sent +// along via other mechanisms (in Kafka, the `ReportKafkaKey). The other information (hostname, +// agent_version, etc.) is sent by the Apollo Engine Reporting agent, but we do not currently save that +// information to any of our persistent storage. +message ReportHeader { + // eg "mygraph@myvariant" + string graph_ref = 12; + + // eg "host-01.example.com" + string hostname = 5; + + // eg "engineproxy 0.1.0" + string agent_version = 6; // required + // eg "prod-4279-20160804T065423Z-5-g3cf0aa8" (taken from `git describe --tags`) + string service_version = 7; + // eg "node v4.6.0" + string runtime_version = 8; + // eg "Linux box 4.6.5-1-ec2 #1 SMP Mon Aug 1 02:31:38 PDT 2016 x86_64 GNU/Linux" + string uname = 9; + // An id that is used to represent the schema to Apollo Graph Manager + // Using this in place of what used to be schema_hash, since that is no longer + // attached to a schema in the backend. + string executable_schema_id = 11; + + reserved 3; // removed string service = 3; +} + +message PathErrorStats { + map children = 1; + uint64 errors_count = 4; + uint64 requests_with_errors_count = 5; +} + +message QueryLatencyStats { + // The latencies of all non-cached requests, so the sum of all counts should equal request_count minus cache_hits. + // This is an array of counts within a logarithmic range of 384 latency buckets. To calculate the bucket from a + // microsecond, use the formula: max(0, min(ceil(ln(x)/ln(1.1)), 383)). So for example, 323424 microseconds (323.424 + // ms) corresponds to bucket 134. Buckets can be skipped using a negative number, so one request on that bucket could + // be represented as [-134, 1] (skip buckets numbered 0 to 133 and set a 1 in bucket 134). + repeated sint64 latency_count = 13; + + // The total number of requests, including both cache hits and cache misses + uint64 request_count = 2; + + // The total number of requests that were cache hits. Each request should be represented in cache_latency_count + uint64 cache_hits = 3; + + uint64 persisted_query_hits = 4; + uint64 persisted_query_misses = 5; + + // This array includes the latency buckets for all operations included in cache_hits + // See comment on latency_count for details. + repeated sint64 cache_latency_count = 14; + + // Paths and counts for each error. The total number of requests with errors within this object should be the same as + // requests_with_errors_count below. + PathErrorStats root_error_stats = 7; + + // Total number of requests that contained at least one error + uint64 requests_with_errors_count = 8; + + repeated sint64 public_cache_ttl_count = 15; + repeated sint64 private_cache_ttl_count = 16; + uint64 registered_operation_count = 11; + uint64 forbidden_operation_count = 12; + + // The number of requests that were executed without field-level + // instrumentation (and thus do not contribute to `observed_execution_count` + // fields on this message's cousin-twice-removed FieldStats). + uint64 requests_without_field_instrumentation = 17; + + // 1, 6, 9, and 10 were old int64 histograms + reserved 1, 6, 9, 10; +} + +// The context around a block of stats and traces indicating from which client the operation was executed and its +// operation type. Operation type and subtype are only used by Apollo Router. +message StatsContext { + reserved 1; // string client_reference_id = 1; + string client_name = 2; + string client_version = 3; + string operation_type = 4; + string operation_subtype = 5; +} + +message ContextualizedQueryLatencyStats { + QueryLatencyStats query_latency_stats = 1; + StatsContext context = 2; +} + +message ContextualizedTypeStats { + StatsContext context = 1; + map per_type_stat = 2; +} + +message FieldStat { + string return_type = 3; // required; eg "String!" for User.email:String! + // Number of errors whose path is this field. Note that we assume that error + // tracking does *not* require field-level instrumentation so this *will* + // include errors from requests that don't contribute to the + // `observed_execution_count` field (and does not need to be scaled by + // field_execution_weight). + uint64 errors_count = 4; + // Number of times that the resolver for this field is directly observed being + // executed. + uint64 observed_execution_count = 5; + // Same as `observed_execution_count` but potentially scaled upwards if the server was only + // performing field-level instrumentation on a sampling of operations. For + // example, if the server randomly instruments 1% of requests for this + // operation, this number will be 100 times greater than + // `observed_execution_count`. (When aggregating a Trace into FieldStats, + // this number goes up by the trace's `field_execution_weight` for each + // observed field execution, while `observed_execution_count` above goes + // up by 1.) + uint64 estimated_execution_count = 10; + // Number of times the resolver for this field is executed that resulted in + // at least one error. "Request" is a misnomer here as this corresponds to + // resolver calls, not overall operations. Like `errors_count` above, this + // includes all requests rather than just requests with field-level + // instrumentation. + uint64 requests_with_errors_count = 6; + // Duration histogram for the latency of this field. Note that it is scaled in + // the same way as estimated_execution_count so its "total count" might be + // greater than `observed_execution_count` and may not exactly equal + // `estimated_execution_count` due to rounding. + // See comment on QueryLatencyStats's latency_count for details. + repeated sint64 latency_count = 9; + reserved 1, 2, 7, 8; +} + +message TypeStat { + // Key is (eg) "email" for User.email:String! + map per_field_stat = 3; + reserved 1, 2; +} + +message ReferencedFieldsForType { + // Contains (eg) "email" for User.email:String! + repeated string field_names = 1; + // True if this type is an interface. + bool is_interface = 2; +} + + + +// This is the top-level message used by Apollo Server, Apollo Router, and other libraries to report usage information +// to Apollo. This message consists of traces and stats for operations. By default, each individual operation execution +// should be either represented as a trace or within stats, but not both. However if the "traces_pre_aggregated" field +// is set to true, all operations should be included in stats and anything specified as a trace is not added in to the +// aggregate stats. For performance reasons, we recommend that traces are sampled so that only somewhere around 1% of +// operation executions are sent as traces. Additionally, buffering operations up until a large size has been reached +// (say, 4MB) or 5-10 seconds has passed is appropriate. +// This message used to be known as FullTracesReport, but got renamed since it isn't just for traces anymore. +message Report { + message OperationCountByType { + string type = 1; + string subtype = 2; + uint64 operation_count = 3; + } + + ReportHeader header = 1; + + // If QueryMetadata isn't provided, this key should be a statsReportKey (# operationName\nsignature). If the operation + // name, signature, and persisted query IDs are provided in the QueryMetadata, and this operation was requested via a + // persisted query, this key can be "pq# " instead of the signature and operation. + map traces_per_query = 5; + + // This is the time that the requests in this trace are considered to have taken place + // If this field is not present the max of the end_time of each trace will be used instead. + // If there are no traces and no end_time present the report will not be able to be processed. + // Note: This will override the end_time from traces. + google.protobuf.Timestamp end_time = 2; // required if no traces in this message + + // Total number of operations processed during this period. This includes all operations, even if they are sampled + // and not included in the query latency stats. + uint64 operation_count = 6; + + // Total number of operations broken up by operation type and operation subtype. + // Only either this or operation_count should be populated, but if both are present, the total across all types and + // subtypes should match the overall operation_count. + repeated OperationCountByType operation_count_by_type = 8; + + // If this is set to true, the stats in TracesWithStats.stats_with_context + // represent all of the operations described from this report, and the + // traces in TracesWithStats.trace are a sampling of some of the same + // operations. If this is false, each operation is described in precisely + // one of those two fields. + bool traces_pre_aggregated = 7; +} + +message ContextualizedStats { + StatsContext context = 1; + QueryLatencyStats query_latency_stats = 2; + // Key is type name. This structure provides data for the count and latency of individual + // field executions and thus only reflects operations for which field-level tracing occurred. + map per_type_stat = 3; + +} + +message QueryMetadata { + // The operation name. For now this is a required field if QueryMetadata is present. + string name = 1; + // the operation signature. For now this is a required field if QueryMetadata is present. + string signature = 2; + // (Optional) Persisted query ID that was used to request this operation. + string pq_id = 3; +} + +// A sequence of traces and stats. If Report.traces_pre_aggregated (at the top +// level of the report) is false, an individual operation should either be +// described as a trace or as part of stats, but not both. If that flag +// is true, then all operations are described as stats and some are also +// described as traces. +message TracesAndStats { + repeated Trace trace = 1; + repeated ContextualizedStats stats_with_context = 2; + // This describes the fields referenced in the operation. Note that this may + // include fields that don't show up in FieldStats (due to being interface fields, + // being nested under null fields or empty lists or non-matching fragments or + // `@include` or `@skip`, etc). It also may be missing fields that show up in FieldStats + // (as FieldStats will include the concrete object type for fields referenced + // via an interface type). + map referenced_fields_by_type = 4; + // This field is used to validate that the algorithm used to construct `stats_with_context` + // matches similar algorithms in Apollo's servers. It is otherwise ignored and should not + // be included in reports. + repeated Trace internal_traces_contributing_to_stats = 3; + + // This is an optional field that is used to provide more context to the key of this object within the + // traces_per_query map. If it's omitted, we assume the key is a standard operation name and signature key. + QueryMetadata query_metadata = 5; +} diff --git a/src/Tracing/Tracing.php b/src/Tracing/Tracing.php index b2cd6f3bdb..05530e54fc 100644 --- a/src/Tracing/Tracing.php +++ b/src/Tracing/Tracing.php @@ -2,107 +2,27 @@ namespace Nuwave\Lighthouse\Tracing; -use Illuminate\Support\Carbon; use Nuwave\Lighthouse\Events\BuildExtensionsResponse; use Nuwave\Lighthouse\Events\StartExecution; +use Nuwave\Lighthouse\Events\StartRequest; use Nuwave\Lighthouse\Execution\ExtensionsResponse; use Nuwave\Lighthouse\Execution\ResolveInfo; -/** - * See https://github.com/apollographql/apollo-tracing#response-format. - */ -class Tracing +interface Tracing { - /** The point in time when the request was initially started. */ - protected Carbon $executionStartAbsolute; + public function handleStartRequest(StartRequest $startRequest): void; - /** - * The precise point in time when the request was initially started. - * - * This is either in seconds with microsecond precision (float) or nanoseconds (int). - */ - protected int|float $executionStartPrecise; - - /** - * Trace entries for a single query execution. - * - * @var array> - */ - protected array $resolverTraces = []; - - public function handleStartExecution(StartExecution $startExecution): void - { - $this->executionStartAbsolute = Carbon::now(); - $this->executionStartPrecise = $this->timestamp(); - $this->resolverTraces = []; - } + public function handleStartExecution(StartExecution $startExecution): void; - public function handleBuildExtensionsResponse(BuildExtensionsResponse $buildExtensionsResponse): ExtensionsResponse - { - $requestEndAbsolute = Carbon::now(); - $requestEndPrecise = $this->timestamp(); - - return new ExtensionsResponse( - 'tracing', - [ - 'version' => 1, - 'startTime' => $this->formatTimestamp($this->executionStartAbsolute), - 'endTime' => $this->formatTimestamp($requestEndAbsolute), - 'duration' => $this->diffTimeInNanoseconds($this->executionStartPrecise, $requestEndPrecise), - 'execution' => [ - 'resolvers' => $this->resolverTraces, - ], - ], - ); - } + public function handleBuildExtensionsResponse(BuildExtensionsResponse $buildExtensionsResponse): ?ExtensionsResponse; /** Record resolver execution time. */ - public function record(ResolveInfo $resolveInfo, float|int $start, float|int $end): void - { - $this->resolverTraces[] = [ - 'path' => $resolveInfo->path, - 'parentType' => $resolveInfo->parentType->name, - 'fieldName' => $resolveInfo->fieldName, - 'returnType' => $resolveInfo->returnType->toString(), - 'startOffset' => $this->diffTimeInNanoseconds($this->executionStartPrecise, $start), - 'duration' => $this->diffTimeInNanoseconds($start, $end), - ]; - } + public function record(ResolveInfo $resolveInfo, float|int $start, float|int $end): void; /** * Get the system's highest resolution of time possible. * * This is either in seconds with microsecond precision (float) or nanoseconds (int). */ - public function timestamp(): float|int - { - return $this->platformSupportsNanoseconds() - ? hrtime(true) - : microtime(true); - } - - /** Diff the time results to each other and convert to nanoseconds if needed. */ - protected function diffTimeInNanoseconds(float|int $start, float|int $end): int - { - if ($this->platformSupportsNanoseconds()) { - return (int) ($end - $start); - } - - // Difference is in seconds (with microsecond precision) - // * 1000 to get to milliseconds - // * 1000 to get to microseconds - // * 1000 to get to nanoseconds - return (int) (($end - $start) * 1000 * 1000 * 1000); - } - - /** Is the `hrtime` function available to get a nanosecond precision point in time? */ - protected function platformSupportsNanoseconds(): bool - { - return function_exists('hrtime'); - } - - protected function formatTimestamp(Carbon $timestamp): string - { - return $timestamp->format(Carbon::RFC3339_EXTENDED); - } + public function timestamp(): float|int; } diff --git a/src/Tracing/TracingDirective.php b/src/Tracing/TracingDirective.php index e5bdbfc0be..efbab6e070 100644 --- a/src/Tracing/TracingDirective.php +++ b/src/Tracing/TracingDirective.php @@ -31,9 +31,9 @@ public function handleField(FieldValue $fieldValue): void $fieldValue->wrapResolver(fn (callable $resolver): \Closure => function (mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver) { $start = $this->tracing->timestamp(); $result = $resolver($root, $args, $context, $resolveInfo); - $end = $this->tracing->timestamp(); - Resolved::handle($result, function () use ($resolveInfo, $start, $end): void { + Resolved::handle($result, function () use ($resolveInfo, $start): void { + $end = $this->tracing->timestamp(); $this->tracing->record($resolveInfo, $start, $end); }); diff --git a/src/Tracing/TracingServiceProvider.php b/src/Tracing/TracingServiceProvider.php index ce640dc8dd..a554366c00 100644 --- a/src/Tracing/TracingServiceProvider.php +++ b/src/Tracing/TracingServiceProvider.php @@ -3,19 +3,22 @@ namespace Nuwave\Lighthouse\Tracing; use GraphQL\Language\Parser; +use Illuminate\Container\Container; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider; use Nuwave\Lighthouse\Events\BuildExtensionsResponse; use Nuwave\Lighthouse\Events\ManipulateAST; use Nuwave\Lighthouse\Events\RegisterDirectiveNamespaces; use Nuwave\Lighthouse\Events\StartExecution; +use Nuwave\Lighthouse\Events\StartRequest; use Nuwave\Lighthouse\Schema\AST\ASTHelper; class TracingServiceProvider extends ServiceProvider { public function register(): void { - $this->app->singleton(Tracing::class); + $this->app->scoped(Tracing::class, static fn (Application $app): Tracing => $app->make(config('lighthouse.tracing.driver'))); } public function boot(Dispatcher $dispatcher): void @@ -25,7 +28,14 @@ public function boot(Dispatcher $dispatcher): void $manipulateAST->documentAST, Parser::constDirective('@tracing'), )); - $dispatcher->listen(StartExecution::class, Tracing::class . '@handleStartExecution'); - $dispatcher->listen(BuildExtensionsResponse::class, Tracing::class . '@handleBuildExtensionsResponse'); + $dispatcher->listen(StartRequest::class, static fn (StartRequest $event) => Container::getInstance() + ->make(Tracing::class) + ->handleStartRequest($event)); + $dispatcher->listen(StartExecution::class, static fn (StartExecution $event) => Container::getInstance() + ->make(Tracing::class) + ->handleStartExecution($event)); + $dispatcher->listen(BuildExtensionsResponse::class, static fn (BuildExtensionsResponse $event) => Container::getInstance() + ->make(Tracing::class) + ->handleBuildExtensionsResponse($event)); } } diff --git a/src/Tracing/TracingUtilities.php b/src/Tracing/TracingUtilities.php new file mode 100644 index 0000000000..0dbab35934 --- /dev/null +++ b/src/Tracing/TracingUtilities.php @@ -0,0 +1,45 @@ +platformSupportsNanoseconds() + ? hrtime(true) + : microtime(true); + } + + /** Diff the time results to each other and convert to nanoseconds if needed. */ + protected function diffTimeInNanoseconds(float|int $start, float|int $end): int + { + if ($this->platformSupportsNanoseconds()) { + return (int) ($end - $start); + } + + // Difference is in seconds (with microsecond precision) + // * 1000 to get to milliseconds + // * 1000 to get to microseconds + // * 1000 to get to nanoseconds + return (int) (($end - $start) * 1000 * 1000 * 1000); + } + + /** Is the `hrtime` function available to get a nanosecond precision point in time? */ + protected function platformSupportsNanoseconds(): bool + { + return function_exists('hrtime'); + } + + protected function formatTimestamp(Carbon $timestamp): string + { + return $timestamp->format(Carbon::RFC3339_EXTENDED); + } +} diff --git a/src/lighthouse.php b/src/lighthouse.php index 79b1a7a95e..a665f460b1 100644 --- a/src/lighthouse.php +++ b/src/lighthouse.php @@ -453,4 +453,27 @@ */ 'entities_resolver_namespace' => 'App\\GraphQL\\Entities', ], + + /* + |-------------------------------------------------------------------------- + | Tracing + |-------------------------------------------------------------------------- + | + | Configuration for tracing support. + | + */ + + 'tracing' => [ + /* + * Driver used for tracing. + * + * Accepts the fully qualified class name of a class that implements Nuwave\Lighthouse\Tracing\Tracing. + * Lighthouse provides: + * - Nuwave\Lighthouse\Tracing\ApolloTracing\ApolloTracing::class + * - Nuwave\Lighthouse\Tracing\FederatedTracing\FederatedTracing::class + * + * In Lighthouse v7 the default will be changed to 'Nuwave\Lighthouse\Tracing\FederatedTracing\FederatedTracing::class'. + */ + 'driver' => Nuwave\Lighthouse\Tracing\ApolloTracing\ApolloTracing::class, + ], ]; diff --git a/tests/Integration/Tracing/TracingExtensionTest.php b/tests/Integration/Tracing/ApolloTracingExtensionTest.php similarity index 96% rename from tests/Integration/Tracing/TracingExtensionTest.php rename to tests/Integration/Tracing/ApolloTracingExtensionTest.php index f926f9a977..cc3f174998 100644 --- a/tests/Integration/Tracing/TracingExtensionTest.php +++ b/tests/Integration/Tracing/ApolloTracingExtensionTest.php @@ -5,11 +5,11 @@ use Nuwave\Lighthouse\Tracing\TracingServiceProvider; use Tests\TestCase; -final class TracingExtensionTest extends TestCase +final class ApolloTracingExtensionTest extends TestCase { protected string $schema = /** @lang GraphQL */ ' type Query { - foo: String! @field(resolver: "Tests\\\Integration\\\Tracing\\\TracingExtensionTest@resolve") + foo: String! @field(resolver: "Tests\\\Integration\\\Tracing\\\ApolloTracingExtensionTest@resolve") } '; diff --git a/tests/Integration/Tracing/FederatedTracingExtensionTest.php b/tests/Integration/Tracing/FederatedTracingExtensionTest.php new file mode 100644 index 0000000000..31ee607b5e --- /dev/null +++ b/tests/Integration/Tracing/FederatedTracingExtensionTest.php @@ -0,0 +1,206 @@ +app->make(Repository::class); + $config->set('lighthouse.tracing.driver', FederatedTracing::class); + + $config->set('lighthouse.debug', DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE); + } + + public function testHeaderIsRequiredToEnableTracing(): void + { + $response = $this + ->graphQL(/** @lang GraphQL */ ' + { + foo { id } + } + '); + + $response->assertJsonMissingPath('extensions.ftv1'); + } + + public function testAddFtv1ExtensionMetaToResult(): void + { + $response = $this + ->graphQL(/** @lang GraphQL */ ' + { + foo { id } + } + ', + headers: ['apollo-federation-include-trace' => FederatedTracing::V1], + ); + + $response->assertJsonStructure([ + 'extensions' => [ + 'ftv1', + ], + ]); + + $traceData = $this->decodeFtv1Record($response->json('extensions.ftv1')); + + $this->assertArrayHasKey('startTime', $traceData); + $this->assertArrayHasKey('endTime', $traceData); + $this->assertArrayHasKey('root', $traceData); + $this->assertGreaterThan(0, (int) $traceData['durationNs']); + $this->assertCount(1, $traceData['root']['child']); + + $fooNode = $traceData['root']['child'][0]; + $this->assertSame('foo', $fooNode['responseName']); + $this->assertSame('Foo!', $fooNode['type']); + $this->assertSame('Query', $fooNode['parentType']); + $this->assertArrayHasKey('startTime', $fooNode); + $this->assertArrayHasKey('endTime', $fooNode); + $this->assertCount(1, $fooNode['child']); + + $fooIdNode = $fooNode['child'][0]; + $this->assertSame('id', $fooIdNode['responseName']); + $this->assertSame('String!', $fooIdNode['type']); + $this->assertSame('Foo', $fooIdNode['parentType']); + $this->assertArrayHasKey('startTime', $fooIdNode); + $this->assertArrayHasKey('endTime', $fooIdNode); + $this->assertArrayNotHasKey('child', $fooIdNode); + } + + public function testAddFtv1ExtensionMetaToBatchedResults(): void + { + $postData = [ + 'query' /** @lang GraphQL */ => ' + { + foo { id } + } + ', + ]; + $expectedResponse = [ + 'extensions' => [ + 'ftv1', + ], + ]; + + $result = $this + ->postGraphQL( + [ + $postData, + $postData, + ], + headers: ['apollo-federation-include-trace' => FederatedTracing::V1], + ); + + $result->assertJsonCount(2) + ->assertJsonStructure([ + $expectedResponse, + $expectedResponse, + ]); + $this->assertNotEquals($result->json('0.extensions.ftv1'), $result->json('1.extensions.ftv1')); + + $trace1Data = $this->decodeFtv1Record($result->json('0.extensions.ftv1')); + $trace2Data = $this->decodeFtv1Record($result->json('1.extensions.ftv1')); + + $startTime1 = $trace1Data['startTime']; + $endTime1 = $trace1Data['endTime']; + + $startTime2 = $trace2Data['startTime']; + $endTime2 = $trace2Data['endTime']; + + // Guaranteed by the usleep() in $this->resolve() + $this->assertGreaterThan($startTime1, $endTime1); + + // Might be the same timestamp if Lighthouse runs quickly in a given environment + $this->assertGreaterThanOrEqual($endTime1, $startTime2); + + // Guaranteed by the usleep() in $this->resolve() + $this->assertGreaterThan($startTime2, $endTime2); + + $this->assertCount(1, $trace1Data['root']['child']); + $this->assertCount(1, $trace2Data['root']['child']); + } + + public function testReportErrorsToResult(): void + { + $response = $this + ->graphQL(/** @lang GraphQL */ ' + { + foo { bar } + } + ', + headers: ['apollo-federation-include-trace' => FederatedTracing::V1], + ); + + $response->assertJsonStructure([ + 'extensions' => [ + 'ftv1', + ], + ]); + + $traceData = $this->decodeFtv1Record($response->json('extensions.ftv1')); + + $barNode = $traceData['root']['child'][0]['child'][0]; + $this->assertArrayHasKey('error', $barNode); + $this->assertCount(1, $barNode['error']); + + $barNodeError = $barNode['error'][0]; + $this->assertSame('Internal server error', $barNodeError['message']); + $this->assertCount(1, $barNodeError['location']); + $this->assertArrayHasKey('line', $barNodeError['location'][0]); + $this->assertArrayHasKey('column', $barNodeError['location'][0]); + $this->assertArrayNotHasKey('json', $barNodeError); + } + + public static function resolve(): string + { + // Just enough to consistently change the resulting timestamp + usleep(1000); + + return 'bar'; + } + + /** @return never */ + public static function throw(): void + { + throw new \RuntimeException('foo'); + } + + /** @return array */ + protected function decodeFtv1Record(string $encoded): array + { + $decoded = \Safe\base64_decode($encoded); + + $trace = new Trace(); + $trace->mergeFromString($decoded); + + return json_decode($trace->serializeToJsonString(), true, 512, JSON_THROW_ON_ERROR); + } +} From 7996dfa9c654f7dcd4ebc3bfbc756eae1445cf46 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Thu, 7 Dec 2023 09:08:06 +0100 Subject: [PATCH 05/14] v6.26.0 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d58caf657..4ec35a4c1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +## v6.26.0 + +### Added + +- Support federated tracing https://github.com/nuwave/lighthouse/pull/2479 + ## v6.25.0 ### Added From e512f947798728b7b41647e4fe057c3ac10c7b3c Mon Sep 17 00:00:00 2001 From: spawnia Date: Sun, 10 Dec 2023 17:54:43 +0100 Subject: [PATCH 06/14] Prefer @whereKey over @eq in examples --- docs/6/api-reference/directives.md | 4 ++-- docs/6/eloquent/getting-started.md | 8 +++++--- docs/6/federation/getting-started.md | 16 +++++++++++++--- docs/6/performance/tracing.md | 15 ++++++++++++++- docs/6/security/authorization.md | 2 +- docs/master/api-reference/directives.md | 4 ++-- docs/master/eloquent/getting-started.md | 8 +++++--- docs/master/security/authorization.md | 2 +- 8 files changed, 43 insertions(+), 16 deletions(-) diff --git a/docs/6/api-reference/directives.md b/docs/6/api-reference/directives.md index e79a8e1db4..906d326164 100644 --- a/docs/6/api-reference/directives.md +++ b/docs/6/api-reference/directives.md @@ -1193,7 +1193,7 @@ directive @find( ```graphql type Query { - userById(id: ID! @eq): User @find + userById(id: ID! @whereKey): User @find } ``` @@ -1204,7 +1204,7 @@ If your model does not sit in the default namespace, you can overwrite it. ```graphql type Query { - userById(id: ID! @eq): User @find(model: "App\\Authentication\\User") + userById(id: ID! @whereKey): User @find(model: "App\\Authentication\\User") } ``` diff --git a/docs/6/eloquent/getting-started.md b/docs/6/eloquent/getting-started.md index ddee113b0d..6cb314721a 100644 --- a/docs/6/eloquent/getting-started.md +++ b/docs/6/eloquent/getting-started.md @@ -99,11 +99,11 @@ And can be queried like this: Lighthouse provides directives such as [@eq](../api-reference/directives.md#eq) to enhance your queries with additional constraints. -The following field definition allows clients to fetch a single User by ID. +The following field definition allows clients to find a user by their email: ```graphql type Query { - user(id: ID! @eq): User @find + user(email: String! @eq): User @find } ``` @@ -111,7 +111,8 @@ Query the field like this: ```graphql { - user(id: 69) { + user(email: "chuck@nor.ris") { + id name } } @@ -123,6 +124,7 @@ If found, the result will look like this: { "data": { "user": { + "id": "69420", "name": "Chuck Norris" } } diff --git a/docs/6/federation/getting-started.md b/docs/6/federation/getting-started.md index 2332ee21cf..ba3a45a9fb 100644 --- a/docs/6/federation/getting-started.md +++ b/docs/6/federation/getting-started.md @@ -50,6 +50,19 @@ extend schema @link( ) ``` +## Federated tracing + +In order to use federated tracing, you need to enabled [tracing](../performance/tracing.md) +and set the driver to `Nuwave\Lighthouse\Tracing\FederatedTracing\FederatedTracing::class` in your `config/lighthouse.php`: + +```php +'tracing' => [ + 'driver' => Nuwave\Lighthouse\Tracing\FederatedTracing\FederatedTracing::class, +], +``` + +Note that federated tracing requires `google/protobuf` to be installed (for better performance you can also install the `protobuf` php extension). + ### Unsupported features Some features of the Apollo Federation specification **are not supported** by Lighthouse: @@ -86,6 +99,3 @@ type Book @federation__shareable { } ``` -#### Federated tracing - -[Federated tracing](https://www.apollographql.com/docs/federation/metrics) is not supported. diff --git a/docs/6/performance/tracing.md b/docs/6/performance/tracing.md index 738f6e1334..eca71d2d96 100644 --- a/docs/6/performance/tracing.md +++ b/docs/6/performance/tracing.md @@ -1,7 +1,6 @@ # Tracing Tracing offers field-level performance monitoring for your GraphQL server. -Lighthouse follows the [Apollo Tracing response format](https://github.com/apollographql/apollo-tracing#response-format). ## Setup @@ -12,3 +11,17 @@ Add the service provider to your `config/app.php` \Nuwave\Lighthouse\Tracing\TracingServiceProvider::class, ], ``` + +## Drivers + +Lighthouse tracing is implemented though drivers, this allows supporting different tracing formats. + +Lighthouse includes the following drivers: + +- `Nuwave\Lighthouse\Tracing\ApolloTracing\ApolloTracing::class` (default) which implements [Apollo Tracing response format](https://github.com/apollographql/apollo-tracing#response-format) +- `Nuwave\Lighthouse\Tracing\FederatedTracing\FederatedTracing::class` which implements [Apollo Federated tracing](https://www.apollographql.com/docs/federation/metrics/) + +### Federated tracing + +Federated tracing driver requires `google/protobuf` to be installed. +For better performance you can also install the `protobuf` php extension. diff --git a/docs/6/security/authorization.md b/docs/6/security/authorization.md index ca8243fdc5..2f6d583f93 100644 --- a/docs/6/security/authorization.md +++ b/docs/6/security/authorization.md @@ -96,7 +96,7 @@ the resolved model instances. ```graphql type Query { - post(id: ID! @eq): Post + post(id: ID! @whereKey): Post @can(ability: "view", resolved: true) @find @softDeletes diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index e79a8e1db4..906d326164 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -1193,7 +1193,7 @@ directive @find( ```graphql type Query { - userById(id: ID! @eq): User @find + userById(id: ID! @whereKey): User @find } ``` @@ -1204,7 +1204,7 @@ If your model does not sit in the default namespace, you can overwrite it. ```graphql type Query { - userById(id: ID! @eq): User @find(model: "App\\Authentication\\User") + userById(id: ID! @whereKey): User @find(model: "App\\Authentication\\User") } ``` diff --git a/docs/master/eloquent/getting-started.md b/docs/master/eloquent/getting-started.md index ddee113b0d..6cb314721a 100644 --- a/docs/master/eloquent/getting-started.md +++ b/docs/master/eloquent/getting-started.md @@ -99,11 +99,11 @@ And can be queried like this: Lighthouse provides directives such as [@eq](../api-reference/directives.md#eq) to enhance your queries with additional constraints. -The following field definition allows clients to fetch a single User by ID. +The following field definition allows clients to find a user by their email: ```graphql type Query { - user(id: ID! @eq): User @find + user(email: String! @eq): User @find } ``` @@ -111,7 +111,8 @@ Query the field like this: ```graphql { - user(id: 69) { + user(email: "chuck@nor.ris") { + id name } } @@ -123,6 +124,7 @@ If found, the result will look like this: { "data": { "user": { + "id": "69420", "name": "Chuck Norris" } } diff --git a/docs/master/security/authorization.md b/docs/master/security/authorization.md index ca8243fdc5..2f6d583f93 100644 --- a/docs/master/security/authorization.md +++ b/docs/master/security/authorization.md @@ -96,7 +96,7 @@ the resolved model instances. ```graphql type Query { - post(id: ID! @eq): Post + post(id: ID! @whereKey): Post @can(ability: "view", resolved: true) @find @softDeletes From 6fbd21b11dc92be683533655b6a393d160e67f8a Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 11 Dec 2023 11:04:39 +0100 Subject: [PATCH 07/14] Improve guard directive docs --- UPGRADE.md | 9 +++++---- docs/6/api-reference/directives.md | 10 +++++----- docs/master/api-reference/directives.md | 10 +++++----- src/Auth/GuardDirective.php | 2 +- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 1c281eefaf..59e24c2287 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -388,11 +388,10 @@ class SomeField ### Replace `@middleware` with `@guard` and specialized FieldMiddleware -The `@middleware` directive has been removed, as it violates the boundary between HTTP and GraphQL -request handling. +The `@middleware` directive has been removed, as it violates the boundary between HTTP and GraphQL request handling. +Laravel middleware acts upon the HTTP request as a whole, whereas field middleware must only apply to a part of it. -Authentication is one of most common use cases for `@middleware`. You can now use -the [@guard](docs/master/api-reference/directives.md#guard) directive on selected fields. +If you used `@middleware` for authentication, replace it with [@guard](docs/master/api-reference/directives.md#guard): ```diff type Query { @@ -405,6 +404,8 @@ Note that [@guard](docs/master/api-reference/directives.md#guard) does not log i To ensure the user is logged in, add the `AttemptAuthenticate` middleware to your `lighthouse.php` middleware config, see the [default config](src/lighthouse.php) for an example. +If you used `@middleware` for authorization, replace it with [@can](docs/master/api-reference/directives.md#can). + Other functionality can be replaced by a custom [`FieldMiddleware`](docs/master/custom-directives/field-directives.md#fieldmiddleware) directive. Just like Laravel Middleware, it can wrap around individual field resolvers. diff --git a/docs/6/api-reference/directives.md b/docs/6/api-reference/directives.md index 906d326164..abfebc4da2 100644 --- a/docs/6/api-reference/directives.md +++ b/docs/6/api-reference/directives.md @@ -1459,7 +1459,7 @@ own mechanism of encoding/decoding global ids. ```graphql """ -Run authentication through one or more guards. +Run authentication through one or more guards from `config/auth.php`. This is run per field and may allow unauthenticated users to still receive partial results. @@ -1495,7 +1495,7 @@ on all of them at once. extend type Query @guard { ... } ``` -The `@guard` directive will be prepended to other directives defined on the fields +The [@guard](#guard) directive will be prepended to other directives defined on the fields and thus executes before them. ```graphql @@ -2400,7 +2400,7 @@ directive @node( ) on OBJECT ``` -When you use `@node` on a type, Lighthouse will add a field `node` to the root Query type. +When you use [@node](#node) on a type, Lighthouse will add a field `node` to the root Query type. If you want to customize its description, change the resolver or add middleware, you can add it yourself like this: ```graphql @@ -2931,7 +2931,7 @@ final class Blog You can provide your own function that resolves the field by directly returning data in a `\Illuminate\Contracts\Pagination\Paginator` instance. -This is mutually exclusive with `builder` and `model`. Not compatible with `scopes` and builder arguments such as `@eq`. +This is mutually exclusive with `builder` and `model`. Not compatible with `scopes` and builder arguments such as [@eq](#eq). ```graphql type Query { @@ -4094,4 +4094,4 @@ type User { } ``` -If you just want to return the count itself as-is, use [`@count`](#count). +If you just want to return the count itself as-is, use [@count](#count). diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index 906d326164..abfebc4da2 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -1459,7 +1459,7 @@ own mechanism of encoding/decoding global ids. ```graphql """ -Run authentication through one or more guards. +Run authentication through one or more guards from `config/auth.php`. This is run per field and may allow unauthenticated users to still receive partial results. @@ -1495,7 +1495,7 @@ on all of them at once. extend type Query @guard { ... } ``` -The `@guard` directive will be prepended to other directives defined on the fields +The [@guard](#guard) directive will be prepended to other directives defined on the fields and thus executes before them. ```graphql @@ -2400,7 +2400,7 @@ directive @node( ) on OBJECT ``` -When you use `@node` on a type, Lighthouse will add a field `node` to the root Query type. +When you use [@node](#node) on a type, Lighthouse will add a field `node` to the root Query type. If you want to customize its description, change the resolver or add middleware, you can add it yourself like this: ```graphql @@ -2931,7 +2931,7 @@ final class Blog You can provide your own function that resolves the field by directly returning data in a `\Illuminate\Contracts\Pagination\Paginator` instance. -This is mutually exclusive with `builder` and `model`. Not compatible with `scopes` and builder arguments such as `@eq`. +This is mutually exclusive with `builder` and `model`. Not compatible with `scopes` and builder arguments such as [@eq](#eq). ```graphql type Query { @@ -4094,4 +4094,4 @@ type User { } ``` -If you just want to return the count itself as-is, use [`@count`](#count). +If you just want to return the count itself as-is, use [@count](#count). diff --git a/src/Auth/GuardDirective.php b/src/Auth/GuardDirective.php index e36beff35a..4a466415aa 100644 --- a/src/Auth/GuardDirective.php +++ b/src/Auth/GuardDirective.php @@ -30,7 +30,7 @@ public static function definition(): string { return /** @lang GraphQL */ <<<'GRAPHQL' """ -Run authentication through one or more guards. +Run authentication through one or more guards from `config/auth.php`. This is run per field and may allow unauthenticated users to still receive partial results. From 9cc3ee2dd91748975a014fc70594db3ecfce8111 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 11 Dec 2023 11:19:06 +0100 Subject: [PATCH 08/14] Fix codestyle --- composer.json | 6 +-- docs/6/api-reference/directives.md | 14 +++--- docs/6/federation/getting-started.md | 49 +++++++++---------- docs/master/api-reference/directives.md | 14 +++--- docs/master/federation/getting-started.md | 49 +++++++++---------- .../FederatedTracing/Proto/FieldStat.php | 2 +- .../Proto/QueryLatencyStats.php | 2 +- src/Tracing/FederatedTracing/Proto/Trace.php | 10 ++-- .../Proto/Trace/QueryPlanNode/FetchNode.php | 6 +-- 9 files changed, 71 insertions(+), 81 deletions(-) diff --git a/composer.json b/composer.json index 8dc74b9f93..818c54bb09 100644 --- a/composer.json +++ b/composer.json @@ -73,14 +73,14 @@ "thecodingmachine/phpstan-safe-rule": "^1.2" }, "suggest": { + "ext-protobuf": "Improve protobuf serialization performance (used for tracing)", "bensampo/laravel-enum": "Convenient enum definitions that can easily be registered in your Schema", + "google/protobuf": "Required when using the tracing driver federated-tracing", "laravel/pennant": "Required for the @feature directive", "laravel/scout": "Required for the @search directive", "mll-lab/graphql-php-scalars": "Useful scalar types, required for @whereConditions", "mll-lab/laravel-graphiql": "A graphical interactive in-browser GraphQL IDE - integrated with Laravel", - "pusher/pusher-php-server": "Required when using the Pusher Subscriptions driver", - "google/protobuf": "Required when using the tracing driver federated-tracing", - "ext-protobuf": "Improve protobuf serialization performance (used for tracing)" + "pusher/pusher-php-server": "Required when using the Pusher Subscriptions driver" }, "minimum-stability": "dev", "prefer-stable": true, diff --git a/docs/6/api-reference/directives.md b/docs/6/api-reference/directives.md index abfebc4da2..dfeddf97e0 100644 --- a/docs/6/api-reference/directives.md +++ b/docs/6/api-reference/directives.md @@ -2293,22 +2293,22 @@ The following example shows how one can namespace queries and mutations. ```graphql type Query { - post: PostQueries! @namespaced + post: PostQueries! @namespaced } type PostQueries { - find(id: ID! @whereKey): Post @find - list(title: String @where(operator: "like")): [Post!]! @paginate + find(id: ID! @whereKey): Post @find + list(title: String @where(operator: "like")): [Post!]! @paginate } type Mutation { - post: PostMutations! @namespaced + post: PostMutations! @namespaced } type PostMutations { - create(input: PostCreateInput! @spread): Post! @create - update(input: PostUpdateInput! @spread): Post! @update - delete(id: ID! @whereKey): Post! @delete + create(input: PostCreateInput! @spread): Post! @create + update(input: PostUpdateInput! @spread): Post! @update + delete(id: ID! @whereKey): Post! @delete } ``` diff --git a/docs/6/federation/getting-started.md b/docs/6/federation/getting-started.md index ba3a45a9fb..e90f5ab584 100644 --- a/docs/6/federation/getting-started.md +++ b/docs/6/federation/getting-started.md @@ -32,22 +32,23 @@ Support for Apollo Federation v2 is `opt-in` and can be enabled by adding the fo See [the Apollo documentation on federated directives](https://www.apollographql.com/docs/federation/federated-types/federated-directives) for the latest spec. ```graphql -extend schema @link( - url: "https://specs.apollo.dev/federation/v2.3", +extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" import: [ - "@composeDirective", - "@extends", - "@external", - "@inaccessible", - "@interfaceObject", - "@key", - "@override", - "@provides", - "@requires", - "@shareable", - "@tag" + "@composeDirective" + "@extends" + "@external" + "@inaccessible" + "@interfaceObject" + "@key" + "@override" + "@provides" + "@requires" + "@shareable" + "@tag" ] -) + ) ``` ## Federated tracing @@ -74,13 +75,10 @@ You can only use the default names. ```graphql extend schema -@link( - url: "https://specs.apollo.dev/federation/v2.3", - import: [ - { name: "@key", as: "@uniqueKey" }, - "@shareable" - ] -) + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: [{ name: "@key", as: "@uniqueKey" }, "@shareable"] + ) ``` #### Namespaced directives @@ -89,13 +87,10 @@ Using directives from a namespace without an import is not supported. You should import the directive and use the default name. ```graphql -extend schema @link( - url: "https://specs.apollo.dev/federation/v2.3", - import: ["@key"] -) +extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) type Book @federation__shareable { - title: String! + title: String! } ``` - diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index abfebc4da2..dfeddf97e0 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -2293,22 +2293,22 @@ The following example shows how one can namespace queries and mutations. ```graphql type Query { - post: PostQueries! @namespaced + post: PostQueries! @namespaced } type PostQueries { - find(id: ID! @whereKey): Post @find - list(title: String @where(operator: "like")): [Post!]! @paginate + find(id: ID! @whereKey): Post @find + list(title: String @where(operator: "like")): [Post!]! @paginate } type Mutation { - post: PostMutations! @namespaced + post: PostMutations! @namespaced } type PostMutations { - create(input: PostCreateInput! @spread): Post! @create - update(input: PostUpdateInput! @spread): Post! @update - delete(id: ID! @whereKey): Post! @delete + create(input: PostCreateInput! @spread): Post! @create + update(input: PostUpdateInput! @spread): Post! @update + delete(id: ID! @whereKey): Post! @delete } ``` diff --git a/docs/master/federation/getting-started.md b/docs/master/federation/getting-started.md index ba3a45a9fb..e90f5ab584 100644 --- a/docs/master/federation/getting-started.md +++ b/docs/master/federation/getting-started.md @@ -32,22 +32,23 @@ Support for Apollo Federation v2 is `opt-in` and can be enabled by adding the fo See [the Apollo documentation on federated directives](https://www.apollographql.com/docs/federation/federated-types/federated-directives) for the latest spec. ```graphql -extend schema @link( - url: "https://specs.apollo.dev/federation/v2.3", +extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.3" import: [ - "@composeDirective", - "@extends", - "@external", - "@inaccessible", - "@interfaceObject", - "@key", - "@override", - "@provides", - "@requires", - "@shareable", - "@tag" + "@composeDirective" + "@extends" + "@external" + "@inaccessible" + "@interfaceObject" + "@key" + "@override" + "@provides" + "@requires" + "@shareable" + "@tag" ] -) + ) ``` ## Federated tracing @@ -74,13 +75,10 @@ You can only use the default names. ```graphql extend schema -@link( - url: "https://specs.apollo.dev/federation/v2.3", - import: [ - { name: "@key", as: "@uniqueKey" }, - "@shareable" - ] -) + @link( + url: "https://specs.apollo.dev/federation/v2.3" + import: [{ name: "@key", as: "@uniqueKey" }, "@shareable"] + ) ``` #### Namespaced directives @@ -89,13 +87,10 @@ Using directives from a namespace without an import is not supported. You should import the directive and use the default name. ```graphql -extend schema @link( - url: "https://specs.apollo.dev/federation/v2.3", - import: ["@key"] -) +extend schema + @link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@key"]) type Book @federation__shareable { - title: String! + title: String! } ``` - diff --git a/src/Tracing/FederatedTracing/Proto/FieldStat.php b/src/Tracing/FederatedTracing/Proto/FieldStat.php index 995f4f6466..fe55b37cf2 100644 --- a/src/Tracing/FederatedTracing/Proto/FieldStat.php +++ b/src/Tracing/FederatedTracing/Proto/FieldStat.php @@ -89,7 +89,7 @@ class FieldStat extends \Google\Protobuf\Internal\Message * field_execution_weight). * @var int|string $observed_execution_count * Number of times that the resolver for this field is directly observed being - * executed. + * executed * @var int|string $estimated_execution_count * Same as `observed_execution_count` but potentially scaled upwards if the server was only * performing field-level instrumentation on a sampling of operations. For diff --git a/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php b/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php index a62ca72476..cef9dd93f5 100644 --- a/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php +++ b/src/Tracing/FederatedTracing/Proto/QueryLatencyStats.php @@ -106,7 +106,7 @@ class QueryLatencyStats extends \Google\Protobuf\Internal\Message * @var int|string $persisted_query_misses * @var array|array|\Google\Protobuf\Internal\RepeatedField $cache_latency_count * This array includes the latency buckets for all operations included in cache_hits - * See comment on latency_count for details. + * See comment on latency_count for details * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\PathErrorStats $root_error_stats * Paths and counts for each error. The total number of requests with errors within this object should be the same as * requests_with_errors_count below. diff --git a/src/Tracing/FederatedTracing/Proto/Trace.php b/src/Tracing/FederatedTracing/Proto/Trace.php index a8fab65164..f8fe37ed32 100644 --- a/src/Tracing/FederatedTracing/Proto/Trace.php +++ b/src/Tracing/FederatedTracing/Proto/Trace.php @@ -170,15 +170,15 @@ class Trace extends \Google\Protobuf\Internal\Message * Optional. Data for populating the Message object. * * @var \Google\Protobuf\Timestamp $start_time - * Wallclock time when the trace began. + * Wallclock time when the trace began * @var \Google\Protobuf\Timestamp $end_time - * Wallclock time when the trace ended. + * Wallclock time when the trace ended * @var int|string $duration_ns * High precision duration of the trace; may not equal end_time-start_time - * (eg, if your machine's clock changed during the trace). + * (eg, if your machine's clock changed during the trace) * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Node $root * A tree containing information about all resolvers run directly by this - * service, including errors. + * service, including errors * @var bool $is_incomplete * If this is true, the trace is potentially missing some nodes that were * present on the query plan. This can happen if the trace span buffer used @@ -201,7 +201,7 @@ class Trace extends \Google\Protobuf\Internal\Message * @var string $unexecutedOperationBody * Optional: when GraphQL parsing or validation against the GraphQL schema fails, these fields * can include reference to the operation being sent for users to dig into the set of operations - * that are failing validation. + * that are failing validation * @var string $unexecutedOperationName * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace\Details $details * @var string $client_name diff --git a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php index f1a4a029fa..edc48c8653 100644 --- a/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php +++ b/src/Tracing/FederatedTracing/Proto/Trace/QueryPlanNode/FetchNode.php @@ -66,12 +66,12 @@ class FetchNode extends \Google\Protobuf\Internal\Message * @var \Nuwave\Lighthouse\Tracing\FederatedTracing\Proto\Trace $trace * This Trace only contains start_time, end_time, duration_ns, and root; * all timings were calculated **on the subgraph**, and clock skew - * will be handled by the ingress server. + * will be handled by the ingress server * @var int|string $sent_time_offset - * relative to the outer trace's start_time, in ns, measured in the Router/Gateway. + * relative to the outer trace's start_time, in ns, measured in the Router/Gateway * @var \Google\Protobuf\Timestamp $sent_time * Wallclock times measured in the Router/Gateway for when this operation was - * sent and received. + * sent and received * @var \Google\Protobuf\Timestamp $received_time * } */ From e7a0103bcb9bc391f3657bddd3368efe9065c1cc Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Mon, 11 Dec 2023 11:23:36 +0100 Subject: [PATCH 09/14] Add `scalar link__Import` and `enum link__Purpose` to `@link` definition --- src/Federation/Directives/LinkDirective.php | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Federation/Directives/LinkDirective.php b/src/Federation/Directives/LinkDirective.php index eb73659639..74a09720c1 100644 --- a/src/Federation/Directives/LinkDirective.php +++ b/src/Federation/Directives/LinkDirective.php @@ -22,6 +22,36 @@ public static function definition(): string https://www.apollographql.com/docs/federation/federated-types/federated-directives#the-link-directive """ directive @link(url: String!, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +""" +An element to import into the document. + +```graphql +@link(url: "https://specs.apollo.dev/link/v1.0", import: ["@link", "Purpose"]) +``` + +or an object with name and (optionally as): + +```graphql +@link(url: "https://specs.apollo.dev/link/v1.0", import: [{ name: "Purpose", as: "LinkPurpose" }]) +``` +""" +scalar link__Import + +""" +The role of a @linked schema. +""" +enum link__Purpose { + """ + Provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + Provide metadata necessary to correctly resolve fields. + """ + EXECUTION +} GRAPHQL; } } From 5d24ac1660c210ed60d68c3159d23023753351c4 Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 11 Dec 2023 11:24:17 +0100 Subject: [PATCH 10/14] v6.26.1 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ec35a4c1b..c3e719dcea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +## v6.26.1 + +### Fixed + +- Add `scalar link__Import` and `enum link__Purpose` to `@link` definition https://github.com/nuwave/lighthouse/pull/2484 + ## v6.26.0 ### Added From e2092a9854fcd674d8d00c3994ffe4f99659a8ea Mon Sep 17 00:00:00 2001 From: Konstantin Babushkin Date: Mon, 11 Dec 2023 13:54:39 +0100 Subject: [PATCH 11/14] @can directive improvements (#2483) --- CHANGELOG.md | 6 + UPGRADE.md | 30 ++ docs/master/api-reference/directives.md | 172 ++++++--- docs/master/eloquent/nested-mutations.md | 2 +- docs/master/security/authorization.md | 96 ++++- src/Auth/BaseCanDirective.php | 161 ++++++++ src/Auth/CanFindDirective.php | 130 +++++++ src/Auth/CanModelDirective.php | 38 ++ src/Auth/CanQueryDirective.php | 50 +++ src/Auth/CanResolvedDirective.php | 61 +++ src/Auth/CanRootDirective.php | 32 ++ tests/Integration/Auth/CanDirectiveDBTest.php | 16 +- .../Auth/CanFindDirectiveDBTest.php | 364 ++++++++++++++++++ .../Auth/CanQueryDirectiveDBTest.php | 175 +++++++++ .../Auth/CanResolvedDirectiveDBTest.php | 140 +++++++ tests/Unit/Auth/CanDirectiveTestBase.php | 180 +++++++++ tests/Unit/Auth/CanModelDirectiveTest.php | 23 ++ tests/Unit/Auth/CanResolvedDirectiveTest.php | 100 +++++ tests/Unit/Auth/CanRootDirectiveTest.php | 180 +++++++++ tests/Utils/Policies/UserPolicy.php | 24 +- 20 files changed, 1890 insertions(+), 90 deletions(-) create mode 100644 src/Auth/BaseCanDirective.php create mode 100644 src/Auth/CanFindDirective.php create mode 100644 src/Auth/CanModelDirective.php create mode 100644 src/Auth/CanQueryDirective.php create mode 100644 src/Auth/CanResolvedDirective.php create mode 100644 src/Auth/CanRootDirective.php create mode 100644 tests/Integration/Auth/CanFindDirectiveDBTest.php create mode 100644 tests/Integration/Auth/CanQueryDirectiveDBTest.php create mode 100644 tests/Integration/Auth/CanResolvedDirectiveDBTest.php create mode 100644 tests/Unit/Auth/CanDirectiveTestBase.php create mode 100644 tests/Unit/Auth/CanModelDirectiveTest.php create mode 100644 tests/Unit/Auth/CanResolvedDirectiveTest.php create mode 100644 tests/Unit/Auth/CanRootDirectiveTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index c3e719dcea..60e97f1308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +### Added + +- Split up `@can` directive into `@canFind`, `@canModel`, `@canQuery`, `@canResolved` and `@canRoot` https://github.com/nuwave/lighthouse/pull/2483 +- Added `action` and `returnValue` arguments to `@can*` family of directives https://github.com/nuwave/lighthouse/pull/2483 +- Allows using any objects in `@can*` family of directives https://github.com/nuwave/lighthouse/pull/2483 + ## v6.26.1 ### Fixed diff --git a/UPGRADE.md b/UPGRADE.md index 59e24c2287..d5a8c6c14e 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -24,6 +24,36 @@ It will prevent the following type of HTTP requests: - `GET` requests - `POST` requests that can be created using HTML forms +### `@can` directive is replaced with `@can*` directives + +The `@can` directive was removed in favor of more specialized directives: +- with `find` field set: `@canFind` +- with `query` field set: `@canQuery` +- with `root` field set: `@canRoot` +- with `resolved` field set: `@canResolved` +- if none of the above are set: `@canModel` + +```diff +type Mutation { +- createPost(input: PostInput! @spread): Post! @can(ability: "create") @create ++ createPost(input: PostInput! @spread): Post! @canModel(ability: "create") @create +- updatePost(input: PostInput! @spread): Post! @can(find: "input.id", ability: "edit") @update ++ updatePost(input: PostInput! @spread): Post! @canFind(find: "input.id", ability: "edit") @update +- deletePosts(ids: [ID!]! @whereKey): [Post!]! @can(query: true, ability: "delete") @delete ++ deletePosts(ids: [ID!]! @whereKey): [Post!]! @canQuery(ability: "delete") @delete +} + +type Query { +- posts: [Post!]! @can(resolved: true, ability: "view") @paginate ++ posts: [Post!]! @canResolved(ability: "view") @paginate +} + +type Post { +- sensitiveInformation: String @can(root: true, ability: "admin") ++ sensitiveInformation: String @canRoot(ability: "admin") +} +``` + ## v5 to v6 ### `messages` on `@rules` and `@rulesForArray` diff --git a/docs/master/api-reference/directives.md b/docs/master/api-reference/directives.md index dfeddf97e0..7c47fa4577 100644 --- a/docs/master/api-reference/directives.md +++ b/docs/master/api-reference/directives.md @@ -604,68 +604,86 @@ You can find usage examples of this directive in [the caching docs](../performan ## @can +Deprecated. Use the [@can* family of directives](#can-family-of-directives) instead. + +## @can* family of directives + +All @can* directives have common arguments. These arguments specify how gates are checked and what to do if the user is not authorized. +Each directive has its own set of arguments that specify what to check against. + ```graphql + """ +The ability to check permissions for. """ -Check a Laravel Policy to ensure the current user is authorized to access a field. +ability: String! -When `injectArgs` and `args` are used together, the client given -arguments will be passed before the static args. """ -directive @can( - """ - The ability to check permissions for. - """ - ability: String! +Pass along the client given input data as arguments to `Gate::check`. +""" +injectArgs: Boolean! = false - """ - Check the policy against the model instances returned by the field resolver. - Only use this if the field does not mutate data, it is run before checking. +""" +Statically defined arguments that are passed to `Gate::check`. - Mutually exclusive with `query`, `find`, and `root`. - """ - resolved: Boolean! = false +You may pass arbitrary GraphQL literals, +e.g.: [1, 2, 3] or { foo: "bar" } +""" +args: CanArgs - """ - Specify the class name of the model to use. - This is only needed when the default model detection does not work. - """ - model: String +""" +Action to do if the user is not authorized. +""" +action: CanAction! = EXCEPTION_PASS - """ - Pass along the client given input data as arguments to `Gate::check`. - """ - injectArgs: Boolean! = false +""" +Value to return if the user is not authorized and `action` is `RETURN_VALUE`. +""" +returnValue: CanArgs +""" +``` - """ - Statically defined arguments that are passed to `Gate::check`. +Types are specified as: +```graphql +""" +Any constant literal value: https://graphql.github.io/graphql-spec/draft/#sec-Input-Values +""" +scalar CanArgs - You may pass arbitrary GraphQL literals, - e.g.: [1, 2, 3] or { foo: "bar" } - """ - args: CanArgs +enum CanAction { + """ + Pass exception to the client. + """ + EXCEPTION_PASS - """ - Query for specific model instances to check the policy against, using arguments - with directives that add constraints to the query builder, such as `@eq`. + """ + Throw generic "not authorized" exception to conceal the real error. + """ + EXCEPTION_NOT_AUTHORIZED - Mutually exclusive with `resolved`, `find`, and `root`. - """ - query: Boolean! = false + """ + Return the value specified in `returnValue` argument to conceal the real error. + """ + RETURN_VALUE +} +``` - """ - Apply scopes to the underlying query. - """ - scopes: [String!] +You can find usage examples of these directives in [the authorization docs](../security/authorization.md#restrict-fields-through-policies). + +### @canFind +```graphql +""" +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Query for specific model instances to check the policy against, using primary key(s) from specified argument. +""" +directive @canFind( """ - If your policy checks against specific model instances, specify - the name of the field argument that contains its primary key(s). + Specify the name of the field argument that contains its primary key(s). You may pass the string in dot notation to use nested inputs. - - Mutually exclusive with `resolved`, `query`, and `root`. """ - find: String + find: String! """ Should the query fail when the models of `find` were not found? @@ -673,40 +691,68 @@ directive @can( findOrFail: Boolean! = true """ - If your policy should check against the root value. - - Mutually exclusive with `resolved`, `query`, and `find`. + Apply scopes to the underlying query. """ - root: Boolean! = false + scopes: [String!] ) repeatable on FIELD_DEFINITION +``` +### canModel + +```graphql """ -Any constant literal value: https://graphql.github.io/graphql-spec/draft/#sec-Input-Values +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Check the policy against the root model. """ -scalar CanArgs +directive @canRoot( + """ + The model name to check against. + """ + model: String + +) repeatable on FIELD_DEFINITION ``` -The name of the returned Type `Post` is used as the Model class, however you may overwrite this by -passing the `model` argument: +### @canQuery ```graphql -type Mutation { - createBlogPost(input: PostInput!): BlogPost - @can(ability: "create", model: "App\\Post") -} +""" +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Query for specific model instances to check the policy against, using arguments +with directives that add constraints to the query builder, such as `@eq`. +""" +directive @canQuery( + """ + Apply scopes to the underlying query. + """ + scopes: [String!] +) repeatable on FIELD_DEFINITION ``` -Check the policy against the resolved model instances with the `resolved` argument: +### @canResolved ```graphql -type Query { - fetchUserByEmail(email: String! @eq): User - @can(ability: "view", resolved: true) - @find -} +""" +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Check the policy against the model instances returned by the field resolver. +Only use this if the field does not mutate data, it is run before checking. +""" +directive @canResolved repeatable on FIELD_DEFINITION ``` -You can find usage examples of this directive in [the authorization docs](../security/authorization.md#restrict-fields-through-policies). +### @canRoot + +```graphql +""" +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Check the policy against the root object. +""" +directive @canRoot repeatable on FIELD_DEFINITION +``` ## @clearCache diff --git a/docs/master/eloquent/nested-mutations.md b/docs/master/eloquent/nested-mutations.md index 635d5ac3d2..4d376c9260 100644 --- a/docs/master/eloquent/nested-mutations.md +++ b/docs/master/eloquent/nested-mutations.md @@ -48,7 +48,7 @@ See [this issue](https://github.com/nuwave/lighthouse/issues/900) for further di ## Security considerations Lighthouse has no mechanism for fine-grained permissions of nested mutation operations. -Field directives such as [@can](../api-reference/directives.md#can) apply to the whole field. +Field directives such as [@can*](../api-reference/directives.md#can-family-of-directives) apply to the whole field. Make sure that fields with nested mutations are only available to users who are allowed to execute all reachable nested mutations. diff --git a/docs/master/security/authorization.md b/docs/master/security/authorization.md index 2f6d583f93..b99b3b3f6f 100644 --- a/docs/master/security/authorization.md +++ b/docs/master/security/authorization.md @@ -57,7 +57,7 @@ limited to seeing just those. ## Restrict fields through policies Lighthouse allows you to restrict field operations to a certain group of users. -Use the [@can](../api-reference/directives.md#can) directive +Use the [@can*](../api-reference/directives.md#can-family-of-directives) family of directives to leverage [Laravel Policies](https://laravel.com/docs/authorization) for authorization. Starting from Laravel 5.7, [authorization of guest users](https://laravel.com/docs/authorization#guest-users) is supported. @@ -66,11 +66,11 @@ Because of this, Lighthouse does **not** validate that the user is authenticated ### Protect mutations As an example, you might want to allow only admin users of your application to create posts. -Start out by defining [@can](../api-reference/directives.md#can) upon a mutation you want to protect: +Start out by defining [@canModel](../api-reference/directives.md#canmodel) upon a mutation you want to protect: ```graphql type Mutation { - createPost(input: PostInput): Post @can(ability: "create") + createPost(input: PostInput): Post @canModel(ability: "create") } ``` @@ -86,18 +86,44 @@ final class PostPolicy } ``` -### Protect specific model instances +### Protect mutations using database queries -For some models, you may want to restrict access for specific instances of a model. -Set the `resolved` argument to `true` to have Lighthouse check permissions against -the resolved model instances. +You can also protect specific models by using the [@canFind](../api-reference/directives.md#canfind) +or [@canQuery](../api-reference/directives.md#canquery) directive. +They will query the database and check the specified policy against the result. + +```graphql +type Mutation { + updatePost(input: UpdatePostInput! @spread): Post! @canFind(ability: "edit", find: "input.id") @update +} + +input PostInput { + id: ID! + title: String +} +``` + +```php +final class PostPolicy +{ + public function edit(User $user, Post $post): bool + { + return $user->id === $post->author_id; + } +} +``` + +### Protect resolved model instances + +For some models, you may want to restrict access for already resolved instance of a model. +Use the [@canResolved](../api-reference/directives.md#canresolved) directive to do so. > This will actually run the field before checking permissions, do not use in mutations. ```graphql type Query { post(id: ID! @whereKey): Post - @can(ability: "view", resolved: true) + @canResolved(ability: "view") @find @softDeletes } @@ -113,14 +139,42 @@ final class PostPolicy } ``` +### Protect fields + +You can protect fields with the [@canRoot](../api-reference/directives.md#canroot) directive. +It checks against the resolved root object. + +This example shows how to restrict reading the `email` field to only the user itself: + +```graphql +type Query { + user(id: ID! @whereKey): User @find +} + +type User { + email: String! @canRoot(ability: "viewEmail") +} +``` + +```php +final class UserPolicy +{ + public function viewEmail(User $actor, User $target): bool + { + return $actor->id === $target->author_id; + } +} +``` + ### Passing additional arguments You can pass additional arguments to the policy checks by specifying them as `args`: ```graphql type Mutation { - createPost(input: PostInput): Post - @can(ability: "create", args: ["FROM_GRAPHQL"]) + createPost(input: CreatePostInput! @spread): Post! + @create + @canModel(ability: "create", args: ["FROM_GRAPHQL"]) } ``` @@ -139,7 +193,7 @@ with the `injectArgs` argument: ```graphql type Mutation { - createPost(title: String!): Post @can(ability: "create", injectArgs: true) + createPost(title: String!): Post @canModel(ability: "create", injectArgs: true) @create } ``` @@ -163,6 +217,26 @@ final class PostPolicy } ``` +### Concealing the existence of a model or other errors + +When a user is not authorized to access a model, you may want to hide the existence of the model. +This can be done by setting action to either EXCEPTION_NOT_AUTHORIZED or RETURN_VALUE. + +In the first case it would always return the generic "not authorized" exception. +In the second case it would return value which you can specify in the `returnValue` argument. + +```graphql +type Query { + user(id: ID! @whereKey): User @find +} + +type User { + banned: Boolean! @canRoot(ability: "admin", action: RETURN_VALUE, returnValue: false) +} +``` + +The `banned` field would return false for all users who are not authorized to access it. + ## Custom field restrictions For applications with role management, it is common to hide some model attributes from a diff --git a/src/Auth/BaseCanDirective.php b/src/Auth/BaseCanDirective.php new file mode 100644 index 0000000000..abb75a6fee --- /dev/null +++ b/src/Auth/BaseCanDirective.php @@ -0,0 +1,161 @@ +directiveArgValue('ability'); + + $fieldValue->wrapResolver(fn (callable $resolver): \Closure => function (mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver, $ability) { + $gate = $this->gate->forUser($context->user()); + $checkArguments = $this->buildCheckArguments($args); + $authorizeModel = fn (mixed $model) => $this->authorizeModel($gate, $ability, $model, $checkArguments); + + try { + return $this->authorizeRequest($root, $args, $context, $resolveInfo, $resolver, $authorizeModel); + } catch (\Throwable $e) { + $action = $this->directiveArgValue('action'); + if ($action === 'EXCEPTION_NOT_AUTHORIZED') { + throw new AuthorizationException(); + } + + if ($action === 'RETURN_VALUE') { + return $this->directiveArgValue('returnValue'); + } + + throw $e; + } + }); + } + + /** + * Authorizes request and resolves the field. + * + * @phpstan-import-type Resolver from \Nuwave\Lighthouse\Schema\Values\FieldValue as Resolver + * + * @param array $args + * @param callable(mixed, array, \Nuwave\Lighthouse\Support\Contracts\GraphQLContext, \Nuwave\Lighthouse\Execution\ResolveInfo): mixed $resolver + * @param callable(mixed): void $authorize + */ + abstract protected function authorizeRequest(mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo, callable $resolver, callable $authorize): mixed; + + /** + * @param string|array $ability + * @param array $arguments + */ + protected function authorizeModel(Gate $gate, string|array $ability, mixed $model, array $arguments): void + { + // The signature of the second argument `$arguments` of `Gate::check` + // should be [modelClassName, additionalArg, additionalArg...] + array_unshift($arguments, $model); + + Utils::applyEach( + static function ($ability) use ($gate, $arguments): void { + $response = $gate->inspect($ability, $arguments); + if ($response->denied()) { + throw new AuthorizationException($response->message(), $response->code()); + } + }, + $ability, + ); + } + + /** + * Additional arguments that are passed to @see Gate::check(). + * + * @param array $args + * + * @return array + */ + protected function buildCheckArguments(array $args): array + { + $checkArguments = []; + + // The injected args come before the static args + if ($this->directiveArgValue('injectArgs')) { + $checkArguments[] = $args; + } + + if ($this->directiveHasArgument('args')) { + $checkArguments[] = $this->directiveArgValue('args'); + } + + return $checkArguments; + } +} diff --git a/src/Auth/CanFindDirective.php b/src/Auth/CanFindDirective.php new file mode 100644 index 0000000000..71ced6f724 --- /dev/null +++ b/src/Auth/CanFindDirective.php @@ -0,0 +1,130 @@ +modelsToCheck($root, $args, $context, $resolveInfo) as $model) { + $authorize($model); + } + + return $resolver($root, $args, $context, $resolveInfo); + } + + /** + * @param array $args + * + * @return iterable<\Illuminate\Database\Eloquent\Model|class-string<\Illuminate\Database\Eloquent\Model>> + */ + protected function modelsToCheck(mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): iterable + { + $find = $this->directiveArgValue('find'); + $findValue = Arr::get($args, $find) + ?? throw self::missingKeyToFindModel($find); + + $queryBuilder = $this->getModelClass()::query(); + + $argumentSetDirectives = $resolveInfo->argumentSet->directives; + $directivesContainsForceDelete = $argumentSetDirectives->contains( + Utils::instanceofMatcher(ForceDeleteDirective::class), + ); + if ($directivesContainsForceDelete) { + /** @see \Illuminate\Database\Eloquent\SoftDeletes */ + // @phpstan-ignore-next-line because it involves mixins + $queryBuilder->withTrashed(); + } + + $directivesContainsRestore = $argumentSetDirectives->contains( + Utils::instanceofMatcher(RestoreDirective::class), + ); + if ($directivesContainsRestore) { + /** @see \Illuminate\Database\Eloquent\SoftDeletes */ + // @phpstan-ignore-next-line because it involves mixins + $queryBuilder->onlyTrashed(); + } + + try { + $enhancedBuilder = $resolveInfo->enhanceBuilder( + $queryBuilder, + $this->directiveArgValue('scopes', []), + $root, + $args, + $context, + $resolveInfo, + Utils::instanceofMatcher(TrashedDirective::class), + ); + assert($enhancedBuilder instanceof EloquentBuilder); + + $modelOrModels = $this->directiveArgValue('findOrFail', true) + ? $enhancedBuilder->findOrFail($findValue) + : $enhancedBuilder->find($findValue); + } catch (ModelNotFoundException $modelNotFoundException) { + throw new Error($modelNotFoundException->getMessage()); + } + + if ($modelOrModels instanceof Model) { + return [$modelOrModels]; + } + + if ($modelOrModels === null) { + return []; + } + + return $modelOrModels; + } + + public static function missingKeyToFindModel(string $find): Error + { + return new Error("Got no key to find a model at the expected input path: {$find}."); + } +} diff --git a/src/Auth/CanModelDirective.php b/src/Auth/CanModelDirective.php new file mode 100644 index 0000000000..6eadd7530e --- /dev/null +++ b/src/Auth/CanModelDirective.php @@ -0,0 +1,38 @@ +getModelClass()); + + return $resolver($root, $args, $context, $resolveInfo); + } +} diff --git a/src/Auth/CanQueryDirective.php b/src/Auth/CanQueryDirective.php new file mode 100644 index 0000000000..c8046f454e --- /dev/null +++ b/src/Auth/CanQueryDirective.php @@ -0,0 +1,50 @@ +enhanceBuilder( + $this->getModelClass()::query(), + $this->directiveArgValue('scopes', []), + $root, + $args, + $context, + $resolveInfo, + ) + ->get(); + foreach ($models as $model) { + $authorize($model); + } + + return $resolver($root, $args, $context, $resolveInfo); + } +} diff --git a/src/Auth/CanResolvedDirective.php b/src/Auth/CanResolvedDirective.php new file mode 100644 index 0000000000..7e4ee8e85e --- /dev/null +++ b/src/Auth/CanResolvedDirective.php @@ -0,0 +1,61 @@ +items() + : $modelLike; + + Utils::applyEach(function (mixed $model) use ($authorize): void { + $authorize($model); + }, $modelOrModels); + + return $modelLike; + }, + ); + } + + public function manipulateFieldDefinition(DocumentAST &$documentAST, FieldDefinitionNode &$fieldDefinition, ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode &$parentType): void + { + if ($parentType->name->value === RootType::MUTATION) { + throw new DefinitionException("Do not use @canResolved on mutation {$fieldDefinition->name->value}, it is unsafe as the resolver will run before checking permissions. Use @canFind or @canQuery instead."); + } + } +} diff --git a/src/Auth/CanRootDirective.php b/src/Auth/CanRootDirective.php new file mode 100644 index 0000000000..4f1814b28b --- /dev/null +++ b/src/Auth/CanRootDirective.php @@ -0,0 +1,32 @@ +schema = /** @lang GraphQL */ ' type Query { - user(id: ID @eq): User + user(id: ID @whereKey): User @can(ability: "view", find: "id") @first } @@ -63,7 +63,7 @@ public function testFailsToFindSpecificModel(): void $this->schema = /** @lang GraphQL */ ' type Query { - user(id: ID @eq): User + user(id: ID @whereKey): User @can(ability: "view", find: "id") @mock } @@ -101,7 +101,7 @@ public function testFailsToFindSpecificModelWithFindOrFailFalse(): void $this->schema = /** @lang GraphQL */ ' type Query { - user(id: ID @eq): User + user(id: ID @whereKey): User @can(ability: "view", find: "id", findOrFail: false) @mock } @@ -212,7 +212,7 @@ public function testThrowsIfNotAuthorized(): void $this->schema = /** @lang GraphQL */ ' type Query { - post(foo: ID! @eq): Post + post(foo: ID! @whereKey): Post @can(ability: "view", find: "foo") @mock } @@ -295,7 +295,7 @@ public function testWorksWithSoftDeletes(): void $this->schema = /** @lang GraphQL */ ' type Query { - task(id: ID! @eq): Task + task(id: ID! @whereKey): Task @can(ability: "adminOnly", find: "id") @softDeletes @find @@ -373,7 +373,7 @@ public function testFailsToFindSpecificModelWithQuery(): void $this->schema = /** @lang GraphQL */ ' type Query { - user(id: ID! @eq): User + user(id: ID! @whereKey): User @can(ability: "view", query: true) @find } @@ -458,7 +458,7 @@ public function testWorksWithSoftDeletesWithQuery(): void $this->schema = /** @lang GraphQL */ ' type Query { - task(id: ID! @eq): Task + task(id: ID! @whereKey): Task @can(ability: "adminOnly", query: true) @softDeletes @find @@ -587,7 +587,7 @@ public function testChecksAgainstMissingResolvedModelWithFind(): void $this->schema = /** @lang GraphQL */ ' type Query { - user(id: ID @eq): User + user(id: ID @whereKey): User @can(ability: "view", resolved: true) @find } diff --git a/tests/Integration/Auth/CanFindDirectiveDBTest.php b/tests/Integration/Auth/CanFindDirectiveDBTest.php new file mode 100644 index 0000000000..a75d36fdc9 --- /dev/null +++ b/tests/Integration/Auth/CanFindDirectiveDBTest.php @@ -0,0 +1,364 @@ +name = UserPolicy::ADMIN; + $this->be($admin); + + $user = factory(User::class)->create(); + assert($user instanceof User); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user(id: ID! @whereKey): User + @canFind(ability: "view", find: "id") + @first + } + + type User { + name: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + query ($id: ID!) { + user(id: $id) { + name + } + } + ', [ + 'id' => $user->getKey(), + ])->assertJson([ + 'data' => [ + 'user' => [ + 'name' => $user->name, + ], + ], + ]); + } + + public function testFailsToFindSpecificModel(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $this->mockResolverExpects( + $this->never(), + ); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user(id: ID! @whereKey): User + @canFind(ability: "view", find: "id") + @mock + } + + type User { + name: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + user(id: "not-present") { + name + } + } + ')->assertJson([ + 'data' => [ + 'user' => null, + ], + 'errors' => [ + [ + 'message' => 'No query results for model [Tests\Utils\Models\User] not-present', + ], + ], + ]); + } + + public function testFailsToFindSpecificModelConcealException(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $this->mockResolverExpects( + $this->never(), + ); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user(id: ID! @whereKey): User + @canFind(ability: "view", find: "id", action: EXCEPTION_NOT_AUTHORIZED) + @mock + } + + type User { + name: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + user(id: "not-present") { + name + } + } + ')->assertJson([ + 'data' => [ + 'user' => null, + ], + 'errors' => [ + [ + 'message' => 'This action is unauthorized.', + ], + ], + ]); + } + + public function testFailsToFindSpecificModelWithFindOrFailFalse(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $this->mockResolver(null); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user(id: ID! @whereKey): User + @canFind(ability: "view", find: "id", findOrFail: false) + @mock + } + + type User { + name: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + user(id: "not-present") { + name + } + } + ')->assertExactJson([ + 'data' => [ + 'user' => null, + ], + ]); + } + + public function testThrowsIfFindValueIsNotGiven(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user(id: ID): User + @canFind(ability: "view", find: "some.path") + @first + } + + type User { + name: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + user { + name + } + } + ')->assertGraphQLError(CanDirective::missingKeyToFindModel('some.path')); + } + + public function testFindUsingNestedInputWithDotNotation(): void + { + $user = factory(User::class)->create(); + assert($user instanceof User); + $this->be($user); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user(input: FindUserInput!): User + @canFind(ability: "view", find: "input.id") + @first + } + + type User { + name: String! + } + + input FindUserInput { + id: ID! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + query ($id: ID!) { + user(input: { + id: $id + }) { + name + } + } + ', [ + 'id' => $user->id, + ])->assertJson([ + 'data' => [ + 'user' => [ + 'name' => $user->name, + ], + ], + ]); + } + + public function testThrowsIfNotAuthorized(): void + { + $admin = new User(); + $admin->name = UserPolicy::ADMIN; + $this->be($admin); + + $author = factory(User::class)->create(); + assert($author instanceof User); + + $post = factory(Post::class)->make(); + assert($post instanceof Post); + $post->user()->associate($author); + $post->save(); + + $this->mockResolverExpects( + $this->never(), + ); + + $this->schema = /** @lang GraphQL */ ' + type Query { + post(foo: ID! @whereKey): Post + @canFind(ability: "view", find: "foo") + @mock + } + + type Post { + title: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + query ($foo: ID!) { + post(foo: $foo) { + title + } + } + ', [ + 'foo' => $post->id, + ])->assertGraphQLErrorMessage(AuthorizationException::MESSAGE); + } + + public function testHandleMultipleModels(): void + { + $admin = new User(); + $admin->name = UserPolicy::ADMIN; + $this->be($admin); + + $postA = factory(Post::class)->make(); + assert($postA instanceof Post); + $postA->user()->associate($admin); + $postA->save(); + + $postB = factory(Post::class)->make(); + assert($postB instanceof Post); + $postB->user()->associate($admin); + $postB->save(); + + $this->schema = /** @lang GraphQL */ ' + type Mutation { + deletePosts(ids: [ID!]! @whereKey): [Post!]! + @canFind(ability: "delete", find: "ids") + @delete + } + + type Post { + title: String! + } + ' . self::PLACEHOLDER_QUERY; + + $this->graphQL(/** @lang GraphQL */ ' + mutation ($ids: [ID!]!) { + deletePosts(ids: $ids) { + title + } + } + ', [ + 'ids' => [$postA->id, $postB->id], + ])->assertJson([ + 'data' => [ + 'deletePosts' => [ + [ + 'title' => $postA->title, + ], + [ + 'title' => $postB->title, + ], + ], + ], + ]); + } + + public function testWorksWithSoftDeletes(): void + { + $admin = new User(); + $admin->name = UserPolicy::ADMIN; + $this->be($admin); + + $task = factory(Task::class)->create(); + assert($task instanceof Task); + $task->delete(); + + $this->schema = /** @lang GraphQL */ ' + type Query { + task(id: ID! @whereKey): Task + @canFind(ability: "adminOnly", find: "id") + @softDeletes + @find + } + + type Task { + name: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + query ($id: ID!) { + task(id: $id, trashed: WITH) { + name + } + } + ', [ + 'id' => $task->id, + ])->assertJson([ + 'data' => [ + 'task' => [ + 'name' => $task->name, + ], + ], + ]); + } +} diff --git a/tests/Integration/Auth/CanQueryDirectiveDBTest.php b/tests/Integration/Auth/CanQueryDirectiveDBTest.php new file mode 100644 index 0000000000..8e63d1b389 --- /dev/null +++ b/tests/Integration/Auth/CanQueryDirectiveDBTest.php @@ -0,0 +1,175 @@ +name = UserPolicy::ADMIN; + $this->be($admin); + + $user = factory(User::class)->create(); + assert($user instanceof User); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user(name: String! @eq): User + @canQuery(ability: "view") + @first + } + + type User { + name: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + query ($name: String!) { + user(name: $name) { + name + } + } + ', [ + 'name' => $user->name, + ])->assertJson([ + 'data' => [ + 'user' => [ + 'name' => $user->name, + ], + ], + ]); + } + + public function testFailsToFindSpecificModelWithQuery(): void + { + $admin = new User(); + $admin->name = UserPolicy::ADMIN; + $this->be($admin); + + $this->mockResolverExpects( + $this->never(), + ); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user(id: ID! @whereKey): User + @canQuery(ability: "view", query: true) + @find + } + + type User { + id: ID! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + user(id: "not-present") { + id + } + } + ')->assertJson([ + 'data' => [ + 'user' => null, + ], + ]); + } + + public function testHandleMultipleModelsWithQuery(): void + { + $admin = new User(); + $admin->name = UserPolicy::ADMIN; + $this->be($admin); + + $postA = factory(Post::class)->make(); + assert($postA instanceof Post); + $postA->user()->associate($admin); + $postA->save(); + + $postB = factory(Post::class)->make(); + assert($postB instanceof Post); + $postB->user()->associate($admin); + $postB->save(); + + $this->schema = /** @lang GraphQL */ ' + type Mutation { + deletePosts(ids: [ID!]! @whereKey): [Post!]! + @canQuery(ability: "delete") + @delete + } + + type Post { + title: String! + } + ' . self::PLACEHOLDER_QUERY; + + $this->graphQL(/** @lang GraphQL */ ' + mutation ($ids: [ID!]!) { + deletePosts(ids: $ids) { + title + } + } + ', [ + 'ids' => [$postA->id, $postB->id], + ])->assertJson([ + 'data' => [ + 'deletePosts' => [ + [ + 'title' => $postA->title, + ], + [ + 'title' => $postB->title, + ], + ], + ], + ]); + } + + public function testWorksWithSoftDeletesWithQuery(): void + { + $admin = new User(); + $admin->name = UserPolicy::ADMIN; + $this->be($admin); + + $task = factory(Task::class)->create(); + assert($task instanceof Task); + $task->delete(); + + $this->schema = /** @lang GraphQL */ ' + type Query { + task(id: ID! @whereKey): Task + @canQuery(ability: "adminOnly") + @softDeletes + @find + } + + type Task { + name: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + query ($id: ID!) { + task(id: $id, trashed: WITH) { + name + } + } + ', [ + 'id' => $task->id, + ])->assertJson([ + 'data' => [ + 'task' => [ + 'name' => $task->name, + ], + ], + ]); + } +} diff --git a/tests/Integration/Auth/CanResolvedDirectiveDBTest.php b/tests/Integration/Auth/CanResolvedDirectiveDBTest.php new file mode 100644 index 0000000000..23fe6b31a6 --- /dev/null +++ b/tests/Integration/Auth/CanResolvedDirectiveDBTest.php @@ -0,0 +1,140 @@ +name = UserPolicy::ADMIN; + $this->be($user); + + $user = factory(User::class)->create(); + + $this->schema = /** @lang GraphQL */ ' + type Query { + users: [User!]! + @canResolved(ability: "view") + @paginate + } + + type User { + name: String + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + users(first: 2) { + data { + name + } + } + } + ')->assertJson([ + 'data' => [ + 'users' => [ + 'data' => [ + [ + 'name' => $user->name, + ], + ], + ], + ], + ]); + } + + public function testChecksAgainstRelation(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $company = factory(Company::class)->create(); + + $user = factory(User::class)->make(); + assert($user instanceof User); + $user->company()->associate($company); + $user->save(); + + $this->schema = /** @lang GraphQL */ ' + type Query { + company: Company @first + } + + type Company { + users: [User!]! + @canResolved(ability: "view") + @hasMany + } + + type User { + name: String + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + company { + users { + name + } + } + } + ')->assertJson([ + 'data' => [ + 'company' => [ + 'users' => [ + [ + 'name' => $user->name, + ], + ], + ], + ], + ]); + } + + public function testChecksAgainstMissingResolvedModelWithFind(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $user = factory(User::class)->create(); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user(id: ID! @whereKey): User + @canResolved(ability: "view") + @find + } + + type User { + name: String! + } + '; + + $this->graphQL(/** @lang GraphQL */ ' + { + user(id: "not-present") { + name + } + } + ')->assertJson([ + 'data' => [ + 'user' => null, + ], + 'errors' => [ + [ + 'message' => 'This action is unauthorized.', + ], + ], + ]); + } +} diff --git a/tests/Unit/Auth/CanDirectiveTestBase.php b/tests/Unit/Auth/CanDirectiveTestBase.php new file mode 100644 index 0000000000..021c07400a --- /dev/null +++ b/tests/Unit/Auth/CanDirectiveTestBase.php @@ -0,0 +1,180 @@ +graphQL($this->getQuery(), ['foo' => $foo]); + } + + public function testThrowsIfNotAuthorized(): void + { + $this->be(new User()); + + $this->schema = $this->getSchema('ability: "adminOnly"'); + + $this->query()->assertGraphQLErrorMessage(AuthorizationException::MESSAGE); + } + + public function testThrowsWithCustomMessageIfNotAuthorized(): void + { + $this->be(new User()); + + $this->schema = $this->getSchema('ability: "superAdminOnly"'); + + $this->query()->assertGraphQLErrorMessage(UserPolicy::SUPER_ADMINS_ONLY_MESSAGE); + } + + public function testThrowsFirstWithCustomMessageIfNotAuthorized(): void + { + $this->be(new User()); + + $this->schema = $this->getSchema('ability: ["superAdminOnly", "adminOnly"]'); + + $this->query()->assertGraphQLErrorMessage(UserPolicy::SUPER_ADMINS_ONLY_MESSAGE); + } + + public function testConcealsCustomMessage(): void + { + $this->be(new User()); + + $this->schema = $this->getSchema('ability: "superAdminOnly", action: EXCEPTION_NOT_AUTHORIZED'); + + $this->query()->assertGraphQLErrorMessage(AuthorizationException::MESSAGE); + } + + public function testReturnsValue(): void + { + $this->schema = $this->getSchema('ability: "superAdminOnly", action: RETURN_VALUE, returnValue: null'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => null, + ], + ]); + } + + public function testPassesAuthIfAuthorized(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $this->mockResolver(fn (): User => $this->resolveUser()); + + $this->schema = $this->getSchema('ability: "adminOnly"'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } + + public function testAcceptsGuestUser(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + + $this->schema = $this->getSchema('ability: "guestOnly"'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } + + public function testPassesMultiplePolicies(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $this->mockResolver(fn (): User => $this->resolveUser()); + + $this->schema = $this->getSchema('ability: ["adminOnly", "alwaysTrue"]'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } + + public function testProcessesTheArgsArgument(): void + { + $this->schema = $this->getSchema('ability: "dependingOnArg", args: [false]'); + + $this->query()->assertGraphQLErrorMessage(AuthorizationException::MESSAGE); + } + + public function testInjectArgsPassesClientArgumentToPolicy(): void + { + $this->be(new User()); + + $this->mockResolver(fn (): User => $this->resolveUser()); + + $this->schema = $this->getSchema('ability: "injectArgs", injectArgs: [true]'); + + $this->query('bar')->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } + + public function testInjectedArgsAndStaticArgs(): void + { + $this->be(new User()); + + $this->mockResolver(fn (): User => $this->resolveUser()); + + $this->schema = $this->getSchema('ability: "argsWithInjectedArgs", args: { foo: "static" }, injectArgs: true'); + + $this->query('dynamic')->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } + + public static function resolveUser(): User + { + $user = new User(); + $user->name = 'foo'; + $user->email = 'test@example.com'; + + return $user; + } +} diff --git a/tests/Unit/Auth/CanModelDirectiveTest.php b/tests/Unit/Auth/CanModelDirectiveTest.php new file mode 100644 index 0000000000..62faa7d45d --- /dev/null +++ b/tests/Unit/Auth/CanModelDirectiveTest.php @@ -0,0 +1,23 @@ +mockResolver(fn (): User => $this->resolveUser()); + parent::testThrowsIfNotAuthorized(); + } + + public function testThrowsWithCustomMessageIfNotAuthorized(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + parent::testThrowsWithCustomMessageIfNotAuthorized(); + } + + public function testThrowsFirstWithCustomMessageIfNotAuthorized(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + parent::testThrowsFirstWithCustomMessageIfNotAuthorized(); + } + + public function testReturnsValue(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + parent::testReturnsValue(); + } + + public function testProcessesTheArgsArgument(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + parent::testProcessesTheArgsArgument(); + } + + public function testChecksAgainstResolvedModels(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $this->mockResolver(fn (): User => $this->resolveUser()); + + $this->schema = $this->getSchema('ability: "view"'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } + + public function testChecksAgainstObject(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $return = new class() { + public string $name = 'foo'; + }; + $this->mockResolver(fn (): object => $return); + + $this->app + ->make(Gate::class) + ->define('customObject', fn (User $authorizedUser, object $root) => $authorizedUser === $user && $root == $return); + + $this->schema = $this->getSchema('ability: "customObject"'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } +} diff --git a/tests/Unit/Auth/CanRootDirectiveTest.php b/tests/Unit/Auth/CanRootDirectiveTest.php new file mode 100644 index 0000000000..bd3260122c --- /dev/null +++ b/tests/Unit/Auth/CanRootDirectiveTest.php @@ -0,0 +1,180 @@ +mockResolver(fn (): User => $this->resolveUser()); + parent::testThrowsIfNotAuthorized(); + } + + public function testThrowsWithCustomMessageIfNotAuthorized(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + parent::testThrowsWithCustomMessageIfNotAuthorized(); + } + + public function testThrowsFirstWithCustomMessageIfNotAuthorized(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + parent::testThrowsFirstWithCustomMessageIfNotAuthorized(); + } + + public function testConcealsCustomMessage(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + parent::testConcealsCustomMessage(); + } + + public function testProcessesTheArgsArgument(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + parent::testProcessesTheArgsArgument(); + } + + public function testReturnsValue(): void + { + $this->mockResolver(fn (): User => $this->resolveUser()); + + $this->schema = $this->getSchema('ability: "superAdminOnly", action: RETURN_VALUE, returnValue: "concealed"'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'concealed', + ], + ], + ]); + } + + public function testChecksAgainstModel(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $this->mockResolver(fn (): User => $this->resolveUser()); + + $this->schema = $this->getSchema('ability: "view"'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } + + public function testChecksAgainstObject(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $return = new class() { + public string $name = 'foo'; + }; + $this->mockResolver(fn (): object => $return); + + $this->app + ->make(Gate::class) + ->define('customObject', fn (User $authorizedUser, object $root) => $authorizedUser === $user && $root == $return); + + $this->schema = $this->getSchema('ability: "customObject"'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } + + public function testChecksAgainstArray(): void + { + $user = new User(); + $user->name = UserPolicy::ADMIN; + $this->be($user); + + $return = ['name' => 'foo']; + $this->mockResolver(fn (): array => $return); + + $this->app + ->make(Gate::class) + ->define('customArray', fn (User $authorizedUser, array $root) => $authorizedUser === $user && $root == $return); + + $this->schema = $this->getSchema('ability: "customArray"'); + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } + + public function testGlobalGate(): void + { + $user = new User(); + $this->be($user); + + $this->app->make(Gate::class)->define('globalAdmin', fn ($authorizedUser) => $authorizedUser === $user); + + $this->mockResolver(fn (): User => $this->resolveUser()); + + $this->schema = /** @lang GraphQL */ ' + type Query { + user: User! + @canRoot(ability: "globalAdmin") + @mock + } + + type User { + name(foo: String): String + } + '; + + $this->query()->assertJson([ + 'data' => [ + 'user' => [ + 'name' => 'foo', + ], + ], + ]); + } +} diff --git a/tests/Utils/Policies/UserPolicy.php b/tests/Utils/Policies/UserPolicy.php index dd917204f2..cba98188b9 100644 --- a/tests/Utils/Policies/UserPolicy.php +++ b/tests/Utils/Policies/UserPolicy.php @@ -47,18 +47,28 @@ public function dependingOnArg(User $viewer, bool $pass): bool return $pass; } - /** @param array $injectedArgs */ - public function injectArgs(User $viewer, array $injectedArgs): bool + /** @param User|array ...$args */ + public function injectArgs(User $viewer, ...$args): bool { + $injectedArgs = $args[0]; + if ($injectedArgs instanceof User) { + $injectedArgs = $args[1]; + } + return $injectedArgs === ['foo' => 'bar']; } - /** - * @param array $injectedArgs - * @param array $staticArgs - */ - public function argsWithInjectedArgs(User $viewer, array $injectedArgs, array $staticArgs): bool + /** @param User|array ...$args */ + public function argsWithInjectedArgs(User $viewer, ...$args): bool { + $injectedArgs = $args[0]; + $staticArgs = $args[1]; + + if ($injectedArgs instanceof User) { + $injectedArgs = $args[1]; + $staticArgs = $args[2]; + } + return $injectedArgs === ['foo' => 'dynamic'] && $staticArgs === ['foo' => 'static']; } From 60b32017ad271f2cdaf192d9073893dedc5becfd Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 11 Dec 2023 14:00:05 +0100 Subject: [PATCH 12/14] v6.27.0 --- CHANGELOG.md | 2 + docs/6/api-reference/directives.md | 172 +++++++++++------- docs/6/eloquent/nested-mutations.md | 2 +- docs/6/security/authentication.md | 2 +- docs/6/security/authorization.md | 104 +++++++++-- docs/master/eloquent/nested-mutations.md | 2 +- docs/master/security/authentication.md | 2 +- docs/master/security/authorization.md | 10 +- src/Auth/CanDirective.php | 1 + tests/Integration/Auth/CanDirectiveDBTest.php | 1 + tests/Unit/Auth/CanDirectiveTest.php | 1 + 11 files changed, 210 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60e97f1308..2ae4c95e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +## v6.27.0 + ### Added - Split up `@can` directive into `@canFind`, `@canModel`, `@canQuery`, `@canResolved` and `@canRoot` https://github.com/nuwave/lighthouse/pull/2483 diff --git a/docs/6/api-reference/directives.md b/docs/6/api-reference/directives.md index dfeddf97e0..7c47fa4577 100644 --- a/docs/6/api-reference/directives.md +++ b/docs/6/api-reference/directives.md @@ -604,68 +604,86 @@ You can find usage examples of this directive in [the caching docs](../performan ## @can +Deprecated. Use the [@can* family of directives](#can-family-of-directives) instead. + +## @can* family of directives + +All @can* directives have common arguments. These arguments specify how gates are checked and what to do if the user is not authorized. +Each directive has its own set of arguments that specify what to check against. + ```graphql + """ +The ability to check permissions for. """ -Check a Laravel Policy to ensure the current user is authorized to access a field. +ability: String! -When `injectArgs` and `args` are used together, the client given -arguments will be passed before the static args. """ -directive @can( - """ - The ability to check permissions for. - """ - ability: String! +Pass along the client given input data as arguments to `Gate::check`. +""" +injectArgs: Boolean! = false - """ - Check the policy against the model instances returned by the field resolver. - Only use this if the field does not mutate data, it is run before checking. +""" +Statically defined arguments that are passed to `Gate::check`. - Mutually exclusive with `query`, `find`, and `root`. - """ - resolved: Boolean! = false +You may pass arbitrary GraphQL literals, +e.g.: [1, 2, 3] or { foo: "bar" } +""" +args: CanArgs - """ - Specify the class name of the model to use. - This is only needed when the default model detection does not work. - """ - model: String +""" +Action to do if the user is not authorized. +""" +action: CanAction! = EXCEPTION_PASS - """ - Pass along the client given input data as arguments to `Gate::check`. - """ - injectArgs: Boolean! = false +""" +Value to return if the user is not authorized and `action` is `RETURN_VALUE`. +""" +returnValue: CanArgs +""" +``` - """ - Statically defined arguments that are passed to `Gate::check`. +Types are specified as: +```graphql +""" +Any constant literal value: https://graphql.github.io/graphql-spec/draft/#sec-Input-Values +""" +scalar CanArgs - You may pass arbitrary GraphQL literals, - e.g.: [1, 2, 3] or { foo: "bar" } - """ - args: CanArgs +enum CanAction { + """ + Pass exception to the client. + """ + EXCEPTION_PASS - """ - Query for specific model instances to check the policy against, using arguments - with directives that add constraints to the query builder, such as `@eq`. + """ + Throw generic "not authorized" exception to conceal the real error. + """ + EXCEPTION_NOT_AUTHORIZED - Mutually exclusive with `resolved`, `find`, and `root`. - """ - query: Boolean! = false + """ + Return the value specified in `returnValue` argument to conceal the real error. + """ + RETURN_VALUE +} +``` - """ - Apply scopes to the underlying query. - """ - scopes: [String!] +You can find usage examples of these directives in [the authorization docs](../security/authorization.md#restrict-fields-through-policies). + +### @canFind +```graphql +""" +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Query for specific model instances to check the policy against, using primary key(s) from specified argument. +""" +directive @canFind( """ - If your policy checks against specific model instances, specify - the name of the field argument that contains its primary key(s). + Specify the name of the field argument that contains its primary key(s). You may pass the string in dot notation to use nested inputs. - - Mutually exclusive with `resolved`, `query`, and `root`. """ - find: String + find: String! """ Should the query fail when the models of `find` were not found? @@ -673,40 +691,68 @@ directive @can( findOrFail: Boolean! = true """ - If your policy should check against the root value. - - Mutually exclusive with `resolved`, `query`, and `find`. + Apply scopes to the underlying query. """ - root: Boolean! = false + scopes: [String!] ) repeatable on FIELD_DEFINITION +``` +### canModel + +```graphql """ -Any constant literal value: https://graphql.github.io/graphql-spec/draft/#sec-Input-Values +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Check the policy against the root model. """ -scalar CanArgs +directive @canRoot( + """ + The model name to check against. + """ + model: String + +) repeatable on FIELD_DEFINITION ``` -The name of the returned Type `Post` is used as the Model class, however you may overwrite this by -passing the `model` argument: +### @canQuery ```graphql -type Mutation { - createBlogPost(input: PostInput!): BlogPost - @can(ability: "create", model: "App\\Post") -} +""" +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Query for specific model instances to check the policy against, using arguments +with directives that add constraints to the query builder, such as `@eq`. +""" +directive @canQuery( + """ + Apply scopes to the underlying query. + """ + scopes: [String!] +) repeatable on FIELD_DEFINITION ``` -Check the policy against the resolved model instances with the `resolved` argument: +### @canResolved ```graphql -type Query { - fetchUserByEmail(email: String! @eq): User - @can(ability: "view", resolved: true) - @find -} +""" +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Check the policy against the model instances returned by the field resolver. +Only use this if the field does not mutate data, it is run before checking. +""" +directive @canResolved repeatable on FIELD_DEFINITION ``` -You can find usage examples of this directive in [the authorization docs](../security/authorization.md#restrict-fields-through-policies). +### @canRoot + +```graphql +""" +Check a Laravel Policy to ensure the current user is authorized to access a field. + +Check the policy against the root object. +""" +directive @canRoot repeatable on FIELD_DEFINITION +``` ## @clearCache diff --git a/docs/6/eloquent/nested-mutations.md b/docs/6/eloquent/nested-mutations.md index 635d5ac3d2..318c6eab8b 100644 --- a/docs/6/eloquent/nested-mutations.md +++ b/docs/6/eloquent/nested-mutations.md @@ -48,7 +48,7 @@ See [this issue](https://github.com/nuwave/lighthouse/issues/900) for further di ## Security considerations Lighthouse has no mechanism for fine-grained permissions of nested mutation operations. -Field directives such as [@can](../api-reference/directives.md#can) apply to the whole field. +Field directives such as the [@can* family of directives](../api-reference/directives.md#can-family-of-directives) apply to the whole field. Make sure that fields with nested mutations are only available to users who are allowed to execute all reachable nested mutations. diff --git a/docs/6/security/authentication.md b/docs/6/security/authentication.md index e30e800e25..9ecb4b454c 100644 --- a/docs/6/security/authentication.md +++ b/docs/6/security/authentication.md @@ -97,7 +97,7 @@ and thus executes before them. extend type Query { user: User! @guard - @can(ability: "adminOnly") + @canModel(ability: "adminOnly") ... } ``` diff --git a/docs/6/security/authorization.md b/docs/6/security/authorization.md index 2f6d583f93..2fe6387e46 100644 --- a/docs/6/security/authorization.md +++ b/docs/6/security/authorization.md @@ -57,7 +57,7 @@ limited to seeing just those. ## Restrict fields through policies Lighthouse allows you to restrict field operations to a certain group of users. -Use the [@can](../api-reference/directives.md#can) directive +Use the [@can* family of directives](../api-reference/directives.md#can-family-of-directives) to leverage [Laravel Policies](https://laravel.com/docs/authorization) for authorization. Starting from Laravel 5.7, [authorization of guest users](https://laravel.com/docs/authorization#guest-users) is supported. @@ -66,11 +66,11 @@ Because of this, Lighthouse does **not** validate that the user is authenticated ### Protect mutations As an example, you might want to allow only admin users of your application to create posts. -Start out by defining [@can](../api-reference/directives.md#can) upon a mutation you want to protect: +Start out by defining [@canModel](../api-reference/directives.md#canmodel) upon a mutation you want to protect: ```graphql type Mutation { - createPost(input: PostInput): Post @can(ability: "create") + createPost(input: PostInput): Post @canModel(ability: "create") } ``` @@ -86,18 +86,44 @@ final class PostPolicy } ``` -### Protect specific model instances +### Protect mutations using database queries -For some models, you may want to restrict access for specific instances of a model. -Set the `resolved` argument to `true` to have Lighthouse check permissions against -the resolved model instances. +You can also protect specific models by using the [@canFind](../api-reference/directives.md#canfind) +or [@canQuery](../api-reference/directives.md#canquery) directive. +They will query the database and check the specified policy against the result. + +```graphql +type Mutation { + updatePost(input: UpdatePostInput! @spread): Post! @canFind(ability: "edit", find: "input.id") @update +} + +input PostInput { + id: ID! + title: String +} +``` + +```php +final class PostPolicy +{ + public function edit(User $user, Post $post): bool + { + return $user->id === $post->author_id; + } +} +``` + +### Protect resolved model instances + +For some models, you may want to restrict access for already resolved instance of a model. +Use the [@canResolved](../api-reference/directives.md#canresolved) directive to do so. > This will actually run the field before checking permissions, do not use in mutations. ```graphql type Query { post(id: ID! @whereKey): Post - @can(ability: "view", resolved: true) + @canResolved(ability: "view") @find @softDeletes } @@ -113,14 +139,42 @@ final class PostPolicy } ``` +### Protect fields + +You can protect fields with the [@canRoot](../api-reference/directives.md#canroot) directive. +It checks against the resolved root object. + +This example shows how to restrict reading the `email` field to only the user itself: + +```graphql +type Query { + user(id: ID! @whereKey): User @find +} + +type User { + email: String! @canRoot(ability: "viewEmail") +} +``` + +```php +final class UserPolicy +{ + public function viewEmail(User $actor, User $target): bool + { + return $actor->id === $target->author_id; + } +} +``` + ### Passing additional arguments You can pass additional arguments to the policy checks by specifying them as `args`: ```graphql type Mutation { - createPost(input: PostInput): Post - @can(ability: "create", args: ["FROM_GRAPHQL"]) + createPost(input: CreatePostInput! @spread): Post! + @create + @canModel(ability: "create", args: ["FROM_GRAPHQL"]) } ``` @@ -139,7 +193,7 @@ with the `injectArgs` argument: ```graphql type Mutation { - createPost(title: String!): Post @can(ability: "create", injectArgs: true) + createPost(title: String!): Post @canModel(ability: "create", injectArgs: true) @create } ``` @@ -163,6 +217,26 @@ final class PostPolicy } ``` +### Concealing the existence of a model or other errors + +When a user is not authorized to access a model, you may want to hide the existence of the model. +This can be done by setting action to either EXCEPTION_NOT_AUTHORIZED or RETURN_VALUE. + +In the first case it would always return the generic "not authorized" exception. +In the second case it would return value which you can specify in the `returnValue` argument. + +```graphql +type Query { + user(id: ID! @whereKey): User @find +} + +type User { + banned: Boolean! @canRoot(ability: "admin", action: RETURN_VALUE, returnValue: false) +} +``` + +The `banned` field would return false for all users who are not authorized to access it. + ## Custom field restrictions For applications with role management, it is common to hide some model attributes from a @@ -208,11 +282,9 @@ GRAPHQL; public function handleField(FieldValue $fieldValue): void { $fieldValue->wrapResolver(fn (callable $resolver) => function (mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver) { - $requiredRole = $this->directiveArgValue('requiredRole'); - // Throw in case of an invalid schema definition to remind the developer - if ($requiredRole === null) { - throw new DefinitionException("Missing argument 'requiredRole' for directive '@canAccess'."); - } + $requiredRole = $this->directiveArgValue('requiredRole') + // Throw in case of an invalid schema definition to remind the developer + ?? throw new DefinitionException("Missing argument 'requiredRole' for directive '@canAccess'."); $user = $context->user(); if ( diff --git a/docs/master/eloquent/nested-mutations.md b/docs/master/eloquent/nested-mutations.md index 4d376c9260..318c6eab8b 100644 --- a/docs/master/eloquent/nested-mutations.md +++ b/docs/master/eloquent/nested-mutations.md @@ -48,7 +48,7 @@ See [this issue](https://github.com/nuwave/lighthouse/issues/900) for further di ## Security considerations Lighthouse has no mechanism for fine-grained permissions of nested mutation operations. -Field directives such as [@can*](../api-reference/directives.md#can-family-of-directives) apply to the whole field. +Field directives such as the [@can* family of directives](../api-reference/directives.md#can-family-of-directives) apply to the whole field. Make sure that fields with nested mutations are only available to users who are allowed to execute all reachable nested mutations. diff --git a/docs/master/security/authentication.md b/docs/master/security/authentication.md index e30e800e25..9ecb4b454c 100644 --- a/docs/master/security/authentication.md +++ b/docs/master/security/authentication.md @@ -97,7 +97,7 @@ and thus executes before them. extend type Query { user: User! @guard - @can(ability: "adminOnly") + @canModel(ability: "adminOnly") ... } ``` diff --git a/docs/master/security/authorization.md b/docs/master/security/authorization.md index b99b3b3f6f..2fe6387e46 100644 --- a/docs/master/security/authorization.md +++ b/docs/master/security/authorization.md @@ -57,7 +57,7 @@ limited to seeing just those. ## Restrict fields through policies Lighthouse allows you to restrict field operations to a certain group of users. -Use the [@can*](../api-reference/directives.md#can-family-of-directives) family of directives +Use the [@can* family of directives](../api-reference/directives.md#can-family-of-directives) to leverage [Laravel Policies](https://laravel.com/docs/authorization) for authorization. Starting from Laravel 5.7, [authorization of guest users](https://laravel.com/docs/authorization#guest-users) is supported. @@ -282,11 +282,9 @@ GRAPHQL; public function handleField(FieldValue $fieldValue): void { $fieldValue->wrapResolver(fn (callable $resolver) => function (mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver) { - $requiredRole = $this->directiveArgValue('requiredRole'); - // Throw in case of an invalid schema definition to remind the developer - if ($requiredRole === null) { - throw new DefinitionException("Missing argument 'requiredRole' for directive '@canAccess'."); - } + $requiredRole = $this->directiveArgValue('requiredRole') + // Throw in case of an invalid schema definition to remind the developer + ?? throw new DefinitionException("Missing argument 'requiredRole' for directive '@canAccess'."); $user = $context->user(); if ( diff --git a/src/Auth/CanDirective.php b/src/Auth/CanDirective.php index a80a3dfce0..4e6572c885 100644 --- a/src/Auth/CanDirective.php +++ b/src/Auth/CanDirective.php @@ -28,6 +28,7 @@ use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; use Nuwave\Lighthouse\Support\Utils; +/** @deprecated TODO remove with v7 */ class CanDirective extends BaseDirective implements FieldMiddleware, FieldManipulator { public function __construct( diff --git a/tests/Integration/Auth/CanDirectiveDBTest.php b/tests/Integration/Auth/CanDirectiveDBTest.php index 42f7ad0cd5..2d867c45cc 100644 --- a/tests/Integration/Auth/CanDirectiveDBTest.php +++ b/tests/Integration/Auth/CanDirectiveDBTest.php @@ -11,6 +11,7 @@ use Tests\Utils\Models\User; use Tests\Utils\Policies\UserPolicy; +/** TODO remove with v7 */ final class CanDirectiveDBTest extends DBTestCase { public function testQueriesForSpecificModel(): void diff --git a/tests/Unit/Auth/CanDirectiveTest.php b/tests/Unit/Auth/CanDirectiveTest.php index 426223bb5b..34408812b1 100644 --- a/tests/Unit/Auth/CanDirectiveTest.php +++ b/tests/Unit/Auth/CanDirectiveTest.php @@ -8,6 +8,7 @@ use Tests\Utils\Models\User; use Tests\Utils\Policies\UserPolicy; +/** TODO remove with v7 */ final class CanDirectiveTest extends TestCase { public function testThrowsIfNotAuthorized(): void From 3a9a7f6e9434d5e41e75fde8faa55964c9ddf551 Mon Sep 17 00:00:00 2001 From: Fabio Capucci Date: Mon, 11 Dec 2023 14:01:46 +0100 Subject: [PATCH 13/14] Federation v2 `@shareable` types (#2485) --- src/Federation/FederationHelper.php | 16 ++++++ src/Pagination/PaginationManipulator.php | 42 ++++++++++------ .../Federation/FederationSchemaTest.php | 49 +++++++++++++++++++ 3 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/Federation/FederationHelper.php b/src/Federation/FederationHelper.php index 5bf252b500..8ca1ad149a 100644 --- a/src/Federation/FederationHelper.php +++ b/src/Federation/FederationHelper.php @@ -5,6 +5,7 @@ use GraphQL\Language\AST\DirectiveNode; use GraphQL\Type\Schema; use Nuwave\Lighthouse\Schema\AST\ASTHelper; +use Nuwave\Lighthouse\Schema\AST\DocumentAST; class FederationHelper { @@ -43,4 +44,19 @@ public static function schemaExtensionDirectives(Schema $schema): array return $schemaDirectives; } + + public static function isUsingFederationV2(DocumentAST $documentAST): bool + { + foreach ($documentAST->schemaExtensions as $extension) { + foreach (ASTHelper::directiveDefinitions($extension, 'link') as $directive) { + $url = ASTHelper::directiveArgValue($directive, 'url'); + + if (str_starts_with($url, 'https://specs.apollo.dev/federation/v2')) { + return true; + } + } + } + + return false; + } } diff --git a/src/Pagination/PaginationManipulator.php b/src/Pagination/PaginationManipulator.php index a8ecf300c2..32c6541b6f 100644 --- a/src/Pagination/PaginationManipulator.php +++ b/src/Pagination/PaginationManipulator.php @@ -9,6 +9,8 @@ use GraphQL\Language\Parser; use Nuwave\Lighthouse\CacheControl\CacheControlServiceProvider; use Nuwave\Lighthouse\Exceptions\DefinitionException; +use Nuwave\Lighthouse\Federation\FederationHelper; +use Nuwave\Lighthouse\Federation\FederationServiceProvider; use Nuwave\Lighthouse\Schema\AST\ASTHelper; use Nuwave\Lighthouse\Schema\AST\DocumentAST; use Nuwave\Lighthouse\Schema\Directives\ModelDirective; @@ -73,7 +75,7 @@ protected function registerConnection( int $maxCount = null, ObjectTypeDefinitionNode $edgeType = null, ): void { - $pageInfoNode = self::pageInfo(); + $pageInfoNode = $this->pageInfo(); if (! isset($this->documentAST->types[$pageInfoNode->getName()->value])) { $this->documentAST->setTypeDefinition($pageInfoNode); } @@ -164,7 +166,7 @@ protected function registerPaginator( int $defaultCount = null, int $maxCount = null, ): void { - $paginatorInfoNode = self::paginatorInfo(); + $paginatorInfoNode = $this->paginatorInfo(); if (! isset($this->documentAST->types[$paginatorInfoNode->getName()->value])) { $this->documentAST->setTypeDefinition($paginatorInfoNode); } @@ -204,7 +206,7 @@ protected function registerSimplePaginator( int $defaultCount = null, int $maxCount = null, ): void { - $simplePaginatorInfoNode = self::simplePaginatorInfo(); + $simplePaginatorInfoNode = $this->simplePaginatorInfo(); if (! isset($this->documentAST->types[$simplePaginatorInfoNode->getName()->value])) { $this->documentAST->setTypeDefinition($simplePaginatorInfoNode); } @@ -266,11 +268,11 @@ protected function paginationResultType(string $typeName): NonNullTypeNode return $typeNode; } - protected static function paginatorInfo(): ObjectTypeDefinitionNode + protected function paginatorInfo(): ObjectTypeDefinitionNode { - return Parser::objectTypeDefinition(/** @lang GraphQL */ ' + return Parser::objectTypeDefinition(/** @lang GraphQL */ <<maybeAddShareableDirective()} { "Number of items in the current page." count: Int! @@ -295,14 +297,14 @@ protected static function paginatorInfo(): ObjectTypeDefinitionNode "Number of total available items." total: Int! } - '); + GRAPHQL); } - protected static function simplePaginatorInfo(): ObjectTypeDefinitionNode + protected function simplePaginatorInfo(): ObjectTypeDefinitionNode { - return Parser::objectTypeDefinition(/** @lang GraphQL */ ' + return Parser::objectTypeDefinition(/** @lang GraphQL */ <<maybeAddShareableDirective()} { "Number of items in the current page." count: Int! @@ -321,14 +323,14 @@ protected static function simplePaginatorInfo(): ObjectTypeDefinitionNode "Are there more pages after this one?" hasMorePages: Boolean! } - '); + GRAPHQL); } - protected static function pageInfo(): ObjectTypeDefinitionNode + protected function pageInfo(): ObjectTypeDefinitionNode { - return Parser::objectTypeDefinition(/** @lang GraphQL */ ' + return Parser::objectTypeDefinition(/** @lang GraphQL */ <<maybeAddShareableDirective()} { "When paginating forwards, are there more items?" hasNextPage: Boolean! @@ -353,7 +355,7 @@ protected static function pageInfo(): ObjectTypeDefinitionNode "Index of the last available page." lastPage: Int! } - '); + GRAPHQL); } /** @@ -372,4 +374,14 @@ private function maybeInheritCacheControlDirective(): string return ''; } + + /** If federation v2 is used, add the @shareable directive to the pagination generic types. */ + private function maybeAddShareableDirective(): string + { + if (app()->providerIsLoaded(FederationServiceProvider::class) && FederationHelper::isUsingFederationV2($this->documentAST)) { + return /** @lang GraphQL */ '@shareable'; + } + + return ''; + } } diff --git a/tests/Integration/Federation/FederationSchemaTest.php b/tests/Integration/Federation/FederationSchemaTest.php index de0462caa4..4ef0818216 100644 --- a/tests/Integration/Federation/FederationSchemaTest.php +++ b/tests/Integration/Federation/FederationSchemaTest.php @@ -8,6 +8,10 @@ final class FederationSchemaTest extends TestCase { + private const FEDERATION_V2_SCHEMA_EXTENSION = /** @lang GraphQL */ <<<'GRAPHQL' +extend schema @link(url: "https:\/\/specs.apollo.dev\/federation\/v2.3", import: ["@composeDirective", "@extends", "@external", "@inaccessible", "@interfaceObject", "@key", "@override", "@provides", "@requires", "@shareable", "@tag"]) +GRAPHQL; + protected function getPackageProviders($app): array { return array_merge( @@ -127,6 +131,51 @@ public function testServiceQueryShouldReturnFederationV2ComposedDirectives(): vo $this->assertStringContainsString($typeFoo, $sdl); } + public function testPaginationTypesAreNotMarkedAsSharableWhenUsingFederationV1(): void + { + $this->schema = /** @lang GraphQL */ <<_serviceSdl(); + + $this->assertStringContainsString('type PaginatorInfo {', $sdl); + $this->assertStringContainsString('type PageInfo {', $sdl); + $this->assertStringContainsString('type SimplePaginatorInfo {', $sdl); + $this->assertStringNotContainsString('@shareable', $sdl); + } + + public function testPaginationTypesAreMarkedAsSharableWhenUsingFederationV2(): void + { + $schema = /** @lang GraphQL */ <<schema = self::FEDERATION_V2_SCHEMA_EXTENSION . $schema; + + $sdl = $this->_serviceSdl(); + + $this->assertStringContainsString('type PaginatorInfo @shareable {', $sdl); + $this->assertStringContainsString('type PageInfo @shareable {', $sdl); + $this->assertStringContainsString('type SimplePaginatorInfo @shareable {', $sdl); + } + protected function _serviceSdl(): string { $response = $this->graphQL(/** @lang GraphQL */ ' From 76018af1019d97739a71eb47c9008f3e69273fbc Mon Sep 17 00:00:00 2001 From: Benedikt Franke Date: Mon, 11 Dec 2023 14:03:36 +0100 Subject: [PATCH 14/14] v6.28.0 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ae4c95e0f..3921f8c849 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +## v6.28.0 + +### Added + +- Mark common pagination types as `@shareable` for Apollo Federation v2 https://github.com/nuwave/lighthouse/pull/2485 + ## v6.27.0 ### Added