Skip to content

Commit

Permalink
fix(remote schemas): must add __typename to remote query to properly …
Browse files Browse the repository at this point in the history
…resolve interfaces.

Error will occur only when querying directly on the remote schema, as delegateToSchema automatically adds type names.

addTypenameToAbstract is necessary for resolveType to work in the parent schema just as checkResultAndHandleErrors is necessary for resolve to work!
  • Loading branch information
yaacovCR committed Sep 24, 2019
1 parent 98b0986 commit db9307d
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 62 deletions.
56 changes: 56 additions & 0 deletions src/stitching/addTypenameToAbstract.ts
@@ -0,0 +1,56 @@
import {
GraphQLType,
DocumentNode,
TypeInfo,
visit,
visitWithTypeInfo,
SelectionSetNode,
Kind,
FieldNode,
GraphQLInterfaceType,
GraphQLUnionType,
} from 'graphql';
import { GraphQLSchemaWithTransforms } from '../Interfaces';

export function addTypenameToAbstract(
targetSchema: GraphQLSchemaWithTransforms,
document: DocumentNode,
): DocumentNode {
const typeInfo = new TypeInfo(targetSchema);
return visit(
document,
visitWithTypeInfo(typeInfo, {
[Kind.SELECTION_SET](
node: SelectionSetNode,
): SelectionSetNode | null | undefined {
const parentType: GraphQLType = typeInfo.getParentType();
let selections = node.selections;
if (
parentType &&
(parentType instanceof GraphQLInterfaceType ||
parentType instanceof GraphQLUnionType) &&
!selections.find(
_ =>
(_ as FieldNode).kind === Kind.FIELD &&
(_ as FieldNode).name.value === '__typename',
)
) {
selections = selections.concat({
kind: Kind.FIELD,
name: {
kind: Kind.NAME,
value: '__typename',
},
});
}

if (selections !== node.selections) {
return {
...node,
selections,
};
}
},
}),
);
}
17 changes: 12 additions & 5 deletions src/stitching/makeRemoteExecutableSchema.ts
Expand Up @@ -8,10 +8,12 @@ import {
buildSchema,
Kind,
GraphQLResolveInfo,
BuildSchemaOptions
BuildSchemaOptions,
DocumentNode,
} from 'graphql';
import linkToFetcher, { execute } from './linkToFetcher';
import { Fetcher, Operation } from '../Interfaces';
import { addTypenameToAbstract } from './addTypenameToAbstract';
import { checkResultAndHandleErrors } from './checkResultAndHandleErrors';
import { observableToAsyncIterable } from './observableToAsyncIterable';
import mapAsyncIterator from './mapAsyncIterator';
Expand Down Expand Up @@ -79,12 +81,15 @@ export default function makeRemoteExecutableSchema({
export function createResolver(fetcher: Fetcher): GraphQLFieldResolver<any, any> {
return async (root, args, context, info) => {
const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]);
const document = {
let query: DocumentNode = {
kind: Kind.DOCUMENT,
definitions: [info.operation, ...fragments]
};

query = addTypenameToAbstract(info.schema, query);

const result = await fetcher({
query: document,
query,
variables: info.variableValues,
context: { graphqlContext: context }
});
Expand All @@ -95,13 +100,15 @@ export function createResolver(fetcher: Fetcher): GraphQLFieldResolver<any, any>
function createSubscriptionResolver(link: ApolloLink): ResolverFn {
return (root, args, context, info) => {
const fragments = Object.keys(info.fragments).map(fragment => info.fragments[fragment]);
const document = {
let query: DocumentNode = {
kind: Kind.DOCUMENT,
definitions: [info.operation, ...fragments]
};

query = addTypenameToAbstract(info.schema, query);

const operation = {
query: document,
query,
variables: info.variableValues,
context: { graphqlContext: context }
};
Expand Down
44 changes: 43 additions & 1 deletion src/test/testMakeRemoteExecutableSchema.ts
Expand Up @@ -2,15 +2,57 @@

import { expect } from 'chai';
import { forAwaitEach } from 'iterall';
import { GraphQLSchema, ExecutionResult, subscribe, parse } from 'graphql';
import { GraphQLSchema, ExecutionResult, subscribe, parse, graphql } from 'graphql';
import {
propertySchema,
subscriptionSchema,
subscriptionPubSubTrigger,
subscriptionPubSub,
makeSchemaRemoteFromLink
} from '../test/testingSchemas';
import { makeRemoteExecutableSchema } from '../stitching';

describe('remote queries', () => {
let schema: GraphQLSchema;
before(async () => {
const remoteSchemaExecConfig = await makeSchemaRemoteFromLink(propertySchema);
schema = makeRemoteExecutableSchema({
schema: remoteSchemaExecConfig.schema,
link: remoteSchemaExecConfig.link
});
});

it('should work', async () => {
const query = `
{
interfaceTest(kind: ONE) {
kind
testString
...on TestImpl1 {
foo
}
...on TestImpl2 {
bar
}
}
}
`;

const expected = {
data: {
interfaceTest: {
foo: 'foo',
kind: 'ONE',
testString: 'test',
},
},
};

const result = await graphql(schema, query);
expect(result).to.deep.equal(expected);
});
});

describe('remote subscriptions', () => {
let schema: GraphQLSchema;
before(async () => {
Expand Down
58 changes: 2 additions & 56 deletions src/transforms/AddTypenameToAbstract.ts
@@ -1,18 +1,7 @@
import {
DocumentNode,
FieldNode,
GraphQLInterfaceType,
GraphQLSchema,
GraphQLType,
GraphQLUnionType,
Kind,
SelectionSetNode,
TypeInfo,
visit,
visitWithTypeInfo,
} from 'graphql';
import { GraphQLSchema } from 'graphql';
import { Request } from '../Interfaces';
import { Transform } from './transforms';
import { addTypenameToAbstract } from '../stitching/addTypenameToAbstract';

export default class AddTypenameToAbstract implements Transform {
private targetSchema: GraphQLSchema;
Expand All @@ -32,46 +21,3 @@ export default class AddTypenameToAbstract implements Transform {
};
}
}

function addTypenameToAbstract(
targetSchema: GraphQLSchema,
document: DocumentNode,
): DocumentNode {
const typeInfo = new TypeInfo(targetSchema);
return visit(
document,
visitWithTypeInfo(typeInfo, {
[Kind.SELECTION_SET](
node: SelectionSetNode,
): SelectionSetNode | null | undefined {
const parentType: GraphQLType = typeInfo.getParentType();
let selections = node.selections;
if (
parentType &&
(parentType instanceof GraphQLInterfaceType ||
parentType instanceof GraphQLUnionType) &&
!selections.find(
_ =>
(_ as FieldNode).kind === Kind.FIELD &&
(_ as FieldNode).name.value === '__typename',
)
) {
selections = selections.concat({
kind: Kind.FIELD,
name: {
kind: Kind.NAME,
value: '__typename',
},
});
}

if (selections !== node.selections) {
return {
...node,
selections,
};
}
},
}),
);
}

0 comments on commit db9307d

Please sign in to comment.