Skip to content

Commit

Permalink
add FilterInputObjectFields, RenameInputObjectFields
Browse files Browse the repository at this point in the history
closes #1058
  • Loading branch information
yaacovCR committed Jun 4, 2020
1 parent 6182b1a commit 6f5f37d
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 12 deletions.
6 changes: 6 additions & 0 deletions packages/utils/src/Interfaces.ts
Expand Up @@ -158,6 +158,12 @@ export type FieldNodeMapper = (

export type FieldNodeMappers = Record<string, Record<string, FieldNodeMapper>>;

export type InputFieldFilter = (
typeName?: string,
fieldName?: string,
inputFieldConfig?: GraphQLInputFieldConfig
) => boolean;

export type FieldFilter = (
typeName?: string,
fieldName?: string,
Expand Down
28 changes: 28 additions & 0 deletions packages/wrap/src/transforms/FilterInputObjectFields.ts
@@ -0,0 +1,28 @@
import { GraphQLSchema, GraphQLInputFieldConfig } from 'graphql';

import { Transform, Request, InputFieldFilter } from '@graphql-tools/utils';

import TransformInputFields from './TransformInputFields';
import { DelegationContext } from 'packages/delegate/src';
import { InputObjectNodeTransformer } from '../types';

export default class FilterInputObjectFields implements Transform {
private readonly transformer: TransformInputFields;

constructor(filter: InputFieldFilter, inputObjectNodeTransformer: InputObjectNodeTransformer) {
this.transformer = new TransformInputFields(
(typeName: string, fieldName: string, inputFieldConfig: GraphQLInputFieldConfig) =>
filter(typeName, fieldName, inputFieldConfig) ? undefined : null,
undefined,
inputObjectNodeTransformer
);
}

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

public transformRequest(originalRequest: Request, delegationContext: DelegationContext): Request {
return this.transformer.transformRequest(originalRequest, delegationContext);
}
}
72 changes: 72 additions & 0 deletions packages/wrap/src/transforms/RenameInputObjectFields.ts
@@ -0,0 +1,72 @@
import { GraphQLSchema, GraphQLInputFieldConfig, ObjectFieldNode } from 'graphql';

import { Transform, Request, mapSchema, MapperKind } from '@graphql-tools/utils';

import TransformInputFields from './TransformInputFields';
import { DelegationContext } from 'packages/delegate/src';

export default class RenameInputObjectFields implements Transform {
private readonly renamer: (typeName: string, fieldName: string, inputFieldConfig: GraphQLInputFieldConfig) => string;
private readonly transformer: TransformInputFields;
private reverseMap: Record<string, Record<string, string>>;

constructor(renamer: (typeName: string, fieldName: string, inputFieldConfig: GraphQLInputFieldConfig) => string) {
this.renamer = renamer;
this.transformer = new TransformInputFields(
(typeName: string, inputFieldName: string, inputFieldConfig: GraphQLInputFieldConfig) => {
const newName = renamer(typeName, inputFieldName, inputFieldConfig);
if (newName !== undefined && newName !== inputFieldName) {
return [renamer(typeName, inputFieldName, inputFieldConfig), inputFieldConfig];
}
},
(typeName: string, inputFieldName: string, inputFieldNode: ObjectFieldNode) => {
if (!(typeName in this.reverseMap)) {
return inputFieldNode;
}

const inputFieldNameMap = this.reverseMap[typeName];
if (!(inputFieldName in inputFieldNameMap)) {
return inputFieldNode;
}

return {
...inputFieldNode,
name: {
...inputFieldNode.name,
value: inputFieldNameMap[inputFieldName],
},
};
}
);
this.reverseMap = Object.create(null);
}

public transformSchema(originalSchema: GraphQLSchema): GraphQLSchema {
mapSchema(originalSchema, {
[MapperKind.INPUT_OBJECT_FIELD]: (
inputFieldConfig: GraphQLInputFieldConfig,
fieldName: string,
typeName
): undefined => {
const newName = this.renamer(typeName, fieldName, inputFieldConfig);
if (newName !== undefined && newName !== fieldName) {
if (this.reverseMap[typeName] == null) {
this.reverseMap[typeName] = Object.create(null);
}
this.reverseMap[typeName][newName] = fieldName;
}
return undefined;
},

[MapperKind.ROOT_OBJECT]() {
return undefined;
},
});

return this.transformer.transformSchema(originalSchema);
}

public transformRequest(originalRequest: Request, delegationContext: DelegationContext): Request {
return this.transformer.transformRequest(originalRequest, delegationContext);
}
}
3 changes: 3 additions & 0 deletions packages/wrap/src/transforms/index.ts
Expand Up @@ -11,6 +11,9 @@ export { default as FilterObjectFields } from './FilterObjectFields';
export { default as TransformInterfaceFields } from './TransformInterfaceFields';
export { default as RenameInterfaceFields } from './RenameInterfaceFields';
export { default as FilterInterfaceFields } from './FilterInterfaceFields';
export { default as TransformInputFields } from './TransformInputFields';
export { default as RenameInputObjectFields } from './RenameInputObjectFields';
export { default as FilterInputObjectFields } from './FilterInputObjectFields';
export { default as TransformQuery } from './TransformQuery';

export { default as ExtendSchema } from './ExtendSchema';
Expand Down
71 changes: 59 additions & 12 deletions packages/wrap/tests/transforms.test.ts
Expand Up @@ -7,8 +7,8 @@ import {
SelectionSetNode,
print,
parse,
GraphQLInputObjectType,
astFromValue,
GraphQLString,
} from 'graphql';

import { makeExecutableSchema } from '@graphql-tools/schema';
Expand All @@ -26,6 +26,8 @@ import {
WrapQuery,
ExtractField,
TransformQuery,
FilterInputObjectFields,
RenameInputObjectFields,
} from '@graphql-tools/wrap';

import {
Expand All @@ -37,7 +39,6 @@ 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 @@ -1372,8 +1373,8 @@ describe('replaces field with processed fragment node', () => {
});
});

describe('transform input type', () => {
test('it works', async () => {
describe('transform input object fields', () => {
test('filtering works', async () => {
const schema = makeExecutableSchema({
typeDefs: `
input InputObject {
Expand All @@ -1400,13 +1401,8 @@ describe('replaces field with processed fragment node', () => {
});

const transformedSchema = wrapSchema(schema, [
new TransformInputFields(
(typeName, fieldName) => {
if (typeName === 'InputObject' && fieldName === 'field2') {
return null;
}
},
undefined,
new FilterInputObjectFields(
(typeName, fieldName) => (typeName !== 'InputObject' || fieldName !== 'field2'),
(typeName, inputObjectNode) => {
if (typeName === 'InputObject') {
return {
Expand All @@ -1417,7 +1413,7 @@ describe('replaces field with processed fragment node', () => {
kind: Kind.NAME,
value: 'field2',
},
value: astFromValue('field2', (schema.getType('InputObject') as GraphQLInputObjectType).getFields()['field2'].type),
value: astFromValue('field2', GraphQLString),
}],
};
}
Expand All @@ -1439,4 +1435,55 @@ describe('replaces field with processed fragment node', () => {
expect(result.data.test.field2).toBe('field2');
});
});

test('renaming 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 RenameInputObjectFields(
(typeName: string, fieldName: string) => {
if (typeName === 'InputObject' && fieldName === 'field2') {
return 'field3';
}
},
)
]);

const query = `{
test(argument: {
field1: "field1"
field3: "field2"
}) {
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 6f5f37d

Please sign in to comment.