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

Commit

Permalink
fix(wrapping): wrapped field names must be unique.
Browse files Browse the repository at this point in the history
...so they can be combined from multiple schemas.

This change also adds use of a delimeter when wrapping fields and fixes specification of the delimeter more broadly, which was not set up appropriately within the dehoist property.

The delimeter can now be specified when using the WrapFields and WrapType transforms.

This change also removes the now unnecessary fromField property, as renaming no longer requires a dedicated resolver now that it just uses aliases.
  • Loading branch information
yaacovCR committed Jan 5, 2020
1 parent 869a0fd commit 98601f7
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 30 deletions.
11 changes: 5 additions & 6 deletions src/stitching/createMergedResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,23 @@ import defaultMergedResolver from './defaultMergedResolver';

export function createMergedResolver({
fromPath,
fromField,
dehoist,
delimeter = '__gqltf__',
}: {
fromPath?: Array<string>;
fromField?: string;
dehoist?: boolean | string;
dehoist?: boolean;
delimeter?: string;
}): IFieldResolver<any, any> {
const parentErrorResolver: IFieldResolver<any, any> =
(parent, args, context, info) => parent instanceof Error ?
parent :
defaultMergedResolver(parent, args, context, info);

const unwrappingResolver: IFieldResolver<any, any> = fromPath ?
const unwrappingResolver: IFieldResolver<any, any> = fromPath && fromPath.length ?
(parent, args, context, info) =>
parentErrorResolver(unwrapResult(parent, info, fromPath), args, context, info) :
parentErrorResolver(unwrapResult(parent, info, fromPath, delimeter), args, context, info) :
parentErrorResolver;

const delimeter = dehoist === 'string' ? dehoist : '__gqltf__';
const dehoistingResolver: IFieldResolver<any, any> = dehoist ?
(parent, args, context, info) =>
unwrappingResolver(dehoistResult(parent, delimeter), args, context, info) :
Expand Down
32 changes: 21 additions & 11 deletions src/stitching/proxiedResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,16 @@ export function getErrors(
export function unwrapResult(
parent: any,
info: IGraphQLToolsResolveInfo,
path: Array<string> = []
path: Array<string>,
delimeter: string = '__gqltf__',
): any {
const pathLength = path.length;
let responseKey = Object.keys(parent).find(key => {
const splitKey = key.split(delimeter);
return (splitKey.length === 3 && splitKey[0] === 'wrapped' && splitKey[2] === path[0]);
});

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

Expand All @@ -74,32 +78,38 @@ export function unwrapResult(
return handleNull(info.fieldNodes, responsePathAsArray(info.path), errors);
}
parent = handleObject(result, errors, subschemas);
responseKey = path[i + 1];
}

return parent;
}

export function dehoistResult(parent: any, delimeter: string): any {
export function dehoistResult(parent: any, delimeter: string = '__gqltf__'): 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];

const prefix = fieldNames.shift();
if (prefix === 'hoisted') {
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));
const prefix = expandedPathSegment.shift();
return (prefix === 'hoisted') ?
relocatedError(error, error.nodes, expandedPathSegment.concat(path)) :
error;
} else {
return error;
}
Expand Down
8 changes: 1 addition & 7 deletions src/test/testAlternateMergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -880,8 +880,7 @@ describe('schema transformation with extraction of nested fields', () => {
`,
resolvers: {
Property: {
locationName: createMergedResolver({ fromPath: ['location'], fromField: 'name' }),
renamedError: createMergedResolver({ fromField: 'error' }),
locationName: createMergedResolver({ fromPath: ['location'] }),
},
},
fieldNodeTransformerMap: {
Expand Down Expand Up @@ -1209,11 +1208,6 @@ describe('schema transformation with renaming of object fields', () => {
new_error: String
}
`,
resolvers: {
Property: {
new_error: createMergedResolver({ fromField: 'error' }),
},
},
fieldNodeTransformerMap: {
'Property': {
'new_error': fieldNode => renameFieldNode(fieldNode, 'error'),
Expand Down
6 changes: 5 additions & 1 deletion src/transforms/WrapFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,22 @@ export default class WrapFields implements Transform {
private wrappingTypeNames: Array<string>;
private numWraps: number;
private fieldNames: Array<string>;
private delimeter: string;
private transformer: Transform;

constructor(
outerTypeName: string,
wrappingFieldNames: Array<string>,
wrappingTypeNames: Array<string>,
fieldNames?: Array<string>,
delimeter: string = '__gqltf__',
) {
this.outerTypeName = outerTypeName;
this.wrappingFieldNames = wrappingFieldNames;
this.wrappingTypeNames = wrappingTypeNames;
this.numWraps = wrappingFieldNames.length;
this.fieldNames = fieldNames;
this.delimeter = delimeter;

const remainingWrappingFieldNames = this.wrappingFieldNames.slice();
const outerMostWrappingFieldName = remainingWrappingFieldNames.shift();
Expand All @@ -43,6 +46,7 @@ export default class WrapFields implements Transform {
path: remainingWrappingFieldNames,
fieldNames: this.fieldNames,
fragments,
delimeter: this.delimeter,
}),
},
});
Expand Down Expand Up @@ -74,7 +78,7 @@ export default class WrapFields implements Transform {
this.appendFields(typeMap, this.outerTypeName, {
[this.wrappingFieldNames[0]]: {
type: typeMap[this.wrappingTypeNames[0]] as GraphQLObjectType,
resolve: createMergedResolver({ dehoist: true }),
resolve: createMergedResolver({ dehoist: true, delimeter: this.delimeter }),
},
});

Expand Down
11 changes: 9 additions & 2 deletions src/transforms/WrapType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,16 @@ export default class WrapType implements Transform {
constructor(
outerTypeName: string,
innerTypeName: string,
fieldName: string
fieldName: string,
delimeter: string = '__gqltf__',
) {
this.transformer = new WrapFields(outerTypeName, [fieldName], [innerTypeName]);
this.transformer = new WrapFields(
outerTypeName,
[fieldName],
[innerTypeName],
undefined,
delimeter
);
}

public transformSchema(schema: GraphQLSchema): GraphQLSchema {
Expand Down
13 changes: 10 additions & 3 deletions src/utils/fieldNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ export function preAliasFieldNode(fieldNode: FieldNode, str: string): FieldNode
};
}

export function wrapFieldNode(fieldNode: FieldNode, path: Array<string>): FieldNode {
export function wrapFieldNode(
fieldNode: FieldNode,
path: Array<string>,
delimeter: string = '__gqltf__',
): FieldNode {
const alias = fieldNode.alias ? fieldNode.alias.value : fieldNode.name.value;

let newFieldNode = fieldNode;
path.forEach(fieldName => {
newFieldNode = {
Expand All @@ -46,7 +52,8 @@ export function wrapFieldNode(fieldNode: FieldNode, path: Array<string>): FieldN
}
};
});
return newFieldNode;

return preAliasFieldNode(newFieldNode, `wrapped${delimeter}${alias}__gqltf__`);
}

export function collectFields(
Expand Down Expand Up @@ -122,7 +129,7 @@ export function hoistFieldNodes({
} else {
collectFields(fieldNode.selectionSet, fragments).forEach((possibleFieldNode: FieldNode) => {
if (!fieldNames || fieldNames.includes(possibleFieldNode.name.value)) {
newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `${alias}${delimeter}`));
newFieldNodes.push(preAliasFieldNode(possibleFieldNode, `hoisted${delimeter}${alias}${delimeter}`));
}
});
}
Expand Down

0 comments on commit 98601f7

Please sign in to comment.