Skip to content
This repository has been archived by the owner on Apr 15, 2020. It is now read-only.

Commit

Permalink
fix(stitching): wrapping and hoisting field transforms
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
Previous version of createMergedResolver did not work with multiple layers of field wrapping.
extractFields was not working, deprecated in favor of hoistFieldNodes.
createMergedResolver now relies on two helper functions, dehoistResult, complement to hoistFieldNoes, and unwrapResult, complement to wrapFieldNodes.
  • Loading branch information
yaacovCR committed Dec 15, 2019
1 parent 55c78e7 commit 9ebad82
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 219 deletions.
86 changes: 20 additions & 66 deletions src/stitching/createMergedResolver.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,35 @@
import { GraphQLObjectType, getNamedType, responsePathAsArray } from 'graphql';
import { IFieldResolver } from '../Interfaces';
import {
getErrors,
getSubschemas,
} from './proxiedResult';
import { unwrapResult, dehoistResult } from './proxiedResult';
import defaultMergedResolver from './defaultMergedResolver';
import { extractOneLevelOfFields } from './extractFields';
import { handleNull, handleObject } from './checkResultAndHandleErrors';

export function wrapField(wrapper: string, fieldName: string): IFieldResolver<any, any> {
return createMergedResolver({ fromPath: [wrapper, fieldName] });
}

export function extractField(fieldName: string): IFieldResolver<any, any> {
return createMergedResolver({ toPath: [fieldName] });
}

export function renameField(fieldName: string): IFieldResolver<any, any> {
return createMergedResolver({ fromPath: [fieldName] });
}

export function createMergedResolver({
fromPath = [],
toPath = [],
fromPath,
fromField,
dehoist,
}: {
fromPath?: Array<string>;
toPath?: Array<string>;
fromField?: string;
dehoist?: string;
}): IFieldResolver<any, any> {
return async (parent, args, context, info) => {

let fieldNodes = info.fieldNodes;
let returnType = info.returnType;
let parentType = info.parentType;
let path = info.path;

toPath.forEach(pathSegment => {
fieldNodes = extractOneLevelOfFields(fieldNodes, pathSegment, info.fragments);
parentType = getNamedType(returnType) as GraphQLObjectType;
returnType = (parentType as GraphQLObjectType).getFields()[pathSegment].type;
path = { prev: path, key: pathSegment };
});

if (!fieldNodes.length) {
return null;
return (parent, args, context, info) => {
if (dehoist) {
parent = dehoistResult(parent, dehoist);
}

let fieldName;

const fromPathLength = fromPath.length;
if (fromPathLength) {
const fromParentPathLength = fromPathLength - 1;

for (let i = 0; i < fromParentPathLength; i++) {
const responseKey = fromPath[i];
const errors = getErrors(parent, responseKey);
const subschemas = getSubschemas(parent);
const result = parent[responseKey];
if (result == null) {
return handleNull(fieldNodes, responsePathAsArray(path), errors);
}
parent = handleObject(result, errors, subschemas);
}

fieldName = fromPath[fromPathLength - 1];
if (fromPath) {
parent = unwrapResult(parent, info, fromPath);
}

if (!fieldName) {
fieldName = toPath[toPath.length - 1];
if (!parent) {
parent = {};
}

return defaultMergedResolver(parent, args, context, {
...info,
fieldName,
fieldNodes,
returnType,
parentType,
path,
});
return parent instanceof Error ?
parent : fromField ?
defaultMergedResolver(parent, args, context, {
...info,
fieldName: fromField,
}) :
defaultMergedResolver(parent, args, context, info);
};
}
34 changes: 0 additions & 34 deletions src/stitching/extractFields.ts

This file was deleted.

14 changes: 4 additions & 10 deletions src/stitching/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import introspectSchema from './introspectSchema';
import mergeSchemas from './mergeSchemas';
import delegateToSchema from './delegateToSchema';
import defaultMergedResolver from './defaultMergedResolver';
import { wrapField, extractField, renameField, createMergedResolver } from './createMergedResolver';
import { extractFields } from './extractFields';

import { createMergedResolver } from './createMergedResolver';
import { dehoistResult, unwrapResult } from './proxiedResult';

export {
makeRemoteExecutableSchema,
Expand All @@ -18,11 +17,6 @@ export {
defaultCreateRemoteResolver,
defaultMergedResolver,
createMergedResolver,
extractFields,

// TBD: deprecate in favor of createMergedResolver?
// OR: fix naming to clarify that these functions return resolvers?
wrapField,
extractField,
renameField,
dehoistResult,
unwrapResult,
};
58 changes: 57 additions & 1 deletion src/stitching/proxiedResult.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {
GraphQLError,
GraphQLSchema,
responsePathAsArray,
} from 'graphql';
import { SubschemaConfig } from '../Interfaces';
import { SubschemaConfig, IGraphQLToolsResolveInfo } from '../Interfaces';
import { handleNull, handleObject } from './checkResultAndHandleErrors';
import { relocatedError } from './errors';

export let SUBSCHEMAS_SYMBOL: any;
export let ERROR_SYMBOL: any;
Expand Down Expand Up @@ -53,3 +56,56 @@ export function getErrors(

return fieldErrors;
}

export function unwrapResult(
parent: any,
info: IGraphQLToolsResolveInfo,
path: Array<string> = []
): any {
const pathLength = path.length;

for (let i = 0; i < pathLength; i++) {
const responseKey = path[i];
const errors = getErrors(parent, responseKey);
const subschemas = getSubschemas(parent);

const result = parent[responseKey];
if (result == null) {
return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors);
}
parent = handleObject(result, errors, subschemas);
}

return parent;
}

export function dehoistResult(parent: any, delimeter: string): any {
const result = Object.create(null);

Object.keys(parent).forEach(alias => {
let obj = result;

const fieldNames = alias.split(delimeter);
const fieldName = fieldNames.pop();
fieldNames.forEach(key => {
obj = obj[key] = obj[key] || Object.create(null);
});
obj[fieldName] = parent[alias];

});

result[ERROR_SYMBOL] = parent[ERROR_SYMBOL].map((error: GraphQLError) => {
if (error.path) {
let path = error.path.slice();
const pathSegment = path.shift();
const expandedPathSegment: Array<string | number> = (pathSegment as string).split(delimeter);
return relocatedError(error, error.nodes, expandedPathSegment.concat(path));
} else {
return error;
}
});

result[SUBSCHEMAS_SYMBOL] = parent[SUBSCHEMAS_SYMBOL];

return result;
}

0 comments on commit 9ebad82

Please sign in to comment.