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 a3108b1
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 0 deletions.
179 changes: 179 additions & 0 deletions packages/wrap/src/transforms/TransformInputFields.ts
@@ -0,0 +1,179 @@
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 } from '../types';

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

constructor(inputFieldTransformer: InputFieldTransformer, inputFieldNodeTransformer?: InputFieldNodeTransformer) {
this.inputFieldTransformer = inputFieldTransformer;
this.inputFieldNodeTransformer = inputFieldNodeTransformer;
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): 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,
originalRequest.variables
);
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,
variables: Record<string, any>
): 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, variables)
: 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,
},
});
});

return {
...node,
fields: newInputFields,
};
}
},
},
})
);
return newDocument;
}
}
15 changes: 15 additions & 0 deletions packages/wrap/src/types.ts
Expand Up @@ -2,10 +2,12 @@ import {
GraphQLSchema,
GraphQLFieldResolver,
BuildSchemaOptions,
GraphQLInputFieldConfig,
GraphQLFieldConfig,
FieldNode,
FragmentDefinitionNode,
SelectionNode,
ObjectFieldNode,
} from 'graphql';
import { Executor, Subscriber } from '@graphql-tools/delegate';

Expand All @@ -17,6 +19,19 @@ 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,
variables: Record<string, any>
) => ObjectFieldNode | Array<ObjectFieldNode>;

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

import { makeExecutableSchema } from '@graphql-tools/schema';
Expand Down Expand Up @@ -35,6 +38,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 +1372,63 @@ 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;
}
},
(typeName, fieldName, inputFieldNode) => {
if (typeName === 'InputObject' && fieldName === 'field1') {
const newInputFieldNode: ObjectFieldNode = {
...inputFieldNode,
value: astFromValue('field2', (schema.getType('InputObject') as GraphQLInputObjectType).getFields()['field2'].type),
};
return newInputFieldNode;
}
}
)
]);

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

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

0 comments on commit a3108b1

Please sign in to comment.