Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bare request delegation #1479

Merged
merged 4 commits into from
May 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/delegate/src/createRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function createRequestFromInfo({
operation = getDelegatingOperation(info.parentType, info.schema),
fieldName = info.fieldName,
selectionSet,
fieldNodes,
fieldNodes = info.fieldNodes,
}: ICreateRequestFromInfo): Request {
return createRequest({
sourceSchema: info.schema,
Expand All @@ -47,7 +47,7 @@ export function createRequestFromInfo({
targetOperation: operation,
targetFieldName: fieldName,
selectionSet,
fieldNodes: selectionSet != null ? undefined : fieldNodes != null ? fieldNodes : info.fieldNodes,
fieldNodes,
});
}

Expand All @@ -66,7 +66,7 @@ export function createRequest({
let newSelectionSet: SelectionSetNode = selectionSet;
let argumentNodeMap: Record<string, ArgumentNode>;

if (selectionSet != null && fieldNodes == null) {
if (fieldNodes == null) {
argumentNodeMap = Object.create(null);
} else {
const selections: Array<SelectionNode> = fieldNodes.reduce(
Expand Down
79 changes: 63 additions & 16 deletions packages/delegate/src/delegateToSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import {
GraphQLOutputType,
isSchema,
GraphQLResolveInfo,
FieldDefinitionNode,
getOperationAST,
OperationTypeNode,
GraphQLObjectType,
OperationDefinitionNode,
} from 'graphql';

import {
Expand Down Expand Up @@ -68,6 +73,28 @@ export function delegateToSchema(options: IDelegateToSchemaOptions | GraphQLSche
});
}

function getDelegationReturnType(
info: GraphQLResolveInfo,
targetSchema: GraphQLSchema,
operation: OperationTypeNode,
fieldName: string
): GraphQLOutputType {
if (info != null) {
return info.returnType;
}

let rootType: GraphQLObjectType<any, any>;
if (operation === 'query') {
rootType = targetSchema.getQueryType();
} else if (operation === 'mutation') {
rootType = targetSchema.getMutationType();
} else {
rootType = targetSchema.getSubscriptionType();
}

return rootType.getFields()[fieldName].type;
}

function buildDelegationTransforms(
subschemaOrSubschemaConfig: GraphQLSchema | SubschemaConfig,
info: GraphQLResolveInfo,
Expand All @@ -84,24 +111,27 @@ function buildDelegationTransforms(
new CheckResultAndHandleErrors(info, fieldName, subschemaOrSubschemaConfig, context, returnType, skipTypeMerging),
];

if (info.mergeInfo != null) {
if (info?.mergeInfo != null) {
delegationTransforms.push(
new AddReplacementSelectionSets(info.schema, info.mergeInfo.replacementSelectionSets),
new AddMergedTypeSelectionSets(info.schema, info.mergeInfo.mergedTypes)
);
}

const transformedTargetSchema =
info.mergeInfo == null
info?.mergeInfo == null
? transformedSchema ?? targetSchema
: transformedSchema ?? info.mergeInfo.transformedSchemas.get(subschemaOrSubschemaConfig) ?? targetSchema;

delegationTransforms.push(new WrapConcreteTypes(returnType, transformedTargetSchema));
delegationTransforms.push(new ExpandAbstractTypes(info.schema, transformedTargetSchema));

if (info != null) {
delegationTransforms.push(new ExpandAbstractTypes(info.schema, transformedTargetSchema));
}

delegationTransforms = delegationTransforms.concat(transforms);

if (info.mergeInfo != null) {
if (info?.mergeInfo != null) {
delegationTransforms.push(new AddReplacementFragments(targetSchema, info.mergeInfo.replacementFragments));
}

Expand All @@ -119,16 +149,34 @@ export function delegateRequest({
schema: subschemaOrSubschemaConfig,
rootValue,
info,
operation = getDelegatingOperation(info.parentType, info.schema),
fieldName = info.fieldName,
operation,
fieldName,
args,
returnType = info.returnType,
returnType,
context,
transforms = [],
transformedSchema,
skipValidation,
skipTypeMerging,
}: IDelegateRequestOptions) {
let operationDefinition: OperationDefinitionNode;
let targetOperation: OperationTypeNode;
let targetFieldName: string;

if (operation == null) {
operationDefinition = getOperationAST(request.document, undefined);
targetOperation = operationDefinition.operation;
} else {
targetOperation = operation;
}

if (fieldName == null) {
operationDefinition = operationDefinition ?? getOperationAST(request.document, undefined);
targetFieldName = ((operationDefinition.selectionSet.selections[0] as unknown) as FieldDefinitionNode).name.value;
} else {
targetFieldName = fieldName;
}

let targetSchema: GraphQLSchema;
let targetRootValue: Record<string, any>;
let requestTransforms: Array<Transform> = transforms.slice();
Expand All @@ -137,24 +185,23 @@ export function delegateRequest({
if (isSubschemaConfig(subschemaOrSubschemaConfig)) {
subschemaConfig = subschemaOrSubschemaConfig;
targetSchema = subschemaConfig.schema;
targetRootValue =
rootValue != null ? rootValue : subschemaConfig.rootValue != null ? subschemaConfig.rootValue : info.rootValue;
targetRootValue = rootValue ?? subschemaConfig?.rootValue ?? info?.rootValue;
if (subschemaConfig.transforms != null) {
requestTransforms = requestTransforms.concat(subschemaConfig.transforms);
}
} else {
targetSchema = subschemaOrSubschemaConfig;
targetRootValue = rootValue != null ? rootValue : info.rootValue;
targetRootValue = rootValue ?? info?.rootValue;
}

const delegationTransforms = buildDelegationTransforms(
subschemaOrSubschemaConfig,
info,
context,
targetSchema,
fieldName,
targetFieldName,
args,
returnType,
returnType ?? getDelegationReturnType(info, targetSchema, targetOperation, targetFieldName),
requestTransforms.reverse(),
transformedSchema,
skipTypeMerging
Expand All @@ -174,7 +221,7 @@ export function delegateRequest({
}
}

if (operation === 'query' || operation === 'mutation') {
if (targetOperation === 'query' || targetOperation === 'mutation') {
const executor =
subschemaConfig?.executor || createDefaultExecutor(targetSchema, subschemaConfig?.rootValue || targetRootValue);

Expand Down Expand Up @@ -209,7 +256,7 @@ export function delegateRequest({
// wrap with fieldName to return for an additional round of resolutioon
// with payload as rootValue
return {
[info.fieldName]: transformedResult,
[targetFieldName]: transformedResult,
};
}
);
Expand All @@ -221,10 +268,10 @@ export function delegateRequest({

function createDefaultExecutor(schema: GraphQLSchema, rootValue: Record<string, any>) {
return ({ document, context, variables, info }: ExecutionParams) =>
execute(schema, document, rootValue || info.rootValue, context, variables);
execute(schema, document, rootValue ?? info?.rootValue, context, variables);
}

function createDefaultSubscriber(schema: GraphQLSchema, rootValue: Record<string, any>) {
return ({ document, context, variables, info }: ExecutionParams) =>
subscribe(schema, document, rootValue || info.rootValue, context, variables) as any;
subscribe(schema, document, rootValue ?? info?.rootValue, context, variables) as any;
}
3 changes: 2 additions & 1 deletion packages/delegate/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ export interface IDelegateToSchemaOptions<TContext = Record<string, any>, TArgs
skipTypeMerging?: boolean;
}

export interface IDelegateRequestOptions extends IDelegateToSchemaOptions {
export interface IDelegateRequestOptions extends Omit<IDelegateToSchemaOptions, 'info'> {
request: Request;
info?: GraphQLResolveInfo;
}

export interface ICreateRequestFromInfo {
Expand Down
122 changes: 122 additions & 0 deletions packages/delegate/tests/createRequest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { graphql, Kind } from 'graphql';

import { createRequest } from '../src/createRequest';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { delegateRequest } from '../src/delegateToSchema';

describe('bare requests', () => {
test('should work', async () => {
const innerSchema = makeExecutableSchema({
typeDefs: `
type Query {
test(input: String): String
}
`,
resolvers: {
Query: {
test: (_root, args) => args.input
},
},
});

const outerSchema = makeExecutableSchema({
typeDefs: `
type Query {
delegate(input: String): String
}
`,
resolvers: {
Query: {
delegate: (_root, args) => {
const request = createRequest({
fieldNodes: [{
kind: Kind.FIELD,
name: {
kind: Kind.NAME,
value: 'delegate',
},
arguments: [{
kind: Kind.ARGUMENT,
name: {
kind: Kind.NAME,
value: 'input',
},
value: {
kind: Kind.STRING,
value: args.input,
}
}]
}],
targetOperation: 'query',
targetFieldName: 'test',
});
return delegateRequest({
request,
schema: innerSchema,
});
},
},
},
});

const result = await graphql(
outerSchema,
`
query {
delegate(input: "test")
}
`,
);

expect(result.data.delegate).toEqual('test');
});

test('should work with adding args on delegation', async () => {
const innerSchema = makeExecutableSchema({
typeDefs: `
type Query {
test(input: String): String
}
`,
resolvers: {
Query: {
test: (_root, args) => args.input
},
},
});

const outerSchema = makeExecutableSchema({
typeDefs: `
type Query {
delegate(input: String): String
}
`,
resolvers: {
Query: {
delegate: (_root, args) => {
const request = createRequest({
targetOperation: 'query',
targetFieldName: 'test',
});
return delegateRequest({
request,
schema: innerSchema,
args,
});
},
},
},
});

const result = await graphql(
outerSchema,
`
query {
delegate(input: "test")
}
`,
);

expect(result.data.delegate).toEqual('test');
});
});
Loading