Skip to content

Commit bb7bd64

Browse files
authored
v7 enhance(utils) expand filterSchema (#2005)
* updated filter schema. * update existing usage.
1 parent 9cbe464 commit bb7bd64

File tree

4 files changed

+274
-28
lines changed

4 files changed

+274
-28
lines changed

packages/stitch/tests/alternateStitchSchemas.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,7 @@ describe('filter and rename object fields', () => {
764764
]),
765765
rootFieldFilter: (operation: string, fieldName: string) =>
766766
`${operation}.${fieldName}` === 'Query.propertyById',
767-
fieldFilter: (typeName: string, fieldName: string) =>
767+
objectFieldFilter: (typeName: string, fieldName: string) =>
768768
typeName === 'New_Property' || fieldName === 'name',
769769
typeFilter: (typeName: string, type) =>
770770
typeName === 'New_Property' ||

packages/utils/src/Interfaces.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export type InputFieldFilter = (
193193
export type FieldFilter = (
194194
typeName?: string,
195195
fieldName?: string,
196-
fieldConfig?: GraphQLFieldConfig<any, any>
196+
fieldConfig?: GraphQLFieldConfig<any, any> | GraphQLInputFieldConfig
197197
) => boolean;
198198

199199
export type RootFieldFilter = (
@@ -204,6 +204,13 @@ export type RootFieldFilter = (
204204

205205
export type TypeFilter = (typeName: string, type: GraphQLType) => boolean;
206206

207+
export type ArgumentFilter = (
208+
typeName?: string,
209+
fieldName?: string,
210+
argName?: string,
211+
argConfig?: GraphQLArgumentConfig
212+
) => boolean;
213+
207214
export type RenameTypesOptions = {
208215
renameBuiltins: boolean;
209216
renameScalars: boolean;

packages/utils/src/filterSchema.ts

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,62 @@ import {
88
GraphQLSchema,
99
} from 'graphql';
1010

11-
import { MapperKind, FieldFilter, RootFieldFilter, TypeFilter } from './Interfaces';
11+
import { MapperKind, FieldFilter, RootFieldFilter, TypeFilter, ArgumentFilter } from './Interfaces';
1212

1313
import { mapSchema } from './mapSchema';
1414

1515
import { Constructor } from './types';
1616

1717
export function filterSchema({
1818
schema,
19-
rootFieldFilter = () => true,
2019
typeFilter = () => true,
21-
fieldFilter = () => true,
22-
objectFieldFilter = () => true,
23-
interfaceFieldFilter = () => true,
20+
fieldFilter = undefined,
21+
rootFieldFilter = undefined,
22+
objectFieldFilter = undefined,
23+
interfaceFieldFilter = undefined,
24+
inputObjectFieldFilter = undefined,
25+
argumentFilter = undefined,
2426
}: {
2527
schema: GraphQLSchema;
2628
rootFieldFilter?: RootFieldFilter;
2729
typeFilter?: TypeFilter;
2830
fieldFilter?: FieldFilter;
2931
objectFieldFilter?: FieldFilter;
3032
interfaceFieldFilter?: FieldFilter;
33+
inputObjectFieldFilter?: FieldFilter;
34+
argumentFilter?: ArgumentFilter;
3135
}): GraphQLSchema {
3236
const filteredSchema: GraphQLSchema = mapSchema(schema, {
3337
[MapperKind.QUERY]: (type: GraphQLObjectType) => filterRootFields(type, 'Query', rootFieldFilter),
3438
[MapperKind.MUTATION]: (type: GraphQLObjectType) => filterRootFields(type, 'Mutation', rootFieldFilter),
3539
[MapperKind.SUBSCRIPTION]: (type: GraphQLObjectType) => filterRootFields(type, 'Subscription', rootFieldFilter),
3640
[MapperKind.OBJECT_TYPE]: (type: GraphQLObjectType) =>
3741
typeFilter(type.name, type)
38-
? filterElementFields<GraphQLObjectType>(type, objectFieldFilter || fieldFilter, GraphQLObjectType)
42+
? filterElementFields<GraphQLObjectType>(
43+
GraphQLObjectType,
44+
type,
45+
objectFieldFilter || fieldFilter,
46+
argumentFilter
47+
)
3948
: null,
4049
[MapperKind.INTERFACE_TYPE]: (type: GraphQLInterfaceType) =>
4150
typeFilter(type.name, type)
42-
? filterElementFields<GraphQLInterfaceType>(type, interfaceFieldFilter, GraphQLInterfaceType)
51+
? filterElementFields<GraphQLInterfaceType>(
52+
GraphQLInterfaceType,
53+
type,
54+
interfaceFieldFilter || fieldFilter,
55+
argumentFilter
56+
)
57+
: null,
58+
[MapperKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) =>
59+
typeFilter(type.name, type)
60+
? filterElementFields<GraphQLInputObjectType>(
61+
GraphQLInputObjectType,
62+
type,
63+
inputObjectFieldFilter || fieldFilter
64+
)
4365
: null,
4466
[MapperKind.UNION_TYPE]: (type: GraphQLUnionType) => (typeFilter(type.name, type) ? undefined : null),
45-
[MapperKind.INPUT_OBJECT_TYPE]: (type: GraphQLInputObjectType) => (typeFilter(type.name, type) ? undefined : null),
4667
[MapperKind.ENUM_TYPE]: (type: GraphQLEnumType) => (typeFilter(type.name, type) ? undefined : null),
4768
[MapperKind.SCALAR_TYPE]: (type: GraphQLScalarType) => (typeFilter(type.name, type) ? undefined : null),
4869
});
@@ -53,27 +74,41 @@ export function filterSchema({
5374
function filterRootFields(
5475
type: GraphQLObjectType,
5576
operation: 'Query' | 'Mutation' | 'Subscription',
56-
rootFieldFilter: RootFieldFilter
77+
rootFieldFilter?: RootFieldFilter
5778
): GraphQLObjectType {
58-
const config = type.toConfig();
59-
Object.keys(config.fields).forEach(fieldName => {
60-
if (!rootFieldFilter(operation, fieldName, config.fields[fieldName])) {
61-
delete config.fields[fieldName];
62-
}
63-
});
64-
return new GraphQLObjectType(config);
79+
if (rootFieldFilter) {
80+
const config = type.toConfig();
81+
Object.keys(config.fields).forEach(fieldName => {
82+
if (!rootFieldFilter(operation, fieldName, config.fields[fieldName])) {
83+
delete config.fields[fieldName];
84+
}
85+
});
86+
return new GraphQLObjectType(config);
87+
}
88+
return type;
6589
}
6690

6791
function filterElementFields<ElementType>(
68-
type: GraphQLObjectType | GraphQLInterfaceType,
69-
fieldFilter: FieldFilter,
70-
ElementConstructor: Constructor<ElementType>
71-
): ElementType {
72-
const config = type.toConfig();
73-
Object.keys(config.fields).forEach(fieldName => {
74-
if (!fieldFilter(type.name, fieldName, config.fields[fieldName])) {
75-
delete config.fields[fieldName];
92+
ElementConstructor: Constructor<ElementType>,
93+
type: GraphQLObjectType | GraphQLInterfaceType | GraphQLInputObjectType,
94+
fieldFilter?: FieldFilter,
95+
argumentFilter?: ArgumentFilter
96+
): ElementType | undefined {
97+
if (fieldFilter || argumentFilter) {
98+
if (!fieldFilter) fieldFilter = () => true;
99+
100+
const config = type.toConfig();
101+
for (const [fieldName, field] of Object.entries(config.fields)) {
102+
if (!fieldFilter(type.name, fieldName, config.fields[fieldName])) {
103+
delete config.fields[fieldName];
104+
} else if (argumentFilter && 'args' in field) {
105+
for (const argName of Object.keys(field.args)) {
106+
if (!argumentFilter(type.name, fieldName, argName, field.args[argName])) {
107+
delete field.args[argName];
108+
}
109+
}
110+
}
76111
}
77-
});
78-
return new ElementConstructor(config);
112+
return new ElementConstructor(config);
113+
}
79114
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import { makeExecutableSchema } from '@graphql-tools/schema';
2+
import { filterSchema } from '@graphql-tools/utils';
3+
4+
describe('filterSchema', () => {
5+
it('filters root fields', () => {
6+
const schema = makeExecutableSchema({
7+
typeDefs: `
8+
type Query {
9+
keep: String
10+
omit: String
11+
}
12+
type Mutation {
13+
keepThis(id: ID): String
14+
omitThis(id: ID): String
15+
}
16+
`
17+
});
18+
19+
const filtered = filterSchema({
20+
schema,
21+
rootFieldFilter: (opName, fieldName) => fieldName.startsWith('keep'),
22+
});
23+
24+
expect(filtered.getType('Query').getFields()['keep']).toBeDefined();
25+
expect(filtered.getType('Query').getFields()['omit']).toBeUndefined();
26+
expect(filtered.getType('Mutation').getFields()['keepThis']).toBeDefined();
27+
expect(filtered.getType('Mutation').getFields()['omitThis']).toBeUndefined();
28+
});
29+
30+
it('filters types', () => {
31+
const schema = makeExecutableSchema({
32+
typeDefs: `
33+
type Keep implements IKeep {
34+
field(input: KeepInput): String
35+
}
36+
interface IKeep {
37+
field(input: KeepInput): String
38+
}
39+
type Remove implements IRemove {
40+
field(input: RemoveInput): String
41+
}
42+
interface IRemove {
43+
field(input: RemoveInput): String
44+
}
45+
union KeepMany = Keep | Remove
46+
union RemoveMany = Keep | Remove
47+
input KeepInput {
48+
field: String
49+
}
50+
input RemoveInput {
51+
field: String
52+
}
53+
enum KeepValues {
54+
VALUE
55+
}
56+
enum RemoveValues {
57+
VALUE
58+
}
59+
scalar KeepScalar
60+
scalar RemoveScalar
61+
`
62+
});
63+
64+
const filtered = filterSchema({
65+
schema,
66+
typeFilter: (typeName) => !/^I?Remove/.test(typeName)
67+
});
68+
69+
expect(filtered.getType('Keep')).toBeDefined();
70+
expect(filtered.getType('IKeep')).toBeDefined();
71+
expect(filtered.getType('KeepMany')).toBeDefined();
72+
expect(filtered.getType('KeepInput')).toBeDefined();
73+
expect(filtered.getType('KeepValues')).toBeDefined();
74+
expect(filtered.getType('KeepScalar')).toBeDefined();
75+
76+
expect(filtered.getType('Remove')).toBeUndefined();
77+
expect(filtered.getType('IRemove')).toBeUndefined();
78+
expect(filtered.getType('RemoveMany')).toBeUndefined();
79+
expect(filtered.getType('RemoveInput')).toBeUndefined();
80+
expect(filtered.getType('RemoveValues')).toBeUndefined();
81+
expect(filtered.getType('RemoveScalar')).toBeUndefined();
82+
});
83+
84+
it('filters object fields', () => {
85+
const schema = makeExecutableSchema({
86+
typeDefs: `
87+
type Thing implements IThing {
88+
keep: String
89+
omit: String
90+
}
91+
interface IThing {
92+
control: String
93+
}
94+
`
95+
});
96+
97+
const filtered = filterSchema({
98+
schema,
99+
objectFieldFilter: (typeName, fieldName) => fieldName.startsWith('keep'),
100+
});
101+
102+
expect(filtered.getType('Thing').getFields()['keep']).toBeDefined();
103+
expect(filtered.getType('Thing').getFields()['omit']).toBeUndefined();
104+
expect(filtered.getType('IThing').getFields()['control']).toBeDefined();
105+
});
106+
107+
it('filters interface fields', () => {
108+
const schema = makeExecutableSchema({
109+
typeDefs: `
110+
interface IThing {
111+
keep: String
112+
omit: String
113+
}
114+
type Thing implements IThing {
115+
control: String
116+
}
117+
`
118+
});
119+
120+
const filtered = filterSchema({
121+
schema,
122+
interfaceFieldFilter: (typeName, fieldName) => fieldName.startsWith('keep'),
123+
});
124+
125+
expect(filtered.getType('IThing').getFields()['keep']).toBeDefined();
126+
expect(filtered.getType('IThing').getFields()['omit']).toBeUndefined();
127+
expect(filtered.getType('Thing').getFields()['control']).toBeDefined();
128+
});
129+
130+
it('filters input object fields', () => {
131+
const schema = makeExecutableSchema({
132+
typeDefs: `
133+
input ThingInput {
134+
keep: String
135+
omit: String
136+
}
137+
type Thing {
138+
control: String
139+
}
140+
`
141+
});
142+
143+
const filtered = filterSchema({
144+
schema,
145+
inputObjectFieldFilter: (typeName, fieldName) => fieldName.startsWith('keep'),
146+
});
147+
148+
expect(filtered.getType('ThingInput').getFields()['keep']).toBeDefined();
149+
expect(filtered.getType('ThingInput').getFields()['omit']).toBeUndefined();
150+
expect(filtered.getType('Thing').getFields()['control']).toBeDefined();
151+
});
152+
153+
it('filters all field types', () => {
154+
const schema = makeExecutableSchema({
155+
typeDefs: `
156+
type Thing implements IThing {
157+
keep: String
158+
omit: String
159+
}
160+
interface IThing {
161+
keep: String
162+
omit: String
163+
}
164+
input ThingInput {
165+
keep: String
166+
omit: String
167+
}
168+
`
169+
});
170+
171+
const filtered = filterSchema({
172+
schema,
173+
fieldFilter: (typeName, fieldName) => fieldName.startsWith('keep'),
174+
});
175+
176+
expect(filtered.getType('Thing').getFields()['keep']).toBeDefined();
177+
expect(filtered.getType('Thing').getFields()['omit']).toBeUndefined();
178+
expect(filtered.getType('IThing').getFields()['keep']).toBeDefined();
179+
expect(filtered.getType('IThing').getFields()['omit']).toBeUndefined();
180+
expect(filtered.getType('ThingInput').getFields()['keep']).toBeDefined();
181+
expect(filtered.getType('ThingInput').getFields()['omit']).toBeUndefined();
182+
});
183+
184+
it('filters all arguments', () => {
185+
const schema = makeExecutableSchema({
186+
typeDefs: `
187+
type Thing implements IThing {
188+
field(keep: String, omit: String): String
189+
}
190+
interface IThing {
191+
field(keep: String, omit: String): String
192+
}
193+
`
194+
});
195+
196+
const filtered = filterSchema({
197+
schema,
198+
argumentFilter: (typeName, fieldName, argName) => argName.startsWith('keep'),
199+
});
200+
201+
expect(filtered.getType('Thing').getFields()['field'].args.map(arg => arg.name)).toEqual(['keep']);
202+
expect(filtered.getType('IThing').getFields()['field'].args.map(arg => arg.name)).toEqual(['keep']);
203+
});
204+
});

0 commit comments

Comments
 (0)