Skip to content

Commit

Permalink
initial attempt
Browse files Browse the repository at this point in the history
  • Loading branch information
yaacovCR committed Jun 4, 2020
1 parent b7b8996 commit 6182b1a
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 1 deletion.
194 changes: 194 additions & 0 deletions packages/wrap/src/transforms/TransformInputFields.ts
@@ -0,0 +1,194 @@
import {
GraphQLSchema,
GraphQLType,
DocumentNode,
TypeInfo,
visit,
visitWithTypeInfo,
Kind,
FragmentDefinitionNode,
GraphQLInputObjectType,
ObjectValueNode,
ObjectFieldNode,
} from 'graphql';

import { Transform, Request, MapperKind, mapSchema } from '@graphql-tools/utils';
import { InputFieldTransformer, InputFieldNodeTransformer, InputObjectNodeTransformer } from '../types';
import { DelegationContext } from 'packages/delegate/src';

export default class TransformInputFields implements Transform {
private readonly inputFieldTransformer: InputFieldTransformer;
private readonly inputFieldNodeTransformer: InputFieldNodeTransformer;
private readonly inputObjectNodeTransformer: InputObjectNodeTransformer;
private transformedSchema: GraphQLSchema;
private mapping: Record<string, Record<string, string>>;

constructor(
inputFieldTransformer: InputFieldTransformer,
inputFieldNodeTransformer?: InputFieldNodeTransformer,
inputObjectNodeTransformer?: InputObjectNodeTransformer
) {
this.inputFieldTransformer = inputFieldTransformer;
this.inputFieldNodeTransformer = inputFieldNodeTransformer;
this.inputObjectNodeTransformer = inputObjectNodeTransformer;
this.mapping = {};
}

public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema {
this.transformedSchema = mapSchema(originalSchema, {
[MapperKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) =>
this.transformFields(type, this.inputFieldTransformer),
});

return this.transformedSchema;
}

public transformRequest(originalRequest: Request, delegationContext: DelegationContext): Request {
const fragments = Object.create(null);
originalRequest.document.definitions
.filter(def => def.kind === Kind.FRAGMENT_DEFINITION)
.forEach(def => {
fragments[(def as FragmentDefinitionNode).name.value] = def;
});
const document = this.transformDocument(
originalRequest.document,
this.mapping,
this.inputFieldNodeTransformer,
this.inputObjectNodeTransformer,
delegationContext,
originalRequest
);
return {
...originalRequest,
document,
};
}

private transformFields(type: GraphQLInputObjectType, inputFieldTransformer: InputFieldTransformer): any {
const config = type.toConfig();

const originalInputFieldConfigMap = config.fields;
const newInputFieldConfigMap = {};

Object.keys(originalInputFieldConfigMap).forEach(fieldName => {
const originalInputFieldConfig = originalInputFieldConfigMap[fieldName];
const transformedField = inputFieldTransformer(type.name, fieldName, originalInputFieldConfig);

if (transformedField === undefined) {
newInputFieldConfigMap[fieldName] = originalInputFieldConfig;
} else if (Array.isArray(transformedField)) {
const newFieldName = transformedField[0];
const newFieldConfig = transformedField[1];
newInputFieldConfigMap[newFieldName] = newFieldConfig;

if (newFieldName !== fieldName) {
const typeName = type.name;
if (!(typeName in this.mapping)) {
this.mapping[typeName] = {};
}
this.mapping[typeName][newFieldName] = fieldName;
}
} else if (transformedField != null) {
newInputFieldConfigMap[fieldName] = transformedField;
}
});

if (!Object.keys(newInputFieldConfigMap).length) {
return null;
}

return new GraphQLInputObjectType({
...type.toConfig(),
fields: newInputFieldConfigMap,
});
}

private transformDocument(
document: DocumentNode,
mapping: Record<string, Record<string, string>>,
inputFieldNodeTransformer: InputFieldNodeTransformer,
inputObjectNodeTransformer: InputObjectNodeTransformer,
delegationContext: DelegationContext,
request: Request
): DocumentNode {
const typeInfo = new TypeInfo(this.transformedSchema);
const newDocument: DocumentNode = visit(
document,
visitWithTypeInfo(typeInfo, {
leave: {
[Kind.OBJECT]: (node: ObjectValueNode): ObjectValueNode => {
const parentType: GraphQLType = typeInfo.getInputType() as GraphQLInputObjectType;
if (parentType != null) {
const parentTypeName = parentType.name;
const newInputFields: Array<ObjectFieldNode> = [];

node.fields.forEach(inputField => {
const newName = inputField.name.value;

const transformedInputField =
inputFieldNodeTransformer != null
? inputFieldNodeTransformer(parentTypeName, newName, inputField, delegationContext, request)
: inputField;

if (Array.isArray(transformedInputField)) {
transformedInputField.forEach(individualTransformedInputField => {
const typeMapping = mapping[parentTypeName];
if (typeMapping == null) {
newInputFields.push(individualTransformedInputField);
return;
}

const oldName = typeMapping[newName];
if (oldName == null) {
newInputFields.push(individualTransformedInputField);
return;
}

newInputFields.push({
...individualTransformedInputField,
name: {
...individualTransformedInputField.name,
value: oldName,
},
});
});
return;
}

const typeMapping = mapping[parentTypeName];
if (typeMapping == null) {
newInputFields.push(transformedInputField);
return;
}

const oldName = typeMapping[newName];
if (oldName == null) {
newInputFields.push(transformedInputField);
return;
}

newInputFields.push({
...transformedInputField,
name: {
...transformedInputField.name,
value: oldName,
},
});
});

const newNode = {
...node,
fields: newInputFields,
};

return inputObjectNodeTransformer != null
? inputObjectNodeTransformer(parentTypeName, newNode, delegationContext, request)
: newNode;
}
},
},
})
);
return newDocument;
}
}
27 changes: 26 additions & 1 deletion packages/wrap/src/types.ts
Expand Up @@ -2,12 +2,16 @@ import {
GraphQLSchema,
GraphQLFieldResolver,
BuildSchemaOptions,
GraphQLInputFieldConfig,
GraphQLFieldConfig,
FieldNode,
FragmentDefinitionNode,
SelectionNode,
ObjectFieldNode,
ObjectValueNode,
} from 'graphql';
import { Executor, Subscriber } from '@graphql-tools/delegate';
import { Executor, Subscriber, DelegationContext } from '@graphql-tools/delegate';
import { Request } from '@graphql-tools/utils';

export interface IMakeRemoteExecutableSchemaOptions {
schema: GraphQLSchema | string;
Expand All @@ -17,6 +21,27 @@ export interface IMakeRemoteExecutableSchemaOptions {
buildSchemaOptions?: BuildSchemaOptions;
}

export type InputFieldTransformer = (
typeName: string,
fieldName: string,
inputFieldConfig: GraphQLInputFieldConfig
) => GraphQLInputFieldConfig | [string, GraphQLInputFieldConfig] | null | undefined;

export type InputFieldNodeTransformer = (
typeName: string,
fieldName: string,
inputFieldNode: ObjectFieldNode,
delegationContext: DelegationContext,
request: Request
) => ObjectFieldNode | Array<ObjectFieldNode>;

export type InputObjectNodeTransformer = (
typeName: string,
inputObjectNode: ObjectValueNode,
delegationContext: DelegationContext,
request: Request
) => ObjectValueNode;

export type FieldTransformer = (
typeName: string,
fieldName: string,
Expand Down
71 changes: 71 additions & 0 deletions packages/wrap/tests/transforms.test.ts
Expand Up @@ -7,6 +7,8 @@ import {
SelectionSetNode,
print,
parse,
GraphQLInputObjectType,
astFromValue,
} from 'graphql';

import { makeExecutableSchema } from '@graphql-tools/schema';
Expand Down Expand Up @@ -35,6 +37,7 @@ import {
} from '@graphql-tools/delegate';

import { propertySchema, bookingSchema } from './fixtures/schemas';
import TransformInputFields from '../src/transforms/TransformInputFields';

function createError<T>(message: string, extra?: T) {
const error = new Error(message);
Expand Down Expand Up @@ -1368,4 +1371,72 @@ describe('replaces field with processed fragment node', () => {
},
});
});

describe('transform input type', () => {
test('it works', async () => {
const schema = makeExecutableSchema({
typeDefs: `
input InputObject {
field1: String
field2: String
}
type OutputObject {
field1: String
field2: String
}
type Query {
test(argument: InputObject): OutputObject
}
`,
resolvers: {
Query: {
test: (_root, args) => {
return args.argument;
}
}
}
});

const transformedSchema = wrapSchema(schema, [
new TransformInputFields(
(typeName, fieldName) => {
if (typeName === 'InputObject' && fieldName === 'field2') {
return null;
}
},
undefined,
(typeName, inputObjectNode) => {
if (typeName === 'InputObject') {
return {
...inputObjectNode,
fields: [...inputObjectNode.fields, {
kind: Kind.OBJECT_FIELD,
name: {
kind: Kind.NAME,
value: 'field2',
},
value: astFromValue('field2', (schema.getType('InputObject') as GraphQLInputObjectType).getFields()['field2'].type),
}],
};
}
}
)
]);

const query = `{
test(argument: {
field1: "field1"
}) {
field1
field2
}
}`;

const result = await graphql(transformedSchema, query);
expect(result.data.test.field1).toBe('field1');
expect(result.data.test.field2).toBe('field2');
});
});
});

0 comments on commit 6182b1a

Please sign in to comment.