Skip to content

Commit

Permalink
Who knows why it doesn't work?!?!!
Browse files Browse the repository at this point in the history
  • Loading branch information
Sashko Stubailo committed Aug 17, 2017
1 parent 5b3fe90 commit e5e368a
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 87 deletions.
33 changes: 25 additions & 8 deletions src/stitching/addSimpleRoutingResolvers.ts
@@ -1,8 +1,17 @@
import { mapValues, isEmpty } from 'lodash';
import { printSchema, print, GraphQLError } from 'graphql';
import { GraphQLFieldResolver, GraphQLSchema, GraphQLInterfaceType, GraphQLUnionType } from 'graphql';
import {
printSchema,
print,
GraphQLError,
GraphQLFieldResolver,
GraphQLSchema,
GraphQLInterfaceType,
GraphQLUnionType,
OperationDefinitionNode,
} from 'graphql';
import { makeExecutableSchema } from '../schemaGenerator';
import { resolveFromParentTypename } from './resolveFromTypename';
import addTypenameForFragments from './addTypenameForFragments';

type ResolverMap = { [key: string]: GraphQLFieldResolver<any, any> };

Expand All @@ -20,14 +29,14 @@ export default function addSimpleRoutingResolvers(
): GraphQLSchema {
const queries = schema.getQueryType().getFields();
const queryResolvers: ResolverMap = mapValues(queries, (field, key) =>
createResolver(fetcher, key),
createResolver(fetcher, key, schema),
);
let mutationResolvers: ResolverMap = {};
const mutationType = schema.getMutationType();
if (mutationType) {
const mutations = mutationType.getFields();
mutationResolvers = mapValues(mutations, (field, key) =>
createResolver(fetcher, key),
createResolver(fetcher, key, schema),
);
}

Expand All @@ -44,11 +53,14 @@ export default function addSimpleRoutingResolvers(

// Add interface and union resolveType functions
const typeMap = schema.getTypeMap();
Object.keys(typeMap).forEach((typeName) => {
Object.keys(typeMap).forEach(typeName => {
const type = typeMap[typeName];

if (type instanceof GraphQLInterfaceType || type instanceof GraphQLUnionType) {
type.resolveType = (parent) => resolveFromParentTypename(parent, schema);
if (
type instanceof GraphQLInterfaceType ||
type instanceof GraphQLUnionType
) {
type.resolveType = parent => resolveFromParentTypename(parent, schema);
}
});

Expand All @@ -61,9 +73,14 @@ export default function addSimpleRoutingResolvers(
function createResolver(
fetcher: Fetcher,
name: string,
schema: GraphQLSchema,
): GraphQLFieldResolver<any, any> {
return async (root, args, context, info) => {
const query = print(info.operation);
// Yo this is not going to work with fragments
const newOperation: OperationDefinitionNode = addTypenameForFragments(info.operation, schema);

const query = print(newOperation);

const result = await fetcher({
query,
variables: info.variableValues,
Expand Down
46 changes: 46 additions & 0 deletions src/stitching/addTypenameForFragments.ts
@@ -0,0 +1,46 @@
import { ASTNode, GraphQLSchema, visit, FieldNode, Kind } from 'graphql';

export default function addTypenameForFragments<T extends ASTNode>(node: T, schema: GraphQLSchema): T {
return visit(node, {
[Kind.FIELD]: {
leave(field: FieldNode) {
if (field.selectionSet) {
const selections = field.selectionSet.selections;

let hasFragment = false;
let hasTypename = false;

selections.forEach((selection) => {
if (selection.kind === Kind.INLINE_FRAGMENT || selection.kind === Kind.FRAGMENT_SPREAD) {
hasFragment = true;
} else if (selection.name.value === '__typename') {
hasTypename = true;
}
});

if (hasFragment && (!hasTypename)) {
return {
...field,
selectionSet: {
...field.selectionSet,
selections: [
...field.selectionSet.selections,
{
kind: 'Field',
name: {
kind: 'Name',
value: '__typename',
}
} as FieldNode
]
}
} as FieldNode;
}

return field;
}
return field;
},
},
});
}
74 changes: 46 additions & 28 deletions src/stitching/mergeSchemas.ts
Expand Up @@ -43,10 +43,13 @@ import {
GraphQLResolveInfo,
GraphQLInterfaceType,
GraphQLUnionType,
TypeInfo,
visitWithTypeInfo
} from 'graphql';
import TypeRegistry from './TypeRegistry';
import { SchemaLink } from './types';
import { resolveFromParentTypename } from './resolveFromTypename';
import addTypenameForFragments from './addTypenameForFragments';

export const ROOT_SCHEMA = '__ROOT__';

Expand Down Expand Up @@ -210,7 +213,11 @@ function recreateCompositeType(
description: type.description,
types: () =>
type.getTypes().map(unionMember => registry.resolveType(unionMember)),
resolveType: parent => resolveFromParentTypename(parent, schema),
resolveType: (parent, context, info) => {
const type = resolveFromParentTypename(parent, schema);
console.log('triple equal', type === schema.getType('Bike'), type === registry.resolveType(type));
return type;
},
});
}
}
Expand Down Expand Up @@ -353,13 +360,17 @@ function createForwardingResolver(
context,
variableValues,
);
// console.log(
// print(document),
// '\n',
// JSON.stringify(variableValues, null, 2),
// '\n',
// JSON.stringify(result, null, 2),
// );

const print = require('graphql').print;
console.log(
'RESULT FROM FORWARDING\n',
print(graphqlDoc),
'\n',
JSON.stringify(variableValues, null, 2),
'\n',
JSON.stringify(result, null, 2),
);

if (result.errors) {
throw new Error(result.errors[0].message);
} else {
Expand Down Expand Up @@ -445,10 +456,13 @@ function createDocument(
selectionSet,
};

return {
const newDoc: DocumentNode = {
kind: Kind.DOCUMENT,
definitions: [operationDefinition, ...processedFragments],
};

const newDocWithTypenames: DocumentNode = addTypenameForFragments(newDoc, schema);
return newDocWithTypenames;
}

function processRootField(
Expand Down Expand Up @@ -519,7 +533,7 @@ function filterSelectionSetDeep(
selectionSet: newSelectionSet,
usedFragments: remainingFragments,
usedVariables,
} = filterSelectionSet(registry, type, selectionSet);
} = filterSelectionSet(registry, type, selectionSet, schema);

const newFragments = {};
// (XXX): So this will break if we have a fragment that only has link fields
Expand All @@ -541,7 +555,7 @@ function filterSelectionSetDeep(
selectionSet: fragmentSelectionSet,
usedFragments: fragmentUsedFragments,
usedVariables: fragmentUsedVariables,
} = filterSelectionSet(registry, innerType, nextFragment.selectionSet);
} = filterSelectionSet(registry, innerType, nextFragment.selectionSet, schema);
remainingFragments = union(remainingFragments, fragmentUsedFragments);
usedVariables = union(usedVariables, fragmentUsedVariables);
newFragments[name] = {
Expand All @@ -566,53 +580,57 @@ function filterSelectionSet(
registry: TypeRegistry,
type: GraphQLType,
selectionSet: SelectionSetNode,
schema: GraphQLSchema
): {
selectionSet: SelectionSetNode;
usedFragments: Array<string>;
usedVariables: Array<string>;
} {
const usedFragments: Array<string> = [];
const usedVariables: Array<string> = [];
const typeStack = [type];
const filteredSelectionSet = visit(selectionSet, {
const typeInfo = new TypeInfo(schema);
const filteredSelectionSet = visit(selectionSet, visitWithTypeInfo(typeInfo, {
[Kind.FIELD]: {
enter(node: FieldNode) {
let parentType = typeStack[typeStack.length - 1];
enter(fieldNode: FieldNode) {
let parentType = typeInfo.getParentType();

if (
parentType instanceof GraphQLNonNull ||
parentType instanceof GraphQLList
) {
parentType = parentType.ofType;
}

if (parentType instanceof GraphQLObjectType) {
const fields = parentType.getFields();
const field = fields[node.name.value];
if (!field) {
const link = registry.getLinkByAddress(
parentType.name,
node.name.value,
);
if (link && link.inlineFragment) {
const link = registry.getLinkByAddress(
parentType.name,
fieldNode.name.value,
);

if (link) {
// This field doesn't exist - it's a fake field created via link
if (link.inlineFragment) {
// If there's fields we need, overwrite with the fragment
return link.inlineFragment;
} else {
// If we don't need anything, remove the field
return null;
}
} else {
typeStack.push(field.type);
return fieldNode;
}
} else {
return fieldNode;
}
},
leave() {
typeStack.pop();
},
},
[Kind.FRAGMENT_SPREAD](node: FragmentSpreadNode) {
usedFragments.push(node.name.value);
},
[Kind.VARIABLE](node: VariableNode) {
usedVariables.push(node.name.value);
},
});
}));

return {
selectionSet: filteredSelectionSet,
Expand Down
50 changes: 13 additions & 37 deletions src/test/testMergeSchemas.ts
Expand Up @@ -225,57 +225,33 @@ query {
});

it('unions', async () => {
(mergedSchema.getType('Bike') as any)._fields.bikeType.resolve = (parent: any) => console.log('CALLED', parent);
const mergedResult = await graphql(
mergedSchema,
`
query {
Booking_customerById(id: "c1") {
... on Person {
name
}
vehicle {
... on Car {
licensePlate
__typename
... on Bike {
bikeType
}
}
}
}
`,
);

console.log(mergedResult);
console.log(JSON.stringify(mergedResult));
expect(mergedResult.errors).to.be.undefined;
expect(mergedResult).to.have.nested.property('data.firstProperty');
expect(mergedResult).to.have.nested.property('data.secondProperty');
expect(mergedResult).to.have.nested.property('data.booking');

expect(mergedResult.data).to.deep.equal({
firstProperty: {
id: 'p2',
name: 'Another great hotel',
bookings: [
{
id: 'b4',
customer: {
name: 'Exampler Customer',
},
},
],
},
secondProperty: {
id: 'p3',
name: 'BedBugs - The Affordable Hostel',
bookings: [],
},
booking: {
id: 'b1',
customer: {
name: 'Exampler Customer',
},

property: {
id: 'p1',
name: 'Super great hotel',
},
},
});
expect(mergedResult).to.have.nested.property('data.Booking_customerById');
expect(mergedResult).to.have.nested.property('data.Booking_customerById.vehicle');
expect(mergedResult).to.not.have.nested.property('data.Booking_customerById.vehicle.licensePlate');
expect(mergedResult).to.have.nested.property('data.Booking_customerById.vehicle.bikeType');
});

it('deep links', async () => {
Expand Down

0 comments on commit e5e368a

Please sign in to comment.