From 038ab403ee3bedc67a61822568bcde6e25552162 Mon Sep 17 00:00:00 2001 From: JivusAyrus Date: Tue, 29 Aug 2023 16:02:55 +0530 Subject: [PATCH] fix: do not include _Service and _entities in the federated graph --- composition/src/subgraph/subgraph.ts | 53 +++++++++----- composition/src/utils/string-constants.ts | 3 + composition/tests/federation-factory.test.ts | 73 +++++++++++++++++++- router/go.mod | 2 +- router/go.sum | 4 +- 5 files changed, 114 insertions(+), 21 deletions(-) diff --git a/composition/src/subgraph/subgraph.ts b/composition/src/subgraph/subgraph.ts index 0d98d821b3..c6cb5d4d8c 100644 --- a/composition/src/subgraph/subgraph.ts +++ b/composition/src/subgraph/subgraph.ts @@ -11,6 +11,7 @@ import { import { getNamedTypeForChild } from '../type-merging/type-merging'; import { getOrThrowError } from '../utils/utils'; import { printTypeNode } from '@graphql-tools/merge'; +import { ENTITIES_FIELD, SERVICE, SERVICE_FIELD } from '../utils/string-constants'; export type Subgraph = { definitions: DocumentNode; @@ -39,10 +40,7 @@ export function validateSubgraphName( } // Places the object-like nodes into the multigraph including the concrete types for abstract types -export function walkSubgraphToCollectObjects( - factory: FederationFactory, - subgraph: InternalSubgraph, -) { +export function walkSubgraphToCollectObjects(factory: FederationFactory, subgraph: InternalSubgraph) { subgraph.definitions = visit(subgraph.definitions, { InterfaceTypeDefinition: { enter(node) { @@ -52,9 +50,11 @@ export function walkSubgraphToCollectObjects( ObjectTypeDefinition: { enter(node) { const name = node.name.value; + if (name === SERVICE) { + return false; + } const operationType = subgraph.operationTypes.get(name); - const parentTypeName = operationType ? getOrThrowError(operationTypeNodeToDefaultType, operationType) - : name; + const parentTypeName = operationType ? getOrThrowError(operationTypeNodeToDefaultType, operationType) : name; factory.addConcreteTypesForInterface(node); if (!factory.graph.hasNode(parentTypeName)) { factory.graph.addNode(parentTypeName); @@ -75,8 +75,7 @@ export function walkSubgraphToCollectObjects( enter(node) { const name = node.name.value; const operationType = subgraph.operationTypes.get(name); - const parentTypeName = operationType ? getOrThrowError(operationTypeNodeToDefaultType, operationType) - : name; + const parentTypeName = operationType ? getOrThrowError(operationTypeNodeToDefaultType, operationType) : name; factory.addConcreteTypesForInterface(node); if (!factory.graph.hasNode(parentTypeName)) { factory.graph.addNode(parentTypeName); @@ -102,14 +101,14 @@ export function walkSubgraphToCollectObjects( }); } -export function walkSubgraphToCollectOperationsAndFields( - factory: FederationFactory, - subgraph: Subgraph, -) { +export function walkSubgraphToCollectOperationsAndFields(factory: FederationFactory, subgraph: Subgraph) { let isCurrentParentRootType = false; visit(subgraph.definitions, { ObjectTypeDefinition: { enter(node) { + if (node.name.value === SERVICE) { + return false; + } isCurrentParentRootType = factory.isObjectRootType(node); factory.isCurrentParentEntity = isObjectLikeNodeEntity(node); factory.parentTypeName = node.name.value; @@ -133,6 +132,11 @@ export function walkSubgraphToCollectOperationsAndFields( FieldDefinition: { enter(node) { const fieldName = node.name.value; + if(isCurrentParentRootType){ + if(fieldName === SERVICE_FIELD || fieldName === ENTITIES_FIELD){ + return false + } + } const fieldPath = `${factory.parentTypeName}.${fieldName}`; const fieldRootTypeName = getNamedTypeForChild(fieldPath, node.type); // If a node exists in the multigraph, it's a concrete object type @@ -169,7 +173,10 @@ export function walkSubgraphToCollectOperationsAndFields( // This also records the appearance of this field in the current subgraph if (factory.graph.hasNode(fieldRootTypeName)) { factory.upsertConcreteObjectLikeOperationFieldNode( - fieldName, fieldRootTypeName, fieldPath, printTypeNode(node.type), + fieldName, + fieldRootTypeName, + fieldPath, + printTypeNode(node.type), ); return false; } @@ -186,7 +193,11 @@ export function walkSubgraphToCollectOperationsAndFields( } // Upsert response types and add edges from the operation to each possible concrete type for the abstract field factory.upsertAbstractObjectLikeOperationFieldNode( - fieldName, fieldRootTypeName, fieldPath, printTypeNode(node.type), concreteTypes + fieldName, + fieldRootTypeName, + fieldPath, + printTypeNode(node.type), + concreteTypes, ); return false; }, @@ -232,7 +243,13 @@ export function walkSubgraphToFederate(subgraph: DocumentNode, factory: Federati }, FieldDefinition: { enter(node) { - factory.childName = node.name.value; + const name = node.name.value; + if (factory.isParentRootType) { + if (name === SERVICE_FIELD || name === ENTITIES_FIELD) { + return false; + } + } + factory.childName = name; factory.upsertFieldNode(node); }, leave() { @@ -276,6 +293,9 @@ export function walkSubgraphToFederate(subgraph: DocumentNode, factory: Federati }, ObjectTypeDefinition: { enter(node) { + if (node.name.value === SERVICE) { + return false; + } factory.areFieldsShareable = !factory.isCurrentSubgraphVersionTwo || isNodeShareable(node); factory.isCurrentParentEntity = isObjectLikeNodeEntity(node); factory.isParentRootType = factory.isObjectRootType(node); @@ -295,8 +315,7 @@ export function walkSubgraphToFederate(subgraph: DocumentNode, factory: Federati factory.isCurrentParentExtensionType = true; factory.isCurrentParentEntity = isObjectLikeNodeEntity(node); factory.parentTypeName = name; - factory.areFieldsShareable = - !factory.isCurrentSubgraphVersionTwo || isNodeShareable(node); + factory.areFieldsShareable = !factory.isCurrentSubgraphVersionTwo || isNodeShareable(node); factory.isParentRootType = factory.isObjectRootType(node); factory.upsertExtensionNode(node); }, diff --git a/composition/src/utils/string-constants.ts b/composition/src/utils/string-constants.ts index cc04e5cd48..c67d6de462 100644 --- a/composition/src/utils/string-constants.ts +++ b/composition/src/utils/string-constants.ts @@ -3,6 +3,7 @@ export const DEFAULT_MUTATION = 'Mutation'; export const DEFAULT_QUERY = 'Query'; export const DEFAULT_SUBSCRIPTION = 'Subscription'; export const DEPRECATED = 'deprecated'; +export const ENTITIES_FIELD = '_entities'; export const ENUM_UPPER = 'ENUM'; export const ENUM_VALUE_UPPER = 'ENUM_VALUE'; export const EXTERNAL = 'external'; @@ -31,6 +32,8 @@ export const REQUIRES = 'requires'; export const SCALAR_UPPER = 'SCALAR'; export const SCHEMA = 'schema'; export const SCHEMA_UPPER = 'SCHEMA'; +export const SERVICE = '_Service'; +export const SERVICE_FIELD = '_service'; export const SHAREABLE = 'shareable'; export const STRING_TYPE = 'String'; export const SUBSCRIPTION = 'Subscription'; diff --git a/composition/tests/federation-factory.test.ts b/composition/tests/federation-factory.test.ts index fb937acd56..03b2b53d63 100644 --- a/composition/tests/federation-factory.test.ts +++ b/composition/tests/federation-factory.test.ts @@ -211,6 +211,29 @@ describe('FederationFactory tests', () => { ), ); }); + + test('that service object, entities and service fields are not included in the federated graph', () => { + const { errors, federatedGraphAST } = federateSubgraphs([subgraphG, subgraphH]); + expect(errors).toBeUndefined(); + expect(documentNodeToNormalizedString(federatedGraphAST!)).toBe( + normalizeString( + versionOneBaseSchema + + ` + union _Entity = User + + type Query { + string: String + } + + type User { + id: String + } + + scalar _Any + `, + ), + ); + }); }); const subgraphA = { @@ -449,4 +472,52 @@ const subgraphF: Subgraph = { string: String } `), -}; \ No newline at end of file +}; + +const subgraphG: Subgraph = { + name: 'subgraph-g', + url: '', + definitions: parse(` + type Query { + string: String + _service: _Service + _entities(representations: [_Any!]!): [_Entity]! + } + + type _Service{ + sdl: String + } + + union _Entity = User + + type User @key(fields: "id"){ + id: String + } + + scalar _Any + `), +}; + +const subgraphH: Subgraph = { + name: 'subgraph-h', + url: '', + definitions: parse(` + type Query { + string: String + _service: _Service + _entities(representations: [_Any!]!): [_Entity]! + } + + type _Service{ + sdl: String + } + + union _Entity = User + + type User @key(fields: "id"){ + id: String + } + + scalar _Any + `), +}; diff --git a/router/go.mod b/router/go.mod index ccd9ba40ca..01ec0e3c2a 100644 --- a/router/go.mod +++ b/router/go.mod @@ -17,7 +17,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/tidwall/gjson v1.14.4 github.com/tidwall/sjson v1.2.5 - github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.2.0.20230822083323-a115bc0c7af6 + github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.2.0.20230829102818-e2553cf069d3 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.39.0 diff --git a/router/go.sum b/router/go.sum index 7314a682e2..deb0d0a54b 100644 --- a/router/go.sum +++ b/router/go.sum @@ -307,8 +307,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/vektah/gqlparser/v2 v2.5.1 h1:ZGu+bquAY23jsxDRcYpWjttRZrUz07LbiY77gUOHcr4= -github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.2.0.20230822083323-a115bc0c7af6 h1:LRQ/s+rdZhVBpOJ2wlIk7vq60VAlanbdzRGZP5ViIeE= -github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.2.0.20230822083323-a115bc0c7af6/go.mod h1:jOEQFeTIDSAEWA//qrpSNjGYcCjMylvc/R/W8eM7+gY= +github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.2.0.20230829102818-e2553cf069d3 h1:BZRss0FCEDbQL04kxX0YPsJghx6Y5Zzemf40BAxlRQM= +github.com/wundergraph/graphql-go-tools/v2 v2.0.0-rc.2.0.20230829102818-e2553cf069d3/go.mod h1:jOEQFeTIDSAEWA//qrpSNjGYcCjMylvc/R/W8eM7+gY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=