Skip to content

Commit

Permalink
Add result transforming to bundled transformers
Browse files Browse the repository at this point in the history
Adds result wrapping capability to the following generic transformers

= TransformCompositeFields
= TransformInterfaceFields
= TransformObjectFields
= TransformRootFields
= ExtendSchema
= MapFields

Adds result wrapping usage to the following transfromers
= WrapFields
= WrapType
= HoistField
  • Loading branch information
yaacovCR committed Jul 3, 2020
1 parent 0d5a635 commit 4f1ea0f
Show file tree
Hide file tree
Showing 11 changed files with 551 additions and 77 deletions.
3 changes: 2 additions & 1 deletion packages/utils/src/Interfaces.ts
Expand Up @@ -153,7 +153,8 @@ export interface Transform {

export type FieldNodeMapper = (
fieldNode: FieldNode,
fragments: Record<string, FragmentDefinitionNode>
fragments: Record<string, FragmentDefinitionNode>,
context: Record<string, any>
) => SelectionNode | Array<SelectionNode>;

export type FieldNodeMappers = Record<string, Record<string, FieldNodeMapper>>;
Expand Down
37 changes: 33 additions & 4 deletions packages/wrap/src/transforms/ExtendSchema.ts
@@ -1,9 +1,18 @@
import { GraphQLSchema, extendSchema, parse } from 'graphql';

import { Transform, IFieldResolver, IResolvers, Request, FieldNodeMappers } from '@graphql-tools/utils';
import {
Transform,
IFieldResolver,
IResolvers,
Request,
FieldNodeMappers,
ExecutionResult,
} from '@graphql-tools/utils';
import { addResolversToSchema } from '@graphql-tools/schema';
import { defaultMergedResolver } from '@graphql-tools/delegate';

import { ObjectValueTransformerMap, ErrorsTransformer } from '../types';

import MapFields from './MapFields';

export default class ExtendSchema implements Transform {
Expand All @@ -17,16 +26,24 @@ export default class ExtendSchema implements Transform {
resolvers = {},
defaultFieldResolver,
fieldNodeTransformerMap,
objectValueTransformerMap,
errorsTransformer,
}: {
typeDefs?: string;
resolvers?: IResolvers;
defaultFieldResolver?: IFieldResolver<any, any>;
fieldNodeTransformerMap?: FieldNodeMappers;
objectValueTransformerMap?: ObjectValueTransformerMap;
errorsTransformer?: ErrorsTransformer;
}) {
this.typeDefs = typeDefs;
this.resolvers = resolvers;
this.defaultFieldResolver = defaultFieldResolver != null ? defaultFieldResolver : defaultMergedResolver;
this.transformer = new MapFields(fieldNodeTransformerMap != null ? fieldNodeTransformerMap : {});
this.transformer = new MapFields(
fieldNodeTransformerMap != null ? fieldNodeTransformerMap : {},
objectValueTransformerMap,
errorsTransformer
);
}

public transformSchema(schema: GraphQLSchema): GraphQLSchema {
Expand All @@ -41,7 +58,19 @@ export default class ExtendSchema implements Transform {
});
}

public transformRequest(originalRequest: Request): Request {
return this.transformer.transformRequest(originalRequest);
public transformRequest(
originalRequest: Request,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): Request {
return this.transformer.transformRequest(originalRequest, delegationContext, transformationContext);
}

public transformResult(
originalResult: ExecutionResult,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): ExecutionResult {
return this.transformer.transformResult(originalResult, delegationContext, transformationContext);
}
}
106 changes: 91 additions & 15 deletions packages/wrap/src/transforms/HoistField.ts
@@ -1,37 +1,46 @@
import { GraphQLSchema, GraphQLObjectType, getNullableType } from 'graphql';
import { GraphQLSchema, GraphQLObjectType, getNullableType, FieldNode, Kind, GraphQLError } from 'graphql';

import {
wrapFieldNode,
renameFieldNode,
appendObjectFields,
removeObjectFields,
Transform,
Request,
ExecutionResult,
relocatedError,
} from '@graphql-tools/utils';

import { defaultMergedResolver } from '@graphql-tools/delegate';

import MapFields from './MapFields';
import { createMergedResolver } from '@graphql-tools/delegate';

export default class HoistField implements Transform {
private readonly typeName: string;
private readonly path: Array<string>;
private readonly newFieldName: string;
private readonly pathToField: Array<string>;
private readonly oldFieldName: string;
private readonly transformer: Transform;

constructor(typeName: string, path: Array<string>, newFieldName: string) {
constructor(typeName: string, path: Array<string>, newFieldName: string, alias = '__gqtlw__') {
this.typeName = typeName;
this.path = path;
this.newFieldName = newFieldName;

this.pathToField = this.path.slice();
this.oldFieldName = this.pathToField.pop();
this.transformer = new MapFields({
[typeName]: {
[newFieldName]: fieldNode => wrapFieldNode(renameFieldNode(fieldNode, this.oldFieldName), this.pathToField),
const pathToField = path.slice();
const oldFieldName = pathToField.pop();

this.oldFieldName = oldFieldName;
this.pathToField = pathToField;
this.transformer = new MapFields(
{
[typeName]: {
[newFieldName]: fieldNode => wrapFieldNode(renameFieldNode(fieldNode, oldFieldName), pathToField, alias),
},
},
});
{
[typeName]: value => unwrapValue(value, alias),
},
errors => unwrapErrors(errors, alias)
);
}

public transformSchema(schema: GraphQLSchema): GraphQLSchema {
Expand All @@ -53,14 +62,81 @@ export default class HoistField implements Transform {
newSchema = appendObjectFields(newSchema, this.typeName, {
[this.newFieldName]: {
type: targetType,
resolve: createMergedResolver({ fromPath: this.pathToField }),
resolve: defaultMergedResolver,
},
});

return this.transformer.transformSchema(newSchema);
}

public transformRequest(originalRequest: Request): Request {
return this.transformer.transformRequest(originalRequest);
public transformRequest(
originalRequest: Request,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): Request {
return this.transformer.transformRequest(originalRequest, delegationContext, transformationContext);
}

public transformResult(
originalResult: ExecutionResult,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): ExecutionResult {
return this.transformer.transformResult(originalResult, delegationContext, transformationContext);
}
}

export function wrapFieldNode(fieldNode: FieldNode, path: Array<string>, alias: string): FieldNode {
let newFieldNode = fieldNode;
path.forEach(fieldName => {
newFieldNode = {
kind: Kind.FIELD,
alias: {
kind: Kind.NAME,
value: alias,
},
name: {
kind: Kind.NAME,
value: fieldName,
},
selectionSet: {
kind: Kind.SELECTION_SET,
selections: [fieldNode],
},
};
});

return newFieldNode;
}

export function unwrapValue(originalValue: any, alias: string): any {
let newValue = originalValue;

let object = newValue[alias];
while (object != null) {
newValue = object;
object = newValue[alias];
}

delete originalValue[alias];
Object.assign(originalValue, newValue);

return originalValue;
}

function unwrapErrors(errors: ReadonlyArray<GraphQLError>, alias: string): Array<GraphQLError> {
if (errors === undefined) {
return undefined;
}

return errors.map(error => {
const originalPath = error.path;
if (originalPath == null) {
return error;
}

const newPath = originalPath.filter(pathSegment => pathSegment !== alias);

return relocatedError(error, newPath);
});
}
54 changes: 46 additions & 8 deletions packages/wrap/src/transforms/MapFields.ts
Expand Up @@ -4,33 +4,71 @@ import { Transform, Request, FieldNodeMappers, ExecutionResult } from '@graphql-

import TransformCompositeFields from './TransformCompositeFields';

import { ObjectValueTransformerMap, ErrorsTransformer } from '../types';

export default class MapFields implements Transform {
private readonly transformer: TransformCompositeFields;

constructor(fieldNodeTransformerMap: FieldNodeMappers) {
constructor(
fieldNodeTransformerMap: FieldNodeMappers,
objectValueTransformerMap?: ObjectValueTransformerMap,
errorsTransformer?: ErrorsTransformer
) {
this.transformer = new TransformCompositeFields(
(_typeName, _fieldName, fieldConfig) => fieldConfig,
(typeName, fieldName, fieldNode, fragments) => {
(typeName, fieldName, fieldNode, fragments, context) => {
const typeTransformers = fieldNodeTransformerMap[typeName];
if (typeTransformers == null) {
return fieldNode;
return undefined;
}

const fieldNodeTransformer = typeTransformers[fieldName];
if (fieldNodeTransformer == null) {
return fieldNode;
return undefined;
}

return fieldNodeTransformer(fieldNode, fragments);
}
return fieldNodeTransformer(fieldNode, fragments, context);
},
objectValueTransformerMap != null
? (data, context) => {
if (data == null) {
return data;
}

const typeName = data.__typename;
if (typeName == null) {
return data;
}

const transformer = objectValueTransformerMap[typeName];
if (transformer == null) {
return data;
}

return transformer(data, context);
}
: undefined,
errorsTransformer != null ? errorsTransformer : undefined
);
}

public transformSchema(schema: GraphQLSchema): GraphQLSchema {
return this.transformer.transformSchema(schema);
}

public transformRequest(request: Request): Request {
return this.transformer.transformRequest(request);
public transformRequest(
originalRequest: Request,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): Request {
return this.transformer.transformRequest(originalRequest, delegationContext, transformationContext);
}

public transformResult(
originalResult: ExecutionResult,
delegationContext?: Record<string, any>,
transformationContext?: Record<string, any>
): ExecutionResult {
return this.transformer.transformResult(originalResult, delegationContext, transformationContext);
}
}

0 comments on commit 4f1ea0f

Please sign in to comment.