diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 95a9ff4ea51..fa5adc8baa1 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -46,13 +46,17 @@ export type Transform = { transformResult?: (result: Result) => Result; }; +export interface IGraphQLToolsResolveInfo extends GraphQLResolveInfo { + mergeInfo?: MergeInfo; +} + export interface IDelegateToSchemaOptions { schema: GraphQLSchema; operation: Operation; fieldName: string; args?: { [key: string]: any }; context: TContext; - info: GraphQLResolveInfo; + info: IGraphQLToolsResolveInfo; transforms?: Array; skipValidation?: boolean; } @@ -67,6 +71,10 @@ export type MergeInfo = { transforms?: Array, ) => any; delegateToSchema(options: IDelegateToSchemaOptions): any; + fragments: Array<{ + field: string; + fragment: string; + }>; }; export type IFieldResolver = ( diff --git a/src/stitching/delegateToSchema.ts b/src/stitching/delegateToSchema.ts index 8f8bbdf48c1..add8a0efebd 100644 --- a/src/stitching/delegateToSchema.ts +++ b/src/stitching/delegateToSchema.ts @@ -2,8 +2,8 @@ import { ArgumentNode, DocumentNode, FieldNode, - FragmentDefinitionNode, Kind, + FragmentDefinitionNode, OperationDefinitionNode, SelectionSetNode, SelectionNode, @@ -30,6 +30,8 @@ import AddArgumentsAsVariables from '../transforms/AddArgumentsAsVariables'; import FilterToSchema from '../transforms/FilterToSchema'; import AddTypenameToAbstract from '../transforms/AddTypenameToAbstract'; import CheckResultAndHandleErrors from '../transforms/CheckResultAndHandleErrors'; +import ExpandAbstractTypes from '../transforms/ExpandAbstractTypes'; +import ReplaceFieldWithFragment from '../transforms/ReplaceFieldWithFragment'; export default function delegateToSchema( options: IDelegateToSchemaOptions | GraphQLSchema, @@ -65,13 +67,23 @@ async function delegateToSchemaImplementation( variables: info.variableValues as Record, }; - const transforms = [ + let transforms = [ ...(options.transforms || []), + new ExpandAbstractTypes(info.schema, options.schema) + ]; + + if (info.mergeInfo && info.mergeInfo.fragments) { + transforms.push( + new ReplaceFieldWithFragment(options.schema, info.mergeInfo.fragments) + ); + } + + transforms = transforms.concat([ new AddArgumentsAsVariables(options.schema, args), new FilterToSchema(options.schema), new AddTypenameToAbstract(options.schema), - new CheckResultAndHandleErrors(info, options.fieldName), - ]; + new CheckResultAndHandleErrors(info, options.fieldName) + ]); const processedRequest = applyRequestTransforms(rawRequest, transforms); diff --git a/src/stitching/mergeSchemas.ts b/src/stitching/mergeSchemas.ts index e8e2d3dc14f..402c7905a62 100644 --- a/src/stitching/mergeSchemas.ts +++ b/src/stitching/mergeSchemas.ts @@ -353,13 +353,10 @@ function createMergeInfo( delegateToSchema(options: IDelegateToSchemaOptions) { return delegateToSchema({ ...options, - transforms: [ - ...(options.transforms || []), - new ExpandAbstractTypes(options.info.schema, options.schema), - new ReplaceFieldWithFragment(options.schema, fragments), - ], + transforms: options.transforms }); }, + fragments }; } diff --git a/src/test/testDelegateToSchema.ts b/src/test/testDelegateToSchema.ts new file mode 100644 index 00000000000..0ff3ecb9b99 --- /dev/null +++ b/src/test/testDelegateToSchema.ts @@ -0,0 +1,108 @@ +/* tslint:disable:no-unused-expression */ + +import { expect } from 'chai'; +import { + GraphQLSchema, + graphql +} from 'graphql'; +import { propertySchema, bookingSchema, sampleData, Property } from './testingSchemas'; +import delegateToSchema from '../stitching/delegateToSchema'; +import mergeSchemas from '../stitching/mergeSchemas'; +import { IResolvers } from '../Interfaces'; + +function findPropertyByLocationName ( + properties: { [key: string]: Property }, + name: string +): Property { + for (const key of Object.keys(properties)) { + const property = properties[key]; + if (property.location.name === name) { + return property; + } + } +} + +const COORDINATES_QUERY = ` + query BookingCoordinates($bookingId: ID!) { + bookingById (id: $bookingId) { + property { + location { + coordinates + } + } + } + } +`; + +function proxyResolvers (spec: string): IResolvers { + return { + Booking: { + property: { + fragment: '... on Booking { propertyId }', + resolve (booking, args, context, info) { + const delegateFn = spec === 'standalone' ? delegateToSchema : + info.mergeInfo.delegateToSchema; + return delegateFn({ + schema: propertySchema, + operation: 'query', + fieldName: 'propertyById', + args: { id: booking.propertyId }, + context, + info + }); + } + } + }, + Location: { + coordinates: { + fragment: '... on Location { name }', + resolve (location, args, context, info) { + const name = location.name; + return findPropertyByLocationName(sampleData.Property, name) + .location.coordinates; + } + } + } + }; +} + +const proxyTypeDefs = ` + extend type Booking { + property: Property! + } + extend type Location { + coordinates: String! + } +`; + +describe('stitching', () => { + describe('delegateToSchema', () => { + ['standalone', 'info.mergeInfo'].forEach(spec => { + context(spec, () => { + let schema: GraphQLSchema; + before(() => { + schema = mergeSchemas({ + schemas: [bookingSchema, propertySchema, proxyTypeDefs], + resolvers: proxyResolvers(spec) + }); + }); + it('should add fragments for deep types', async () => { + const result = await graphql(schema, COORDINATES_QUERY, + {}, {}, { bookingId: 'b1' }); + + expect(result).to.deep.equal({ + data: { + bookingById: { + property: { + location: { + coordinates: sampleData.Property.p1.location.coordinates + } + } + } + } + }); + }); + }); + }); + }); +}); diff --git a/src/test/testingSchemas.ts b/src/test/testingSchemas.ts index eb1c5346c95..af8155a86a4 100644 --- a/src/test/testingSchemas.ts +++ b/src/test/testingSchemas.ts @@ -22,12 +22,15 @@ import makeRemoteExecutableSchema, { import introspectSchema from '../stitching/introspectSchema'; import { PubSub } from 'graphql-subscriptions'; +export type Location = { + name: string; + coordinates: string; +}; + export type Property = { id: string; name: string; - location: { - name: string; - }; + location: Location; }; export type Product = { @@ -84,6 +87,7 @@ export const sampleData: { name: 'Super great hotel', location: { name: 'Helsinki', + coordinates: '60.1698° N, 24.9383° E' }, }, p2: { @@ -91,6 +95,7 @@ export const sampleData: { name: 'Another great hotel', location: { name: 'San Francisco', + coordinates: '37.7749° N, 122.4194° W' }, }, p3: { @@ -98,6 +103,7 @@ export const sampleData: { name: 'BedBugs - The Affordable Hostel', location: { name: 'Helsinki', + coordinates: '60.1699° N, 24.9384° E' }, }, }, diff --git a/src/test/tests.ts b/src/test/tests.ts index fb83fc64665..82a85b0038a 100755 --- a/src/test/tests.ts +++ b/src/test/tests.ts @@ -10,3 +10,4 @@ import './testTransforms'; import './testAlternateMergeSchemas'; import './testErrors'; import './testDirectives'; +import './testDelegateToSchema';