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

Commit

Permalink
feat(transforms): add dedicated filterSchema function to allow schema…
Browse files Browse the repository at this point in the history
… filtering without a layer of delegation
  • Loading branch information
yaacovCR committed Sep 22, 2019
1 parent 69b963f commit 98dafae
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 35 deletions.
93 changes: 58 additions & 35 deletions src/test/testAlternateMergeSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ import {
import mergeSchemas from '../stitching/mergeSchemas';
import {
transformSchema,
FilterRootFields,
filterSchema,
RenameTypes,
RenameRootFields,
RenameObjectFields,
FilterObjectFields,
TransformObjectFields,
} from '../transforms';
import {
Expand Down Expand Up @@ -88,33 +87,33 @@ describe('merge schemas through transforms', () => {
bookingSchemaExecConfig = await remoteBookingSchema;

// namespace and strip schemas
const transformedPropertySchema = transformSchema(propertySchema, [
new FilterRootFields(
(operation: string, rootField: string) =>
'Query.properties' === `${operation}.${rootField}`,
),
new RenameTypes((name: string) => `Properties_${name}`),
new RenameRootFields((operation: string, name: string) => `Properties_${name}`),
]);
const transformedBookingSchema = transformSchema(bookingSchemaExecConfig, [
new FilterRootFields(
(operation: string, rootField: string) =>
'Query.bookings' === `${operation}.${rootField}`,
),
new RenameTypes((name: string) => `Bookings_${name}`),
new RenameRootFields((operation: string, name: string) => `Bookings_${name}`),
]);
const transformedSubscriptionSchema = transformSchema(subscriptionSchema, [
new FilterRootFields(
(operation: string, rootField: string) =>
const transformedPropertySchema = filterSchema({
schema: transformSchema(propertySchema, [
new RenameTypes((name: string) => `Properties_${name}`),
new RenameRootFields((operation: string, name: string) => `Properties_${name}`),
]),
rootFieldFilter: (operation: string, rootField: string) =>
'Query.Properties_properties' === `${operation}.${rootField}`,
});
const transformedBookingSchema = filterSchema({
schema: transformSchema(bookingSchemaExecConfig, [
new RenameTypes((name: string) => `Bookings_${name}`),
new RenameRootFields((operation: string, name: string) => `Bookings_${name}`),
]),
rootFieldFilter: (operation: string, rootField: string) =>
'Query.Bookings_bookings' === `${operation}.${rootField}`
});
const transformedSubscriptionSchema = filterSchema({
schema: transformSchema(subscriptionSchema, [
new RenameTypes((name: string) => `Subscriptions_${name}`),
new RenameRootFields(
(operation: string, name: string) => `Subscriptions_${name}`),
]),
rootFieldFilter: (operation: string, rootField: string) =>
// must include a Query type otherwise graphql will error
'Query.notifications' === `${operation}.${rootField}` ||
'Subscription.notifications' === `${operation}.${rootField}`,
),
new RenameTypes((name: string) => `Subscriptions_${name}`),
new RenameRootFields(
(operation: string, name: string) => `Subscriptions_${name}`),
]);
'Query.Subscriptions_notifications' === `${operation}.${rootField}` ||
'Subscription.Subscriptions_notifications' === `${operation}.${rootField}`,
});

mergedSchema = mergeSchemas({
schemas: [
Expand Down Expand Up @@ -380,13 +379,37 @@ describe('filter and rename object fields', () => {
let transformedPropertySchema: GraphQLSchema;

before(async () => {
transformedPropertySchema = transformSchema(propertySchema, [
new RenameTypes((name: string) => `New_${name}`),
new FilterObjectFields((typeName: string, fieldName: string) =>
(typeName !== 'NewProperty' || fieldName === 'id' || fieldName === 'name' || fieldName === 'location')
),
new RenameObjectFields((typeName: string, fieldName: string) => (typeName === 'New_Property' ? `new_${fieldName}` : fieldName))
]);
transformedPropertySchema = filterSchema({
schema: transformSchema(propertySchema, [
new RenameTypes((name: string) => `New_${name}`),
new RenameObjectFields((typeName: string, fieldName: string) => (typeName === 'New_Property' ? `new_${fieldName}` : fieldName))
]),
rootFieldFilter: (operation: string, fieldName: string) =>
'Query.propertyById' === `${operation}.${fieldName}`,
fieldFilter: (typeName: string, fieldName: string) =>
(typeName === 'New_Property' || fieldName === 'name'),
typeFilter: (typeName: string) =>
(typeName === 'New_Property' || typeName === 'New_Location')
});
});

it('should filter', () => {
expect(printSchema(transformedPropertySchema)).to.equal(`type New_Location {
name: String!
}
type New_Property {
new_id: ID!
new_name: String!
new_location: New_Location
new_error: String
}
type Query {
propertyById(id: ID!): New_Property
}
`
);
});

it('should work', async () => {
Expand Down
124 changes: 124 additions & 0 deletions src/transforms/filterSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {
GraphQLEnumType,
GraphQLInputObjectType,
GraphQLInterfaceType,
GraphQLObjectType,
GraphQLScalarType,
GraphQLUnionType,
} from 'graphql';
import { GraphQLSchemaWithTransforms } from '../Interfaces';
import { visitSchema, VisitSchemaKind } from './visitSchema';
import { fieldToFieldConfig, createResolveType } from '../stitching/schemaRecreation';
import isEmptyObject from '../isEmptyObject';

export type RootFieldFilter = (
operation: 'Query' | 'Mutation' | 'Subscription',
rootFieldName: string
) => boolean;

export type FieldFilter = (
typeName: string,
rootFieldName: string
) => boolean;

export default function filterSchema({
schema,
rootFieldFilter = () => true,
typeFilter = () => true,
fieldFilter = () => true,
}: {
schema: GraphQLSchemaWithTransforms;
rootFieldFilter?: RootFieldFilter;
typeFilter?: (typeName: string) => boolean;
fieldFilter?: (typeName: string, fieldName: string) => boolean;
}): GraphQLSchemaWithTransforms {
const filteredSchema: GraphQLSchemaWithTransforms = visitSchema(schema, {
[VisitSchemaKind.QUERY]: (type: GraphQLObjectType) => {
return rootFieldFilter ? filterRootFields(type, 'Query', rootFieldFilter) : undefined;
},
[VisitSchemaKind.MUTATION]: (type: GraphQLObjectType) => {
return rootFieldFilter ? filterRootFields(type, 'Mutation', rootFieldFilter) : undefined;
},
[VisitSchemaKind.SUBSCRIPTION]: (type: GraphQLObjectType) => {
return rootFieldFilter ? filterRootFields(type, 'Subscription', rootFieldFilter) : undefined;
},
[VisitSchemaKind.OBJECT_TYPE]: (type: GraphQLObjectType) => {
return (!typeFilter || typeFilter(type.name)) ?
(filterObjectFields ?
filterObjectFields(type, fieldFilter) :
undefined) :
null;
},
[VisitSchemaKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) => {
return (!typeFilter || typeFilter(type.name)) ? undefined : null;
},
[VisitSchemaKind.UNION_TYPE]: (type: GraphQLUnionType) => {
return (!typeFilter || typeFilter(type.name)) ? undefined : null;
},
[VisitSchemaKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => {
return (!typeFilter || typeFilter(type.name)) ? undefined : null;
},
[VisitSchemaKind.ENUM_TYPE]: (type: GraphQLEnumType) => {
return (!typeFilter || typeFilter(type.name)) ? undefined : null;
},
[VisitSchemaKind.SCALAR_TYPE]: (type: GraphQLScalarType) => {
return (!typeFilter || typeFilter(type.name)) ? undefined : null;
},
});

filteredSchema.transforms = schema.transforms;

return filteredSchema;
}

function filterRootFields(
type: GraphQLObjectType,
operation: 'Query' | 'Mutation' | 'Subscription',
rootFieldFilter: RootFieldFilter,
): GraphQLObjectType {
const resolveType = createResolveType((_, t) => t);
const fields = type.getFields();
const newFields = {};
Object.keys(fields).forEach(fieldName => {
if (rootFieldFilter(operation, fieldName)) {
newFields[fieldName] = fieldToFieldConfig(fields[fieldName], resolveType, true);
}
});
if (isEmptyObject(newFields)) {
return null;
} else {
return new GraphQLObjectType({
name: type.name,
description: type.description,
astNode: type.astNode,
fields: newFields,
});
}
}

function filterObjectFields(
type: GraphQLObjectType,
fieldFilter: FieldFilter,
): GraphQLObjectType {
const resolveType = createResolveType((_, t) => t);
const fields = type.getFields();
const interfaces = type.getInterfaces();
const newFields = {};
Object.keys(fields).forEach(fieldName => {
if (fieldFilter(type.name, fieldName)) {
newFields[fieldName] = fieldToFieldConfig(fields[fieldName], resolveType, true);
}
});
if (isEmptyObject(newFields)) {
return null;
} else {
return new GraphQLObjectType({
name: type.name,
description: type.description,
astNode: type.astNode,
isTypeOf: type.isTypeOf,
fields: newFields,
interfaces: () => interfaces.map(iface => resolveType(iface)),
});
}
}
1 change: 1 addition & 0 deletions src/transforms/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Transform } from './transforms';
export { Transform };

export { default as filterSchema } from './filterSchema';
export { default as transformSchema } from './transformSchema';

export { default as AddArgumentsAsVariables } from './AddArgumentsAsVariables';
Expand Down

0 comments on commit 98dafae

Please sign in to comment.