From 9ec80789a8e4926c04dc77d5f5b85347d5934c76 Mon Sep 17 00:00:00 2001 From: Kamil Kisiela Date: Wed, 26 Jun 2024 11:11:47 +0200 Subject: [PATCH] fix: detect incorrect subtypes of interface fields across subgraphs (#64) - [x] cover a case when interface field expects an interface (or union) and the field of an object type implementing the interface resolves a member. --- .changeset/witty-ghosts-applaud.md | 5 + .gitignore | 2 +- CHANGELOG.md | 24 ++- __tests__/ast.spec.ts | 6 +- __tests__/composition.spec.ts | 7 +- __tests__/fixtures/dgs/aside397.graphql | 34 ++- __tests__/fixtures/dgs/cower3fc.graphql | 181 +++++++++++++--- __tests__/fixtures/dgs/fooey584.graphql | 34 ++- __tests__/fixtures/dgs/houndd92.graphql | 38 +++- __tests__/fixtures/dgs/light7e6.graphql | 46 +++- __tests__/fixtures/dgs/roughb27.graphql | 2 +- __tests__/fixtures/dgs/truly859.graphql | 87 ++++++-- __tests__/fixtures/dgs/yahoob90.graphql | 8 +- __tests__/fixtures/dgs/yowza9a2.graphql | 85 +++++++- .../fixtures/huge-schema/fooeya02.graphql | 2 +- .../subgraph/errors/INVALID_GRAPHQL.spec.ts | 33 +++ __tests__/supergraph-composition.spec.ts | 176 ++++++++-------- .../supergraph/errors/INVALID-GRAPHQL.spec.ts | 93 +++++++++ src/graphql/contains-supergraph-spec.ts | 33 +-- src/subgraph/validation/validate-state.ts | 2 +- src/subgraph/validation/validation-context.ts | 4 +- src/supergraph/composition/directive.ts | 7 +- .../composition/input-object-type.ts | 15 +- src/supergraph/composition/interface-type.ts | 9 +- src/supergraph/composition/object-type.ts | 9 +- src/supergraph/state.ts | 1 - .../rules/interface-subtype-rule.ts | 197 ++++++++++++++++++ .../validation/rules/satisfiablity/walker.ts | 4 +- .../validation/validate-supergraph.ts | 2 + 29 files changed, 929 insertions(+), 217 deletions(-) create mode 100644 .changeset/witty-ghosts-applaud.md create mode 100644 __tests__/supergraph/errors/INVALID-GRAPHQL.spec.ts create mode 100644 src/supergraph/validation/rules/interface-subtype-rule.ts diff --git a/.changeset/witty-ghosts-applaud.md b/.changeset/witty-ghosts-applaud.md new file mode 100644 index 0000000..8c87c82 --- /dev/null +++ b/.changeset/witty-ghosts-applaud.md @@ -0,0 +1,5 @@ +--- +'@theguild/federation-composition': patch +--- + +fix: detect incorrect subtypes of interface fields across subgraphs diff --git a/.gitignore b/.gitignore index 5b6fb48..8e632b0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ node_modules .DS_Store dist *.cpuprofile -.bob \ No newline at end of file +.bob diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f2c874..ec90676 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,29 +4,43 @@ ### Patch Changes -- [#62](https://github.com/the-guild-org/federation/pull/62) [`e50bc90`](https://github.com/the-guild-org/federation/commit/e50bc90d4dc65769dbe44fa01994148d968755dc) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix: do not expose `federation__Scope` and `federation__Policy` scalar definitions to a supergraph +- [#62](https://github.com/the-guild-org/federation/pull/62) + [`e50bc90`](https://github.com/the-guild-org/federation/commit/e50bc90d4dc65769dbe44fa01994148d968755dc) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Fix: do not expose `federation__Scope` + and `federation__Policy` scalar definitions to a supergraph ## 0.11.2 ### Patch Changes -- [#60](https://github.com/the-guild-org/federation/pull/60) [`2f7fef1`](https://github.com/the-guild-org/federation/commit/2f7fef10409a25f8366182448a48e72d5451abf9) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Normalize enum values to be printed as enum values in Supergraph SDL, even if the user's subgraph schema has them as strings +- [#60](https://github.com/the-guild-org/federation/pull/60) + [`2f7fef1`](https://github.com/the-guild-org/federation/commit/2f7fef10409a25f8366182448a48e72d5451abf9) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Normalize enum values to be printed as + enum values in Supergraph SDL, even if the user's subgraph schema has them as strings ## 0.11.1 ### Patch Changes -- [#58](https://github.com/the-guild-org/federation/pull/58) [`ab707b9`](https://github.com/the-guild-org/federation/commit/ab707b9517c141377ab10d46ec6ce2efa1401450) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Support directives on Input Object types +- [#58](https://github.com/the-guild-org/federation/pull/58) + [`ab707b9`](https://github.com/the-guild-org/federation/commit/ab707b9517c141377ab10d46ec6ce2efa1401450) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Support directives on Input Object + types ## 0.11.0 ### Minor Changes -- [#52](https://github.com/the-guild-org/federation/pull/52) [`589effd`](https://github.com/the-guild-org/federation/commit/589effd5b82286704db2a4678bf47ffe33e01c0d) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Support @interfaceObject directive +- [#52](https://github.com/the-guild-org/federation/pull/52) + [`589effd`](https://github.com/the-guild-org/federation/commit/589effd5b82286704db2a4678bf47ffe33e01c0d) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Support @interfaceObject directive ### Patch Changes -- [#52](https://github.com/the-guild-org/federation/pull/52) [`589effd`](https://github.com/the-guild-org/federation/commit/589effd5b82286704db2a4678bf47ffe33e01c0d) Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Improve INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE +- [#52](https://github.com/the-guild-org/federation/pull/52) + [`589effd`](https://github.com/the-guild-org/federation/commit/589effd5b82286704db2a4678bf47ffe33e01c0d) + Thanks [@kamilkisiela](https://github.com/kamilkisiela)! - Improve + INTERFACE_KEY_MISSING_IMPLEMENTATION_TYPE ## 0.10.1 diff --git a/__tests__/ast.spec.ts b/__tests__/ast.spec.ts index c081d3c..55e94b2 100644 --- a/__tests__/ast.spec.ts +++ b/__tests__/ast.spec.ts @@ -1,5 +1,6 @@ -import { ConstDirectiveNode, Kind, TypeKind, parse } from 'graphql'; +import { ConstDirectiveNode, Kind, parse, TypeKind } from 'graphql'; import { describe, expect, test } from 'vitest'; +import { ArgumentKind } from '../src/subgraph/state.js'; import { createEnumTypeNode, createInputObjectTypeNode, @@ -11,7 +12,6 @@ import { createUnionTypeNode, stripFederation, } from '../src/supergraph/composition/ast.js'; -import { ArgumentKind } from '../src/subgraph/state.js'; function createDirective(name: string): ConstDirectiveNode { return { @@ -893,7 +893,7 @@ describe('input object type', () => { }), ).toEqualGraphQL(/* GraphQL */ ` input Filter { - obj: Obj = {limit: 1} + obj: Obj = { limit: 1 } limit: Int = 2 } `); diff --git a/__tests__/composition.spec.ts b/__tests__/composition.spec.ts index d84c4bf..4f65138 100644 --- a/__tests__/composition.spec.ts +++ b/__tests__/composition.spec.ts @@ -46,7 +46,6 @@ testImplementations(api => { assertCompositionSuccess(result); - expect(result.supergraphSdl).toContainGraphQL(/* GraphQL */ ` type Query @join__type(graph: A) { users(type: UserType! = Regular): [User!]! @@ -1245,7 +1244,7 @@ testImplementations(api => { @join__unionMember(graph: A, member: "Book") @join__unionMember(graph: B, member: "Movie") @join__unionMember(graph: B, member: "Book") = - Movie + | Movie | Book `); }); @@ -1303,7 +1302,7 @@ testImplementations(api => { @join__type(graph: B) @join__unionMember(graph: A, member: "Book") @join__unionMember(graph: B, member: "Movie") = - Book + | Book | Movie `); }); @@ -5426,7 +5425,7 @@ testImplementations(api => { @join__unionMember(graph: A, member: "Movie") @join__unionMember(graph: B, member: "Book") @join__unionMember(graph: C, member: "Song") = - Movie + | Movie | Book | Song `); diff --git a/__tests__/fixtures/dgs/aside397.graphql b/__tests__/fixtures/dgs/aside397.graphql index 929b269..f85b69f 100644 --- a/__tests__/fixtures/dgs/aside397.graphql +++ b/__tests__/fixtures/dgs/aside397.graphql @@ -1,4 +1,18 @@ -schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@provides", "@requires", "@extends", "@shareable", "@tag", "@inaccessible"]) { +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link( + url: "https://specs.apollo.dev/federation/v2.0" + import: [ + "@key" + "@external" + "@provides" + "@requires" + "@extends" + "@shareable" + "@tag" + "@inaccessible" + ] + ) { query: Query } @@ -6,9 +20,17 @@ directive @extends on INTERFACE | OBJECT directive @external(reason: String) on FIELD_DEFINITION | OBJECT -directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on INTERFACE | OBJECT +directive @key( + fields: federation__FieldSet! + resolvable: Boolean = true +) repeatable on INTERFACE | OBJECT -directive @link(as: String, for: link__Purpose, import: [link__Import], url: String) repeatable on SCHEMA +directive @link( + as: String + for: link__Purpose + import: [link__Import] + url: String +) repeatable on SCHEMA directive @provides(fields: federation__FieldSet!) on FIELD_DEFINITION @@ -16,7 +38,9 @@ directive @requires(fields: federation__FieldSet!) on FIELD_DEFINITION directive @shareable on FIELD_DEFINITION | OBJECT -directive @tag(name: String!) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION +directive @tag( + name: String! +) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION enum whosec2c { first056 @@ -56,4 +80,4 @@ scalar link__Import enum link__Purpose { EXECUTION SECURITY -} \ No newline at end of file +} diff --git a/__tests__/fixtures/dgs/cower3fc.graphql b/__tests__/fixtures/dgs/cower3fc.graphql index ee6aefd..d9830b7 100644 --- a/__tests__/fixtures/dgs/cower3fc.graphql +++ b/__tests__/fixtures/dgs/cower3fc.graphql @@ -1,20 +1,77 @@ type Query { - kendob21(sadly2d3: String!, madly3bc: abaftb74!, wetly4a7: String, slimy8ee: under62b): stark083 @deprecated(reason: "") - shyly083(minor556: String, after1a5: String, given5cd: [scrapa5c], slimy8ee: under62b): bossybc0 @deprecated(reason: "") - blackfe9(minor556: String, fooey0ff: String, brave147: String, abafta31: String, wetly4a7: String, slimy8ee: under62b, whilebdb: fixedfa3, hence04b: Int, latchfa9: Int, stake684: Int, aboutb64: String): whirr189 @deprecated(reason: "") - poundaa2(slimy8ee: under62b, wetly4a7: String!, yowzae3f: String!, exile934: triala1a, sprayd11: amongc75, aboutb64: String): yahoob6d @deprecated(reason: "") - yowzafc6(slimy8ee: under62b, wetly4a7: String!, yowzae3f: String!, exile934: triala1a, yummye2a: String): worth1a1 @deprecated(reason: "") - divvy8b4(minor556: String, every5f7: [String!], vividc7d: [String!], while964: [String!], trolle10: [String!]): smartff1 @deprecated(reason: "") + kendob21(sadly2d3: String!, madly3bc: abaftb74!, wetly4a7: String, slimy8ee: under62b): stark083 + @deprecated(reason: "") + shyly083(minor556: String, after1a5: String, given5cd: [scrapa5c], slimy8ee: under62b): bossybc0 + @deprecated(reason: "") + blackfe9( + minor556: String + fooey0ff: String + brave147: String + abafta31: String + wetly4a7: String + slimy8ee: under62b + whilebdb: fixedfa3 + hence04b: Int + latchfa9: Int + stake684: Int + aboutb64: String + ): whirr189 @deprecated(reason: "") + poundaa2( + slimy8ee: under62b + wetly4a7: String! + yowzae3f: String! + exile934: triala1a + sprayd11: amongc75 + aboutb64: String + ): yahoob6d @deprecated(reason: "") + yowzafc6( + slimy8ee: under62b + wetly4a7: String! + yowzae3f: String! + exile934: triala1a + yummye2a: String + ): worth1a1 @deprecated(reason: "") + divvy8b4( + minor556: String + every5f7: [String!] + vividc7d: [String!] + while964: [String!] + trolle10: [String!] + ): smartff1 @deprecated(reason: "") pushyddd(midst77d: [worth47f!]!): zowiecbd @deprecated(reason: "") until79c(lurch6e0: String, wetly4a7: String): above989 hedge338: yowza37f @deprecated(reason: "") - dailyb9f(minor556: String, embedccf: spearf97!, wetly4a7: String!, slimy8ee: under62b, sprayd11: amongc75, aboutb64: String, dimly444: drown56b): cacaod75 @deprecated(reason: "") - madly928(abhordc1: String!, yowza5e8: String!, wetly4a7: String!, slimy8ee: under62b): cacaod75 @deprecated(reason: "") - slope65b(yahooeeb: String!, zowie361: String!, wetly9b7: String!, fluid1f7: String!, aboutb64: String!): chafee47 @deprecated(reason: "") + dailyb9f( + minor556: String + embedccf: spearf97! + wetly4a7: String! + slimy8ee: under62b + sprayd11: amongc75 + aboutb64: String + dimly444: drown56b + ): cacaod75 @deprecated(reason: "") + madly928(abhordc1: String!, yowza5e8: String!, wetly4a7: String!, slimy8ee: under62b): cacaod75 + @deprecated(reason: "") + slope65b( + yahooeeb: String! + zowie361: String! + wetly9b7: String! + fluid1f7: String! + aboutb64: String! + ): chafee47 @deprecated(reason: "") unify58b(seize765: String!): creakb2c @deprecated(reason: "") never6a7: badly479 @deprecated(reason: "") - abaft7c8(minor556: String!, zowie361: String!, wetly4a7: String!): never1d1 @deprecated(reason: "") - oddly558(minor556: String, wetly4a7: String, aboutb64: String, hence04b: Int, latchfa9: Int, stake684: Int, slimy8ee: under62b): whirr189 @deprecated(reason: "") + abaft7c8(minor556: String!, zowie361: String!, wetly4a7: String!): never1d1 + @deprecated(reason: "") + oddly558( + minor556: String + wetly4a7: String + aboutb64: String + hence04b: Int + latchfa9: Int + stake684: Int + slimy8ee: under62b + ): whirr189 @deprecated(reason: "") } type mimica1e { @@ -159,7 +216,8 @@ type faint877 @key(fields: "until877 oftend70 about6e9") { about6e9: String } -type since8b6 @key(fields: "where46e underbbc { until877 oftend70 about6e9 } oddlye7e { where46e often286 }") { +type since8b6 + @key(fields: "where46e underbbc { until877 oftend70 about6e9 } oddlye7e { where46e often286 }") { where46e: String underbbc: [faint877!] oddlye7e: [smart2a4!] @@ -187,7 +245,11 @@ type slide8c0 { where46e: String } -type eager01f @key(fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }") @key(fields: "where46e") { +type eager01f + @key( + fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }" + ) + @key(fields: "where46e") { where46e: String chilib72: String fooeyc5f: Int @@ -250,7 +312,11 @@ type oftenc6d { where46e: String } -type these524 @key(fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }") @key(fields: "where46e") { +type these524 + @key( + fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }" + ) + @key(fields: "where46e") { where46e: String chilib72: String fooeyc5f: Int @@ -309,7 +375,11 @@ type until172 { where46e: String } -type which640 @key(fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }") @key(fields: "where46e") { +type which640 + @key( + fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }" + ) + @key(fields: "where46e") { where46e: String chilib72: String fooeyc5f: Int @@ -365,7 +435,11 @@ type draft352 { where46e: String } -type fooey2eb @key(fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }") @key(fields: "where46e") { +type fooey2eb + @key( + fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }" + ) + @key(fields: "where46e") { where46e: String chilib72: String fooeyc5f: Int @@ -449,7 +523,15 @@ type yahoob6d @key(fields: "madlybd2") { oddlya58: whosefe1 bunch447: where230 fooey5d8: [roundd0b!] - worrye40(creek166: String, yahoo609: String, fooeyc5f: Int, sally487: String, until877: Int, chief6ff: String, messy877: drown56b): cacaod75 + worrye40( + creek166: String + yahoo609: String + fooeyc5f: Int + sally487: String + until877: Int + chief6ff: String + messy877: drown56b + ): cacaod75 madlybd2: String } @@ -462,7 +544,9 @@ type where1ed { weaveff6: String } -type circa3d0 @key(fields: "zowief17 { where374 { often286 } sadlyb5f } trickcaf { seize765 }") @key(fields: "madlybd2") { +type circa3d0 + @key(fields: "zowief17 { where374 { often286 } sadlyb5f } trickcaf { seize765 }") + @key(fields: "madlybd2") { fullyff7: where1ed fooeyd09: wherefcd fooeyebc: where1ed @@ -498,7 +582,11 @@ type wreak80e { where46e: String } -type voter0ee @key(fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }") @key(fields: "where46e") { +type voter0ee + @key( + fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }" + ) + @key(fields: "where46e") { where46e: String chilib72: String fooeyc5f: Int @@ -573,7 +661,11 @@ type curly7d0 { where46e: String } -type minus92f @key(fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }") @key(fields: "where46e") { +type minus92f + @key( + fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }" + ) + @key(fields: "where46e") { where46e: String chilib72: String fooeyc5f: Int @@ -629,7 +721,11 @@ type yowza576 { where46e: String } -type reign19a @key(fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }") @key(fields: "where46e") { +type reign19a + @key( + fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }" + ) + @key(fields: "where46e") { where46e: String chilib72: String fooeyc5f: Int @@ -685,7 +781,11 @@ type abase102 { where46e: String } -type yahoo4f8 @key(fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }") @key(fields: "where46e") { +type yahoo4f8 + @key( + fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }" + ) + @key(fields: "where46e") { where46e: String chilib72: String fooeyc5f: Int @@ -1139,7 +1239,11 @@ type yahoof16 @key(fields: "sadlyb5f where374 { often286 }") { along6d8: [String] } -type worth1a1 @key(fields: "madlybd2") @key(fields: "slimy8ee { where46e underbbc { until877 oftend70 about6e9 } oddlye7e { where46e often286 } } wetly4a7 boxer271 plonk194 fooeyc5f until877 round05b gussy4f6 { where46e fully04a } wedge7f4 { where46e fully04a } windy599 { where46e fully04a } royal067 { never10e { badlyc3a kitty5fb } } fooey36b { zowief17 { where374 { often286 } sadlyb5f } trickcaf { seize765 } } where5b0 { where46e fully04a } truly811 { where46e fully04a } sadly185 { where46e fully04a } sadlybc2 { fully04a } never2c7 { rigid037 often7b6 badly305 stand735 } trainefc") { +type worth1a1 + @key(fields: "madlybd2") + @key( + fields: "slimy8ee { where46e underbbc { until877 oftend70 about6e9 } oddlye7e { where46e often286 } } wetly4a7 boxer271 plonk194 fooeyc5f until877 round05b gussy4f6 { where46e fully04a } wedge7f4 { where46e fully04a } windy599 { where46e fully04a } royal067 { never10e { badlyc3a kitty5fb } } fooey36b { zowief17 { where374 { often286 } sadlyb5f } trickcaf { seize765 } } where5b0 { where46e fully04a } truly811 { where46e fully04a } sadly185 { where46e fully04a } sadlybc2 { fully04a } never2c7 { rigid037 often7b6 badly305 stand735 } trainefc" + ) { slimy8ee: since8b6! wetly4a7: String! zowie2b2: String @@ -1171,7 +1275,15 @@ type worth1a1 @key(fields: "madlybd2") @key(fields: "slimy8ee { where46e underbb below011: sliceefd oddlya58: whosefe1 bunch447: where230 - worrye40(creek166: String, yahoo609: String, fooeyc5f: Int, sally487: String, until877: Int, chief6ff: String, messy877: drown56b): cacaod75 + worrye40( + creek166: String + yahoo609: String + fooeyc5f: Int + sally487: String + until877: Int + chief6ff: String + messy877: drown56b + ): cacaod75 madlybd2: String } @@ -1619,7 +1731,13 @@ type coverd95 { type yowza37f { clump720(creek166: String): [hedge33b!] click084(creek166: String!): [until82b!] - metal6ef(creek166: String!, yahoocc1: String!, until877: Int!, truly39b: Int!, untilb37: urbanc52): [fooey861!] + metal6ef( + creek166: String! + yahoocc1: String! + until877: Int! + truly39b: Int! + untilb37: urbanc52 + ): [fooey861!] } type until82b { @@ -1753,7 +1871,10 @@ type shade783 { zowie8ed: Boolean } -type stark083 @key(fields: "slimy8ee { where46e underbbc { until877 oftend70 about6e9 } oddlye7e { where46e often286 } } wetly4a7 sadly2d3 madly3bc royal067 { never10e { badlyc3a kitty5fb } }") { +type stark083 + @key( + fields: "slimy8ee { where46e underbbc { until877 oftend70 about6e9 } oddlye7e { where46e often286 } } wetly4a7 sadly2d3 madly3bc royal067 { never10e { badlyc3a kitty5fb } }" + ) { slimy8ee: since8b6! wetly4a7: String! aforee26: String! @@ -1878,7 +1999,11 @@ type belowc43 { where46e: String } -type daily61c @key(fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }") @key(fields: "where46e") { +type daily61c + @key( + fields: "where46e fully04a chilib72 fooeyc5f zowie2b2 until877 yahoo572 sport35d { where46e fully04a }" + ) + @key(fields: "where46e") { where46e: String chilib72: String fooeyc5f: Int @@ -1992,4 +2117,4 @@ type trialb22 { type coastb28 { where46e: String while7c7: Int -} \ No newline at end of file +} diff --git a/__tests__/fixtures/dgs/fooey584.graphql b/__tests__/fixtures/dgs/fooey584.graphql index be0f8fb..5cfe2eb 100644 --- a/__tests__/fixtures/dgs/fooey584.graphql +++ b/__tests__/fixtures/dgs/fooey584.graphql @@ -1,4 +1,18 @@ -schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@provides", "@requires", "@extends", "@shareable", "@tag", "@inaccessible"]) { +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link( + url: "https://specs.apollo.dev/federation/v2.0" + import: [ + "@key" + "@external" + "@provides" + "@requires" + "@extends" + "@shareable" + "@tag" + "@inaccessible" + ] + ) { query: Query } @@ -6,9 +20,17 @@ directive @extends on INTERFACE | OBJECT directive @external(reason: String) on FIELD_DEFINITION | OBJECT -directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on INTERFACE | OBJECT +directive @key( + fields: federation__FieldSet! + resolvable: Boolean = true +) repeatable on INTERFACE | OBJECT -directive @link(as: String, for: link__Purpose, import: [link__Import], url: String) repeatable on SCHEMA +directive @link( + as: String + for: link__Purpose + import: [link__Import] + url: String +) repeatable on SCHEMA directive @provides(fields: federation__FieldSet!) on FIELD_DEFINITION @@ -16,7 +38,9 @@ directive @requires(fields: federation__FieldSet!) on FIELD_DEFINITION directive @shareable on FIELD_DEFINITION | OBJECT -directive @tag(name: String!) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION +directive @tag( + name: String! +) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION enum whosec2c { first056 @@ -52,4 +76,4 @@ scalar link__Import enum link__Purpose { EXECUTION SECURITY -} \ No newline at end of file +} diff --git a/__tests__/fixtures/dgs/houndd92.graphql b/__tests__/fixtures/dgs/houndd92.graphql index f50f818..6911e19 100644 --- a/__tests__/fixtures/dgs/houndd92.graphql +++ b/__tests__/fixtures/dgs/houndd92.graphql @@ -1,4 +1,19 @@ -schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@provides", "@requires", "@extends", "@shareable", "@tag", "@inaccessible", "@override"]) { +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link( + url: "https://specs.apollo.dev/federation/v2.0" + import: [ + "@key" + "@external" + "@provides" + "@requires" + "@extends" + "@shareable" + "@tag" + "@inaccessible" + "@override" + ] + ) { query: Query } @@ -6,9 +21,17 @@ directive @extends on INTERFACE | OBJECT directive @external(reason: String) on FIELD_DEFINITION | OBJECT -directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on INTERFACE | OBJECT +directive @key( + fields: federation__FieldSet! + resolvable: Boolean = true +) repeatable on INTERFACE | OBJECT -directive @link(as: String, for: link__Purpose, import: [link__Import], url: String) repeatable on SCHEMA +directive @link( + as: String + for: link__Purpose + import: [link__Import] + url: String +) repeatable on SCHEMA directive @override(from: String!) on FIELD_DEFINITION @@ -18,7 +41,9 @@ directive @requires(fields: federation__FieldSet!) on FIELD_DEFINITION directive @shareable on FIELD_DEFINITION | OBJECT -directive @tag(name: String!) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION +directive @tag( + name: String! +) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION enum whosec2c { first056 @@ -40,7 +65,8 @@ type wharf9ca @key(fields: "adornea4") { type round32e @extends @key(fields: "adornea4") { bribe135: [given59d!] @external adornea4: smalld4d! @external - fooey36b(gussyd7d: zowie062): [given59d!] @requires(fields: "bribe135 { adornea4 wriste37 gussyd7d }") + fooey36b(gussyd7d: zowie062): [given59d!] + @requires(fields: "bribe135 { adornea4 wriste37 gussyd7d }") } type Query @@ -77,4 +103,4 @@ scalar link__Import enum link__Purpose { EXECUTION SECURITY -} \ No newline at end of file +} diff --git a/__tests__/fixtures/dgs/light7e6.graphql b/__tests__/fixtures/dgs/light7e6.graphql index 81f71cc..81f803f 100644 --- a/__tests__/fixtures/dgs/light7e6.graphql +++ b/__tests__/fixtures/dgs/light7e6.graphql @@ -1,4 +1,19 @@ -schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@provides", "@requires", "@extends", "@shareable", "@tag", "@inaccessible", "@override"]) { +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link( + url: "https://specs.apollo.dev/federation/v2.0" + import: [ + "@key" + "@external" + "@provides" + "@requires" + "@extends" + "@shareable" + "@tag" + "@inaccessible" + "@override" + ] + ) { query: Query mutation: Mutation } @@ -7,9 +22,17 @@ directive @extends on INTERFACE | OBJECT directive @external(reason: String) on FIELD_DEFINITION | OBJECT -directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on INTERFACE | OBJECT +directive @key( + fields: federation__FieldSet! + resolvable: Boolean = true +) repeatable on INTERFACE | OBJECT -directive @link(as: String, for: link__Purpose, import: [link__Import], url: String) repeatable on SCHEMA +directive @link( + as: String + for: link__Purpose + import: [link__Import] + url: String +) repeatable on SCHEMA directive @override(from: String!) on FIELD_DEFINITION @@ -19,7 +42,9 @@ directive @requires(fields: federation__FieldSet!) on FIELD_DEFINITION directive @shareable on FIELD_DEFINITION | OBJECT -directive @tag(name: String!) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION +directive @tag( + name: String! +) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION type barge0fe @key(fields: "where46e") { where46e: String @@ -334,7 +359,10 @@ type noisy3be { thongf57: untilffb } -type whose2eb @key(fields: "wriste37 shyly5db since9b7 { adornea4 whose18d } aside50a { adornea4 } thick5ed { adornea4 }") { +type whose2eb + @key( + fields: "wriste37 shyly5db since9b7 { adornea4 whose18d } aside50a { adornea4 } thick5ed { adornea4 }" + ) { aside50a: [plate86e!]! since9b7: [plate86e!]! glass900: plate86e @shareable @@ -498,7 +526,8 @@ type wetly948 @key(fields: "madlybd2") { yowza57c: String @external madlybd2: String fooeyfc9: plate86e - fooeya1c(young7fc: yowza062): givenc6d @requires(fields: "thick5ed { where46e trail84e } zowie555 tauntc02 recur12b yowza57c powerf43") + fooeya1c(young7fc: yowza062): givenc6d + @requires(fields: "thick5ed { where46e trail84e } zowie555 tauntc02 recur12b yowza57c powerf43") thick5ed: barge0fe! @external } @@ -507,7 +536,8 @@ type yowzab51 @extends @key(fields: "adornea4") { adornea4: smalld4d! @external supere4a: yowza033! @external yahoo45c: since841 @external - fooeya1c(young7fc: since055): givenc6d @requires(fields: "zowie51a yahoo45c vital7d7 wherefd4 supere4a { fooey29e trail84e }") + fooeya1c(young7fc: since055): givenc6d + @requires(fields: "zowie51a yahoo45c vital7d7 wherefd4 supere4a { fooey29e trail84e }") wherefd4: smalld4d! @external zowie51a: smalld4d! @external } @@ -590,4 +620,4 @@ scalar link__Import enum link__Purpose { EXECUTION SECURITY -} \ No newline at end of file +} diff --git a/__tests__/fixtures/dgs/roughb27.graphql b/__tests__/fixtures/dgs/roughb27.graphql index 0312a3d..9205a07 100644 --- a/__tests__/fixtures/dgs/roughb27.graphql +++ b/__tests__/fixtures/dgs/roughb27.graphql @@ -634,4 +634,4 @@ enum which983 { type madlyffa { kitty5fb: [after7f2] -} \ No newline at end of file +} diff --git a/__tests__/fixtures/dgs/truly859.graphql b/__tests__/fixtures/dgs/truly859.graphql index b5e151e..bd1fbfa 100644 --- a/__tests__/fixtures/dgs/truly859.graphql +++ b/__tests__/fixtures/dgs/truly859.graphql @@ -1,4 +1,19 @@ -schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@provides", "@requires", "@extends", "@shareable", "@tag", "@inaccessible", "@override"]) { +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link( + url: "https://specs.apollo.dev/federation/v2.0" + import: [ + "@key" + "@external" + "@provides" + "@requires" + "@extends" + "@shareable" + "@tag" + "@inaccessible" + "@override" + ] + ) { query: Query mutation: Mutation } @@ -7,9 +22,17 @@ directive @extends on INTERFACE | OBJECT directive @external(reason: String) on FIELD_DEFINITION | OBJECT -directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on INTERFACE | OBJECT +directive @key( + fields: federation__FieldSet! + resolvable: Boolean = true +) repeatable on INTERFACE | OBJECT -directive @link(as: String, for: link__Purpose, import: [link__Import], url: String) repeatable on SCHEMA +directive @link( + as: String + for: link__Purpose + import: [link__Import] + url: String +) repeatable on SCHEMA directive @override(from: String!) on FIELD_DEFINITION @@ -19,9 +42,14 @@ directive @requires(fields: federation__FieldSet!) on FIELD_DEFINITION directive @shareable on FIELD_DEFINITION | OBJECT -directive @tag(name: String!) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION +directive @tag( + name: String! +) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION -type untilba2 @key(fields: "metal6ef { fooey29e trail84e adornea4 } until877 oftend70 where5b0 truly811 sadly185 fooeyc5f") { +type untilba2 + @key( + fields: "metal6ef { fooey29e trail84e adornea4 } until877 oftend70 where5b0 truly811 sadly185 fooeyc5f" + ) { oftend70: String round159: [oftena26!]! given11b: [oftena26!]! @@ -88,7 +116,8 @@ type badly696 { type eager01f @key(fields: "where46e") { powerf43: String @external madlybd2: String @external - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): flaky271 @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): flaky271 + @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") prune878: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") truly8c7: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") waist526: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") @@ -101,7 +130,8 @@ type eager01f @key(fields: "where46e") { type barge0fe @key(fields: "where46e") { powerf43: String @external madlybd2: String @external - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 @requires(fields: "powerf43 madlybd2 trail84e") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 + @requires(fields: "powerf43 madlybd2 trail84e") where46e: String trail84e: String @external } @@ -110,7 +140,8 @@ type fooey861 @key(fields: "fooey29e") { powerf43: String @external madlybd2: String @external fooey29e: String! - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 @requires(fields: "powerf43 madlybd2 trail84e") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 + @requires(fields: "powerf43 madlybd2 trail84e") trail84e: String! @external } @@ -132,7 +163,10 @@ type madly346 { bunch75f: String } -type plate86e @key(fields: "adornea4") @key(fields: "adornea4 whose18d") @key(fields: "adornea4 creek166 ready859") { +type plate86e + @key(fields: "adornea4") + @key(fields: "adornea4 whose18d") + @key(fields: "adornea4 creek166 ready859") { badlyd13: [yowza245!]! yowzae8f: badly696 hinge935(yahoocc1: String!): diver2db @@ -227,7 +261,9 @@ type badlya1f @key(fields: "fooey29e trail84e adornea4") { type curvecc9 @key(fields: "adornea4 ready859 sincead5") { itchy8a4: [yowza033!]! @external adornea4: smalld4d - metal6ef(saint721: Boolean, trail84e: yowza5b1, cheep759: [yowza5b1!]): [yowza033!]! @override(from: "yowza9a2") @requires(fields: "itchy8a4 { adornea4 until4b4 fooey29e trail84e ready859 sincead5 midstc23 }") + metal6ef(saint721: Boolean, trail84e: yowza5b1, cheep759: [yowza5b1!]): [yowza033!]! + @override(from: "yowza9a2") + @requires(fields: "itchy8a4 { adornea4 until4b4 fooey29e trail84e ready859 sincead5 midstc23 }") sincead5: chinoaa2 ready859: chinoaa2 } @@ -272,7 +308,9 @@ input pilotdb4 { aforedf5: String } -type madly4e3 @key(fields: "since9b7 { adornea4 whose18d } thick5ed { adornea4 }") @key(fields: "since9b7 { adornea4 creek166 ready859 }") { +type madly4e3 + @key(fields: "since9b7 { adornea4 whose18d } thick5ed { adornea4 }") + @key(fields: "since9b7 { adornea4 creek166 ready859 }") { since9b7: [plate86e!]! glass900: plate86e @shareable troopc57(untilb37: String, hence9d2: Boolean, shyly5db: spike788, trail84e: String): [whose2eb!]! @@ -480,7 +518,8 @@ type madlycb0 { type cellof6e @key(fields: "where46e") { powerf43: String @external madlybd2: String @external - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 @requires(fields: "powerf43 madlybd2 trail84e") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 + @requires(fields: "powerf43 madlybd2 trail84e") where46e: String trail84e: String @external } @@ -603,7 +642,8 @@ type since829 { type judgeb10 @key(fields: "where46e fully04a") { powerf43: String @external madlybd2: String @external - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 @requires(fields: "madlybd2 powerf43") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 + @requires(fields: "madlybd2 powerf43") fully04a: String prune878: String @requires(fields: "madlybd2") truly8c7: String @requires(fields: "madlybd2") @@ -700,7 +740,9 @@ type minusd5e { bunch75f: String } -type whose2eb @key(fields: "wriste37 shyly5db since9b7 { adornea4 whose18d } aside50a { adornea4 }") @key(fields: "wriste37 shyly5db since9b7 { adornea4 creek166 ready859 } aside50a { adornea4 }") { +type whose2eb + @key(fields: "wriste37 shyly5db since9b7 { adornea4 whose18d } aside50a { adornea4 }") + @key(fields: "wriste37 shyly5db since9b7 { adornea4 creek166 ready859 } aside50a { adornea4 }") { aside50a: [plate86e!]! since9b7: [plate86e!]! glass900: plate86e @shareable @@ -786,7 +828,8 @@ type board4dc { type which640 @key(fields: "where46e") { powerf43: String @external madlybd2: String @external - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): fooeya48 @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): fooeya48 + @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") prune878: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") truly8c7: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") waist526: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") @@ -880,7 +923,8 @@ enum mustye8e { type fooey2eb @key(fields: "where46e") { powerf43: String @external madlybd2: String @external - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): aside4fb @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): aside4fb + @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") prune878: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") truly8c7: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") waist526: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") @@ -893,7 +937,8 @@ type fooey2eb @key(fields: "where46e") { type yowzacca @key(fields: "where46e") { powerf43: String @external madlybd2: String @external - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 @requires(fields: "powerf43 madlybd2 trail84e") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 + @requires(fields: "powerf43 madlybd2 trail84e") where46e: String trail84e: String @external } @@ -1015,7 +1060,8 @@ type peskya84 @key(fields: "madlybd2") { type daily61c @key(fields: "where46e") { powerf43: String @external madlybd2: String @external - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): chick5a5 + @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") prune878: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") truly8c7: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") waist526: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") @@ -1117,7 +1163,8 @@ type about448 { type these524 @key(fields: "where46e") { powerf43: String @external madlybd2: String @external - chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): moundc50 @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") + chops471(yahoocc1: String, until22e: String, scalp0ef: Boolean): moundc50 + @requires(fields: "powerf43 madlybd2 trail84e thick5ed perky28f { madlybd2 }") prune878: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") truly8c7: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") waist526: String @requires(fields: "madlybd2 trail84e thick5ed perky28f { madlybd2 }") @@ -1247,4 +1294,4 @@ scalar link__Import enum link__Purpose { EXECUTION SECURITY -} \ No newline at end of file +} diff --git a/__tests__/fixtures/dgs/yahoob90.graphql b/__tests__/fixtures/dgs/yahoob90.graphql index d1d775b..0a8b6b1 100644 --- a/__tests__/fixtures/dgs/yahoob90.graphql +++ b/__tests__/fixtures/dgs/yahoob90.graphql @@ -1,4 +1,8 @@ -extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@provides", "@requires", "@shareable", "@tag", "@inaccessible"]) +extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.0" + import: ["@key", "@external", "@provides", "@requires", "@shareable", "@tag", "@inaccessible"] + ) enum whosec2c { first056 @@ -270,4 +274,4 @@ type given59d @key(fields: "adornea4 wriste37 gussyd7d") { type Query { yowza85d(wheref5f: [until9cd!]!): [yahooe9b!]! -} \ No newline at end of file +} diff --git a/__tests__/fixtures/dgs/yowza9a2.graphql b/__tests__/fixtures/dgs/yowza9a2.graphql index 2e1f40d..d90f5e6 100644 --- a/__tests__/fixtures/dgs/yowza9a2.graphql +++ b/__tests__/fixtures/dgs/yowza9a2.graphql @@ -1,4 +1,19 @@ -schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@provides", "@requires", "@extends", "@shareable", "@tag", "@inaccessible", "@override"]) { +schema + @link(url: "https://specs.apollo.dev/link/v1.0") + @link( + url: "https://specs.apollo.dev/federation/v2.0" + import: [ + "@key" + "@external" + "@provides" + "@requires" + "@extends" + "@shareable" + "@tag" + "@inaccessible" + "@override" + ] + ) { query: Query mutation: Mutation } @@ -7,9 +22,17 @@ directive @extends on INTERFACE | OBJECT directive @external(reason: String) on FIELD_DEFINITION | OBJECT -directive @key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on INTERFACE | OBJECT +directive @key( + fields: federation__FieldSet! + resolvable: Boolean = true +) repeatable on INTERFACE | OBJECT -directive @link(as: String, for: link__Purpose, import: [link__Import], url: String) repeatable on SCHEMA +directive @link( + as: String + for: link__Purpose + import: [link__Import] + url: String +) repeatable on SCHEMA directive @override(from: String!) on FIELD_DEFINITION @@ -19,9 +42,15 @@ directive @requires(fields: federation__FieldSet!) on FIELD_DEFINITION directive @shareable on FIELD_DEFINITION | OBJECT -directive @tag(name: String!) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION +directive @tag( + name: String! +) repeatable on ARGUMENT_DEFINITION | ENUM | ENUM_VALUE | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | INPUT_OBJECT | INTERFACE | OBJECT | SCALAR | UNION -type untilba2 @extends @key(fields: "metal6ef { fooey29e trail84e adornea4 } until877 oftend70 where5b0 truly811 sadly185 fooeyc5f") { +type untilba2 + @extends + @key( + fields: "metal6ef { fooey29e trail84e adornea4 } until877 oftend70 where5b0 truly811 sadly185 fooeyc5f" + ) { oftend70: String @external metal6ef: [badlya1f!]! @external where5b0: String @external @@ -47,7 +76,18 @@ type plate86e @key(fields: "adornea4 creek166 ready859") { adornea4: smalld4d! dwarf084: madly4e3 rigida5f(whose5d3: tastyc47): curvecc9 - along2c9(which541: Boolean!, sally487: String, whose5d3: tastyc47, chief6ff: String, sadly930: Boolean, until877: Int, yahoo609: String, creek166: String!, messy877: madlyd8f, fooeyc5f: Int): curvecc9 + along2c9( + which541: Boolean! + sally487: String + whose5d3: tastyc47 + chief6ff: String + sadly930: Boolean + until877: Int + yahoo609: String + creek166: String! + messy877: madlyd8f + fooeyc5f: Int + ): curvecc9 bruite6b(whose5d3: tastyc47, fooeyc5f: Int, forgeced: madly963!, fullyb31: String!): curvecc9! repay45b: dimlyf57 oftenec4(organ346: String!, until22e: String!): click2e7 @@ -219,7 +259,9 @@ type Mutation { yowzac84: query60d! } -type dimlyf57 @key(fields: "yahoo45c untilb37 { organ346 until22e often162 }") @key(fields: "whose18d") { +type dimlyf57 + @key(fields: "yahoo45c untilb37 { organ346 until22e often162 }") + @key(fields: "whose18d") { awake90c: shyly29e! churn085: madly4e3! rigida5f: afterbba! @@ -287,7 +329,9 @@ type among79f @key(fields: "seize765 whose18d") { sadlya5e: [abovefd8!]! } -type beach049 @key(fields: "seize765 trail84e yahoo45c") @key(fields: "seize765 trail84e whose18d") { +type beach049 + @key(fields: "seize765 trail84e yahoo45c") + @key(fields: "seize765 trail84e whose18d") { wetlyc96: String! spoola62: until845! seize765: String! @@ -333,11 +377,30 @@ type whereee4 { } type Query { - hence908(oftend70: String, organ346: String, whose5d3: tastyc47, where5b0: String, truly811: String, until877: Float!, creek166: String!, sadly185: String, fooeyc5f: Float): untilba2! + hence908( + oftend70: String + organ346: String + whose5d3: tastyc47 + where5b0: String + truly811: String + until877: Float! + creek166: String! + sadly185: String + fooeyc5f: Float + ): untilba2! whose8f8(whose5d3: tastyc47, fullyb31: String): plate86e! shyly130(sally487: String, creek166: String!, messy877: madlyd8f, trulyc40: unban11a!): curvecc9! where3c7(often162: String!, organ346: String!, until22e: String!): madly4e3! - dwarf084(organ346: String, creek166: String, until7aa: pilotdb4, ditch5b8: String, fooeyc5f: Int, fully85e: Boolean, while7c7: Int, alert274: Int): madly4e3! + dwarf084( + organ346: String + creek166: String + until7aa: pilotdb4 + ditch5b8: String + fooeyc5f: Int + fully85e: Boolean + while7c7: Int + alert274: Int + ): madly4e3! freshb8f(fullyb31: String!): dimlyf57 cadgea1b(often162: String!, organ346: String!, until22e: String!): click2e7 dailycb0(creek166: String!): madly4e3! @@ -370,4 +433,4 @@ scalar link__Import enum link__Purpose { EXECUTION SECURITY -} \ No newline at end of file +} diff --git a/__tests__/fixtures/huge-schema/fooeya02.graphql b/__tests__/fixtures/huge-schema/fooeya02.graphql index c05927e..cd01ef7 100644 --- a/__tests__/fixtures/huge-schema/fooeya02.graphql +++ b/__tests__/fixtures/huge-schema/fooeya02.graphql @@ -111,7 +111,7 @@ type madly997 { } union punch4a7 = - sadlye17 + | sadlye17 | ethylafa | month7b6 | sprag134 diff --git a/__tests__/subgraph/errors/INVALID_GRAPHQL.spec.ts b/__tests__/subgraph/errors/INVALID_GRAPHQL.spec.ts index d8e2cc2..e7b5f74 100644 --- a/__tests__/subgraph/errors/INVALID_GRAPHQL.spec.ts +++ b/__tests__/subgraph/errors/INVALID_GRAPHQL.spec.ts @@ -501,4 +501,37 @@ testVersions((api, version) => { ]), ); }); + + test('interface field -> object field subtype', () => { + const result = api.composeServices([ + { + typeDefs: graphql` + extend schema @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key"]) + + type Query { + a: Admin + } + + type Admin implements Node { + id: ID + } + + interface Node { + id: ID! + } + `, + name: 'a', + }, + ]); + + assertCompositionFailure(result); + + expect(result.errors).toContainEqual( + expect.objectContaining({ + message: + '[a] Interface field Node.id expects type ID! but Admin.id of type ID is not a proper subtype.', + extensions: expect.objectContaining({ code: 'INVALID_GRAPHQL' }), + }), + ); + }); }); diff --git a/__tests__/supergraph-composition.spec.ts b/__tests__/supergraph-composition.spec.ts index b1d8bee..12279c9 100644 --- a/__tests__/supergraph-composition.spec.ts +++ b/__tests__/supergraph-composition.spec.ts @@ -676,95 +676,93 @@ testImplementations(api => { }); test('removes federation__Policy and federation__Scope scalars', () => { - const result = api.composeServices( - [ - { - name: 'feed', - typeDefs: graphql` - extend schema - @link( - url: "https://specs.apollo.dev/federation/v2.6" - import: ["@requiresScopes", "@policy", "@key", "@shareable"] - ) - - scalar federation__Scope - scalar federation__Policy - - directive @federation__requiresScopes( - scopes: [[federation__Scope!]!]! - ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM - directive @federation__policy( - policies: [[federation__Policy!]!]! - ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM - - type Query { - feed: [Post!]! - @shareable - @requiresScopes(scopes: [["read:posts"]]) - @policy(policies: [["read_posts"]]) - } - - type Post @key(fields: "id") { - id: ID! - title: String! - } - `, - }, - { - name: 'comments', - typeDefs: graphql` - extend schema - @link( - url: "https://specs.apollo.dev/federation/v2.6" - import: [ - "@authenticated" - "@requiresScopes" - "@policy" - "@key" - "@external" - "@shareable" - ] - ) - - scalar federation__Scope - scalar federation__Policy - - directive @federation__requiresScopes( - scopes: [[federation__Scope!]!]! - ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM - directive @federation__policy( - policies: [[federation__Policy!]!]! - ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM - - extend type Post @key(fields: "id") { - id: ID! @external - comments: [Comment!]! @policy(policies: [["read_post_comments"]]) - } - - extend type Query { - feed: [Post!]! - @shareable - @policy(policies: [["read_post_comments"], ["read_post_comments"]]) - @requiresScopes(scopes: [["read:posts"]]) - } - - type Mutation { - commentPost(input: CommentPostInput!): Comment! @authenticated - } - - input CommentPostInput { - postId: ID! - comment: String! - } - - type Comment { - id: ID! - body: String! - } - `, - }, - ], - ); + const result = api.composeServices([ + { + name: 'feed', + typeDefs: graphql` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.6" + import: ["@requiresScopes", "@policy", "@key", "@shareable"] + ) + + scalar federation__Scope + scalar federation__Policy + + directive @federation__requiresScopes( + scopes: [[federation__Scope!]!]! + ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + directive @federation__policy( + policies: [[federation__Policy!]!]! + ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + + type Query { + feed: [Post!]! + @shareable + @requiresScopes(scopes: [["read:posts"]]) + @policy(policies: [["read_posts"]]) + } + + type Post @key(fields: "id") { + id: ID! + title: String! + } + `, + }, + { + name: 'comments', + typeDefs: graphql` + extend schema + @link( + url: "https://specs.apollo.dev/federation/v2.6" + import: [ + "@authenticated" + "@requiresScopes" + "@policy" + "@key" + "@external" + "@shareable" + ] + ) + + scalar federation__Scope + scalar federation__Policy + + directive @federation__requiresScopes( + scopes: [[federation__Scope!]!]! + ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + directive @federation__policy( + policies: [[federation__Policy!]!]! + ) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + + extend type Post @key(fields: "id") { + id: ID! @external + comments: [Comment!]! @policy(policies: [["read_post_comments"]]) + } + + extend type Query { + feed: [Post!]! + @shareable + @policy(policies: [["read_post_comments"], ["read_post_comments"]]) + @requiresScopes(scopes: [["read:posts"]]) + } + + type Mutation { + commentPost(input: CommentPostInput!): Comment! @authenticated + } + + input CommentPostInput { + postId: ID! + comment: String! + } + + type Comment { + id: ID! + body: String! + } + `, + }, + ]); assertCompositionSuccess(result); diff --git a/__tests__/supergraph/errors/INVALID-GRAPHQL.spec.ts b/__tests__/supergraph/errors/INVALID-GRAPHQL.spec.ts new file mode 100644 index 0000000..e9ce932 --- /dev/null +++ b/__tests__/supergraph/errors/INVALID-GRAPHQL.spec.ts @@ -0,0 +1,93 @@ +import { expect, test } from 'vitest'; +import { + assertCompositionFailure, + assertCompositionSuccess, + graphql, + testVersions, +} from '../../shared/testkit.js'; + +testVersions((api, version) => { + test('interface field -> object field subtype', () => { + const result = api.composeServices([ + { + typeDefs: graphql` + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@shareable"]) + + type Query { + a: Admin + } + + type Admin implements Node { + id: ID! @shareable + } + + type User implements Node { + id: ID! + } + + interface Node { + id: ID! + } + `, + name: 'a', + }, + { + typeDefs: graphql` + extend schema @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@key"]) + + type Query { + b: Admin + } + + type Admin @key(fields: "id") { + id: ID + name: String! + } + `, + name: 'b', + }, + ]); + + assertCompositionFailure(result); + + expect(result.errors).toContainEqual( + expect.objectContaining({ + message: + 'Interface field Node.id expects type ID! but Admin.id of type ID is not a proper subtype.', + extensions: expect.objectContaining({ code: 'INVALID_GRAPHQL' }), + }), + ); + + assertCompositionSuccess( + api.composeServices([ + { + typeDefs: graphql` + extend schema + @link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@shareable"]) + + type Query { + a: Admin + } + + type Admin implements Node { + id: ID! @shareable + parent: Node + } + + type User implements Node { + id: ID! + parent: User + } + + interface Node { + id: ID! + parent: Node + } + `, + name: 'a', + }, + ]), + ); + }); +}); diff --git a/src/graphql/contains-supergraph-spec.ts b/src/graphql/contains-supergraph-spec.ts index ad5c8c8..54b2968 100644 --- a/src/graphql/contains-supergraph-spec.ts +++ b/src/graphql/contains-supergraph-spec.ts @@ -1,17 +1,20 @@ -import { federationDirectives, federationEnums, federationScalars } from "./transform-supergraph-to-public-schema.js"; - +import { + federationDirectives, + federationEnums, + federationScalars, +} from './transform-supergraph-to-public-schema.js'; const supergraphSpecDetectionRegex = new RegExp( - Array.from(federationScalars) - .concat(Array.from(federationEnums)) - // "[NAME" or " NAME" for scalars and enums - .map(name => [`\\[${name}`, `\\s${name}`]) - .flat(2) - // "@NAME" for directives - .concat(Array.from(federationDirectives).map(name => `@${name}`)) - .join('|'), - ); - - export function containsSupergraphSpec(sdl: string): boolean { - return supergraphSpecDetectionRegex.test(sdl); - } \ No newline at end of file + Array.from(federationScalars) + .concat(Array.from(federationEnums)) + // "[NAME" or " NAME" for scalars and enums + .map(name => [`\\[${name}`, `\\s${name}`]) + .flat(2) + // "@NAME" for directives + .concat(Array.from(federationDirectives).map(name => `@${name}`)) + .join('|'), +); + +export function containsSupergraphSpec(sdl: string): boolean { + return supergraphSpecDetectionRegex.test(sdl); +} diff --git a/src/subgraph/validation/validate-state.ts b/src/subgraph/validation/validate-state.ts index 7646b99..4e8a327 100644 --- a/src/subgraph/validation/validate-state.ts +++ b/src/subgraph/validation/validate-state.ts @@ -570,7 +570,7 @@ function validateTypeImplementsInterface( if (!isTypeSubTypeOf(state, implementationsMap, typeField.type, ifaceField.type)) { reportError( `Interface field ${interfaceType.name}.${fieldName} expects type ` + - `${ifaceField.type} but ${type.name}.${fieldName} is type ${typeField.type}.`, + `${ifaceField.type} but ${type.name}.${fieldName} of type ${typeField.type} is not a proper subtype.`, ); } diff --git a/src/subgraph/validation/validation-context.ts b/src/subgraph/validation/validation-context.ts index 32b024c..93e7f5e 100644 --- a/src/subgraph/validation/validation-context.ts +++ b/src/subgraph/validation/validation-context.ts @@ -71,8 +71,8 @@ export function createSimpleValidationContext(typeDefs: DocumentNode, typeNodeIn ancestor.kind === Kind.SCHEMA_DEFINITION || ancestor.kind === Kind.SCHEMA_EXTENSION ? 'schema' : 'name' in ancestor && ancestor.name - ? ancestor.name.value - : ''; + ? ancestor.name.value + : ''; if (coordinate.length > 0) { coordinate = coordinate + '.' + name; } else { diff --git a/src/supergraph/composition/directive.ts b/src/supergraph/composition/directive.ts index cc5d336..ccb892b 100644 --- a/src/supergraph/composition/directive.ts +++ b/src/supergraph/composition/directive.ts @@ -134,7 +134,12 @@ function getOrCreateDirective(state: Map, directiveName: return def; } -function getOrCreateArg(directiveState: DirectiveState, argName: string, argType: string, argKind: ArgumentKind) { +function getOrCreateArg( + directiveState: DirectiveState, + argName: string, + argType: string, + argKind: ArgumentKind, +) { const existing = directiveState.args.get(argName); if (existing) { diff --git a/src/supergraph/composition/input-object-type.ts b/src/supergraph/composition/input-object-type.ts index 3ee3dd8..f9f0305 100644 --- a/src/supergraph/composition/input-object-type.ts +++ b/src/supergraph/composition/input-object-type.ts @@ -1,6 +1,12 @@ import { DirectiveNode } from 'graphql'; import { FederationVersion } from '../../specifications/federation.js'; -import { Argument, ArgumentKind, Deprecated, Description, InputObjectType } from '../../subgraph/state.js'; +import { + Argument, + ArgumentKind, + Deprecated, + Description, + InputObjectType, +} from '../../subgraph/state.js'; import { createInputObjectTypeNode } from './ast.js'; import { convertToConst, type MapByGraph, type TypeBuilder } from './common.js'; @@ -35,7 +41,12 @@ export function inputObjectTypeBuilder(): TypeBuilder fieldState.tags.add(tag)); diff --git a/src/supergraph/composition/interface-type.ts b/src/supergraph/composition/interface-type.ts index b834977..1d09b14 100644 --- a/src/supergraph/composition/interface-type.ts +++ b/src/supergraph/composition/interface-type.ts @@ -150,7 +150,7 @@ export function interfaceTypeBuilder(): TypeBuilder { argState.defaultValue = arg.defaultValue; } - argState.kind = arg.kind + argState.kind = arg.kind; argState.byGraph.set(graph.id, { type: arg.type, @@ -917,7 +917,12 @@ function getOrCreateField(objectTypeState: ObjectTypeState, fieldName: string, f return def; } -function getOrCreateArg(fieldState: ObjectTypeFieldState, argName: string, argType: string, argKind: ArgumentKind) { +function getOrCreateArg( + fieldState: ObjectTypeFieldState, + argName: string, + argType: string, + argKind: ArgumentKind, +) { const existing = fieldState.args.get(argName); if (existing) { diff --git a/src/supergraph/state.ts b/src/supergraph/state.ts index bd9987a..ff8264e 100644 --- a/src/supergraph/state.ts +++ b/src/supergraph/state.ts @@ -10,7 +10,6 @@ import { Link, mergeLinks } from '../specifications/link.js'; import { parseFields } from '../subgraph/helpers.js'; import { SubgraphState } from '../subgraph/state.js'; import { createJoinGraphEnumTypeNode } from './composition/ast.js'; -import { Graph } from './composition/common.js'; import { directiveBuilder, DirectiveState } from './composition/directive.js'; import { enumTypeBuilder, EnumTypeState } from './composition/enum-type.js'; import { inputObjectTypeBuilder, InputObjectTypeState } from './composition/input-object-type.js'; diff --git a/src/supergraph/validation/rules/interface-subtype-rule.ts b/src/supergraph/validation/rules/interface-subtype-rule.ts new file mode 100644 index 0000000..85b9c7b --- /dev/null +++ b/src/supergraph/validation/rules/interface-subtype-rule.ts @@ -0,0 +1,197 @@ +import { GraphQLError } from 'graphql'; +import { isList, isNonNull, stripList, stripNonNull } from '../../../utils/state.js'; +import { EnumTypeState } from '../../composition/enum-type.js'; +import { InterfaceTypeState } from '../../composition/interface-type.js'; +import { ObjectTypeState } from '../../composition/object-type.js'; +import { ScalarTypeState } from '../../composition/scalar-type.js'; +import { UnionTypeState } from '../../composition/union-type.js'; +import { SupergraphVisitorMap } from '../../composition/visitor.js'; +import { SupergraphState } from '../../state.js'; +import { SupergraphValidationContext } from '../validation-context.js'; + +export function InterfaceSubtypeRule( + context: SupergraphValidationContext, + supergraph: SupergraphState, +): SupergraphVisitorMap { + const implementationsMap = new Map>(); + + for (const type of supergraph.interfaceTypes.values()) { + // Store implementations by interface. + for (const iface of type.interfaces) { + const interfaceType = getTypeFromSupergraph(supergraph, iface); + if (interfaceType && isInterfaceType(interfaceType)) { + let implementations = implementationsMap.get(iface); + if (implementations === undefined) { + implementationsMap.set(iface, new Set([type.name])); + } else { + implementations.add(type.name); + } + } + } + } + + for (const type of supergraph.objectTypes.values()) { + // Store implementations by objects. + for (const iface of type.interfaces) { + const interfaceType = getTypeFromSupergraph(supergraph, iface); + if (interfaceType && isInterfaceType(interfaceType)) { + let implementations = implementationsMap.get(iface); + if (implementations === undefined) { + implementationsMap.set(iface, new Set([type.name])); + } else { + implementations.add(type.name); + } + } + } + } + + return { + ObjectTypeField(objectTypeState, fieldState) { + if (objectTypeState.interfaces.size === 0) { + return; + } + + const interfaceNames = Array.from(objectTypeState.interfaces.values()); + + for (const interfaceName of interfaceNames) { + const interfaceState = supergraph.interfaceTypes.get(interfaceName); + if (!interfaceState) { + continue; + } + + const interfaceField = interfaceState.fields.get(fieldState.name); + if (!interfaceField) { + continue; + } + + if ( + !isTypeSubTypeOf(supergraph, implementationsMap, fieldState.type, interfaceField.type) + ) { + context.reportError( + new GraphQLError( + `Interface field ${interfaceName}.${interfaceField.name} expects type ${interfaceField.type} but ${objectTypeState.name}.${fieldState.name} of type ${fieldState.type} is not a proper subtype.`, + { + extensions: { + code: 'INVALID_GRAPHQL', + }, + }, + ), + ); + } + } + }, + }; +} + +/** + * Provided a type and a super type, return true if the first type is either + * equal or a subset of the second super type (covariant). + */ +function isTypeSubTypeOf( + state: SupergraphState, + implementationsMap: Map>, + maybeSubTypeName: string, + superTypeName: string, +): boolean { + // Equivalent type is a valid subtype + if (maybeSubTypeName === superTypeName) { + return true; + } + + // If superType is non-null, maybeSubType must also be non-null. + if (isNonNull(superTypeName)) { + if (isNonNull(maybeSubTypeName)) { + return isTypeSubTypeOf( + state, + implementationsMap, + stripNonNull(maybeSubTypeName), + stripNonNull(superTypeName), + ); + } + return false; + } + if (isNonNull(maybeSubTypeName)) { + // If superType is nullable, maybeSubType may be non-null or nullable. + return isTypeSubTypeOf( + state, + implementationsMap, + stripNonNull(maybeSubTypeName), + superTypeName, + ); + } + + // If superType type is a list, maybeSubType type must also be a list. + if (isList(superTypeName)) { + if (isList(maybeSubTypeName)) { + return isTypeSubTypeOf( + state, + implementationsMap, + stripList(maybeSubTypeName), + stripList(superTypeName), + ); + } + return false; + } + if (isList(maybeSubTypeName)) { + // If superType is not a list, maybeSubType must also be not a list. + return false; + } + + const superType = getTypeFromSupergraph(state, superTypeName); + const maybeSubType = getTypeFromSupergraph(state, maybeSubTypeName); + + // The existence of the types was already validated. + // If one of them does not exist, it means it's been reported by KnownTypeNamesRule. + if (!superType || !maybeSubType) { + return false; + } + + // If superType type is an abstract type, check if it is super type of maybeSubType. + // Otherwise, the child type is not a valid subtype of the parent type. + return ( + isAbstractType(superType) && + (isInterfaceType(maybeSubType) || isObjectType(maybeSubType)) && + isSubType(implementationsMap, superType, maybeSubType) + ); +} + +function getTypeFromSupergraph(state: SupergraphState, name: string) { + return ( + state.objectTypes.get(name) ?? state.interfaceTypes.get(name) ?? state.unionTypes.get(name) + ); +} + +function isSubType( + implementationsMap: Map>, + abstractType: InterfaceTypeState | UnionTypeState, + maybeSubType: ObjectTypeState | InterfaceTypeState, +): boolean { + if (isUnionType(abstractType)) { + return abstractType.members.has(maybeSubType.name); + } + + return implementationsMap.get(abstractType.name)?.has(maybeSubType.name) ?? false; +} + +type SupergraphType = + | ObjectTypeState + | EnumTypeState + | ScalarTypeState + | UnionTypeState + | InterfaceTypeState; + +function isAbstractType(type: SupergraphType): type is UnionTypeState | InterfaceTypeState { + return isInterfaceType(type) || isUnionType(type); +} + +function isObjectType(type: SupergraphType): type is ObjectTypeState { + return type.kind === 'object'; +} + +function isInterfaceType(type: SupergraphType): type is InterfaceTypeState { + return type.kind === 'interface'; +} + +function isUnionType(type: SupergraphType): type is UnionTypeState { + return type.kind === 'union'; +} diff --git a/src/supergraph/validation/rules/satisfiablity/walker.ts b/src/supergraph/validation/rules/satisfiablity/walker.ts index 7922ee7..9d56dab 100644 --- a/src/supergraph/validation/rules/satisfiablity/walker.ts +++ b/src/supergraph/validation/rules/satisfiablity/walker.ts @@ -96,8 +96,8 @@ export class Walker { operationType === OperationTypeNode.QUERY ? 'Query' : operationType === OperationTypeNode.MUTATION - ? 'Mutation' - : 'Subscription', + ? 'Mutation' + : 'Subscription', false, ); diff --git a/src/supergraph/validation/validate-supergraph.ts b/src/supergraph/validation/validate-supergraph.ts index 19d9921..a1fa83f 100644 --- a/src/supergraph/validation/validate-supergraph.ts +++ b/src/supergraph/validation/validate-supergraph.ts @@ -15,6 +15,7 @@ import { InputFieldDefaultMismatchRule } from './rules/input-field-default-misma import { InputObjectValuesRule } from './rules/input-object-values-rule.js'; import { InterfaceKeyMissingImplementationTypeRule } from './rules/interface-key-missing-implementation-type.js'; import { InterfaceObjectUsageErrorRule } from './rules/interface-object-usage-error.js'; +import { InterfaceSubtypeRule } from './rules/interface-subtype-rule.js'; import { InvalidFieldSharingRule } from './rules/invalid-field-sharing-rule.js'; import { OnlyInaccessibleChildrenRule } from './rules/only-inaccessible-children-rule.js'; import { OverrideSourceHasOverrideRule } from './rules/override-source-has-override.js'; @@ -78,6 +79,7 @@ export function validateSupergraph( SatisfiabilityRule, SubgraphNameRule, RequiredArgumentOrFieldIsNotInaccessibleRule, + InterfaceSubtypeRule, ]; const supergraph = state.getSupergraphState();