diff --git a/packages/apollo-federation/CHANGELOG.md b/packages/apollo-federation/CHANGELOG.md index b0a7e2667b7..752c94cf6ad 100644 --- a/packages/apollo-federation/CHANGELOG.md +++ b/packages/apollo-federation/CHANGELOG.md @@ -4,7 +4,7 @@ > The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the appropriate changes within that release will be moved into the new section. -- _Nothing yet! Stay tuned._ +- Export `defaultRootOperationNameLookup` and `normalizeTypeDefs`; needed by `@apollo/gateway` to normalize root operation types when reporting to Apollo Graph Manager. [#4071](https://github.com/apollographql/apollo-server/pull/4071) ## 0.15.0 diff --git a/packages/apollo-federation/src/composition/index.ts b/packages/apollo-federation/src/composition/index.ts index 92f38fe9a62..1d30fd138bb 100644 --- a/packages/apollo-federation/src/composition/index.ts +++ b/packages/apollo-federation/src/composition/index.ts @@ -2,3 +2,4 @@ export * from './compose'; export * from './composeAndValidate'; export * from './types'; export { compositionRules } from './rules'; +export { defaultRootOperationNameLookup, normalizeTypeDefs } from './normalize'; diff --git a/packages/apollo-federation/src/composition/normalize.ts b/packages/apollo-federation/src/composition/normalize.ts index 75f423c561a..36fe58d51fe 100644 --- a/packages/apollo-federation/src/composition/normalize.ts +++ b/packages/apollo-federation/src/composition/normalize.ts @@ -15,18 +15,18 @@ export function normalizeTypeDefs(typeDefs: DocumentNode) { ); } +// Map of OperationTypeNode to its respective default root operation type name +export const defaultRootOperationNameLookup: { + [node in OperationTypeNode]: DefaultRootOperationTypeName; +} = { + query: 'Query', + mutation: 'Mutation', + subscription: 'Subscription', +}; + export function defaultRootOperationTypes( typeDefs: DocumentNode, ): DocumentNode { - // Map of OperationTypeNode to its respective default root operation type name - const defaultRootOperationNameLookup: { - [node in OperationTypeNode]: DefaultRootOperationTypeName; - } = { - query: 'Query', - mutation: 'Mutation', - subscription: 'Subscription', - }; - // Array of default root operation names const defaultRootOperationNames = Object.values( defaultRootOperationNameLookup, diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md index 4e66cee7e10..e3c31aea821 100644 --- a/packages/apollo-gateway/CHANGELOG.md +++ b/packages/apollo-gateway/CHANGELOG.md @@ -5,6 +5,7 @@ > The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the appropriate changes within that release will be moved into the new section. - __FIX__: Correctly handle unions with nested conditions that have no `possibleTypes` [#4071](https://github.com/apollographql/apollo-server/pull/4071) +- __FIX__: Normalize root operation types when reporting to Apollo Graph Manager. Federation always uses the default names `Query`, `Mutation`, and `Subscription` for root operation types even if downstream services choose different names; now we properly normalize traces received from downstream services in the same way. [#4100](https://github.com/apollographql/apollo-server/pull/4100) ## 0.15.0 diff --git a/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/accounts.ts b/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/accounts.ts index 12dcf6993de..375ea53b5e5 100644 --- a/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/accounts.ts +++ b/packages/apollo-gateway/src/__tests__/__fixtures__/schemas/accounts.ts @@ -6,7 +6,12 @@ export const typeDefs = gql` directive @stream on FIELD directive @transform(from: String!) on FIELD - extend type Query { + schema { + query: RootQuery + mutation: Mutation + } + + extend type RootQuery { user(id: ID!): User me: User } @@ -36,7 +41,7 @@ export const typeDefs = gql` metadata: [UserMetadata] } - extend type Mutation { + type Mutation { login(username: String!, password: String!): User } @@ -80,7 +85,7 @@ const libraryUsers: { [name: string]: string[] } = { }; export const resolvers: GraphQLResolverMap = { - Query: { + RootQuery: { user(_, args) { return { id: args.id }; }, diff --git a/packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts b/packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts index b35c104bb40..8c8cf3f1dc0 100644 --- a/packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts +++ b/packages/apollo-gateway/src/__tests__/buildQueryPlan.test.ts @@ -5,7 +5,7 @@ import { GraphQLSchemaValidationError, } from 'apollo-graphql'; import gql from 'graphql-tag'; -import { composeServices, buildFederatedSchema } from '@apollo/federation'; +import { composeServices, buildFederatedSchema, normalizeTypeDefs } from '@apollo/federation'; import { buildQueryPlan, buildOperationContext } from '../buildQueryPlan'; @@ -45,7 +45,7 @@ describe('buildQueryPlan', () => { ({ schema, errors } = composeServices( Object.entries(serviceMap).map(([serviceName, service]) => ({ name: serviceName, - typeDefs: service.sdl(), + typeDefs: normalizeTypeDefs(service.sdl()), })), )); diff --git a/packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts b/packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts index a75f59eea97..f02aed05d1c 100644 --- a/packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts +++ b/packages/apollo-gateway/src/__tests__/executeQueryPlan.test.ts @@ -9,7 +9,7 @@ import { import gql from 'graphql-tag'; import { GraphQLRequestContext } from 'apollo-server-types'; import { AuthenticationError } from 'apollo-server-core'; -import { composeServices, buildFederatedSchema } from '@apollo/federation'; +import { composeServices, buildFederatedSchema, normalizeTypeDefs } from '@apollo/federation'; import { buildQueryPlan, buildOperationContext } from '../buildQueryPlan'; import { executeQueryPlan } from '../executeQueryPlan'; @@ -60,7 +60,7 @@ describe('executeQueryPlan', () => { ({ schema, errors } = composeServices( Object.entries(serviceMap).map(([serviceName, service]) => ({ name: serviceName, - typeDefs: service.sdl(), + typeDefs: normalizeTypeDefs(service.sdl()), })), )); @@ -104,7 +104,7 @@ describe('executeQueryPlan', () => { it(`should include an error when a root-level field errors out`, async () => { overrideResolversInService('accounts', { - Query: { + RootQuery: { me() { throw new AuthenticationError('Something went wrong'); }, @@ -151,7 +151,7 @@ describe('executeQueryPlan', () => { it(`should still include other root-level results if one root-level field errors out`, async () => { overrideResolversInService('accounts', { - Query: { + RootQuery: { me() { throw new Error('Something went wrong'); }, diff --git a/packages/apollo-gateway/src/executeQueryPlan.ts b/packages/apollo-gateway/src/executeQueryPlan.ts index 72116c8e83d..902c6552902 100644 --- a/packages/apollo-gateway/src/executeQueryPlan.ts +++ b/packages/apollo-gateway/src/executeQueryPlan.ts @@ -12,6 +12,7 @@ import { GraphQLFieldResolver, } from 'graphql'; import { Trace, google } from 'apollo-engine-reporting-protobuf'; +import { defaultRootOperationNameLookup } from '@apollo/federation'; import { GraphQLDataSource } from './datasources/types'; import { FetchNode, @@ -361,6 +362,19 @@ async function executeFetch( traceParsingFailed = true; } } + if (traceNode.trace) { + // Federation requires the root operations in the composed schema + // to have the default names (Query, Mutation, Subscription) even + // if the implementing services choose different names, so we override + // whatever the implementing service reported here. + const rootTypeName = + defaultRootOperationNameLookup[ + context.operationContext.operation.operation + ]; + traceNode.trace.root?.child?.forEach((child) => { + child.parentType = rootTypeName; + }); + } traceNode.traceParsingFailed = traceParsingFailed; } }