Skip to content

Commit efab640

Browse files
authored
Introduce Bare mode to filter-schema transform (#1624)
* Introduce Bare mode to filter-schema transform * Keep filter-schema backwards compatible hence accepting array of strings as config * Apply new syntax for filtering args to wrap mode this introduces a breaking change but simplifies code significantly * Extending filter transform tests * Adding changeset
1 parent 5c7f803 commit efab640

File tree

11 files changed

+477
-231
lines changed

11 files changed

+477
-231
lines changed

.changeset/itchy-radios-lick.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@graphql-mesh/transform-filter-schema': minor
3+
---
4+
5+
Introducing "bare" mode on filter schema.
6+
7+
** Breaking Changes **
8+
Changed syntax for filtering field arguments.
9+
This is done by declaring type name, field name and args:
10+
`Query.user.!{pk, name}`

packages/transforms/filter-schema/src/FilterObjectFieldsAndArguments.ts

Lines changed: 0 additions & 30 deletions
This file was deleted.

packages/transforms/filter-schema/src/FilterRootFieldsAndArguments.ts

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { GraphQLSchema } from 'graphql';
2+
import { MeshTransform, MeshTransformOptions, YamlConfig } from '@graphql-mesh/types';
3+
import { MapperKind, mapSchema, pruneSchema } from '@graphql-tools/utils';
4+
import { matcher } from 'micromatch';
5+
6+
export default class BareFilter implements MeshTransform {
7+
noWrap = true;
8+
typeGlobs: string[];
9+
fieldsMap: Map<string, string[]>;
10+
argsMap: Map<string, string[]>;
11+
12+
constructor(options: MeshTransformOptions<YamlConfig.FilterSchemaTransform>) {
13+
const {
14+
config: { filters },
15+
} = options;
16+
this.typeGlobs = [];
17+
this.fieldsMap = new Map();
18+
this.argsMap = new Map();
19+
20+
for (const filter of filters) {
21+
const [typeName, fieldNameOrGlob, argsGlob] = filter.split('.');
22+
23+
if (!fieldNameOrGlob) {
24+
this.typeGlobs.push(typeName);
25+
continue;
26+
}
27+
28+
const rawGlob = argsGlob || fieldNameOrGlob;
29+
const mapName = argsGlob ? 'argsMap' : 'fieldsMap';
30+
const mapKey = argsGlob ? `${typeName}_${fieldNameOrGlob}` : typeName;
31+
const currentRules = this[mapName].get(mapKey) || [];
32+
const fixedGlob =
33+
rawGlob.includes('{') && !rawGlob.includes(',') ? rawGlob.replace('{', '').replace('}', '') : rawGlob;
34+
const polishedGlob = fixedGlob.split(', ').join(',').trim();
35+
36+
this[mapName].set(mapKey, [...currentRules, polishedGlob]);
37+
}
38+
}
39+
40+
matchInArray(rulesArray: string[], value: string): null | undefined {
41+
for (const rule of rulesArray) {
42+
const isMatch = matcher(rule);
43+
if (!isMatch(value)) return null;
44+
}
45+
return undefined;
46+
}
47+
48+
transformSchema(schema: GraphQLSchema) {
49+
const transformedSchema = mapSchema(schema, {
50+
...(this.typeGlobs.length && {
51+
[MapperKind.TYPE]: type => this.matchInArray(this.typeGlobs, type.toString()),
52+
}),
53+
...((this.fieldsMap.size || this.argsMap.size) && {
54+
[MapperKind.COMPOSITE_FIELD]: (fieldConfig, fieldName, typeName) => {
55+
const fieldRules = this.fieldsMap.get(typeName);
56+
const argRules = this.argsMap.get(`${typeName}_${fieldName}`);
57+
const hasFieldRules = Boolean(fieldRules && fieldRules.length);
58+
const hasArgRules = Boolean(argRules && argRules.length);
59+
60+
if (hasFieldRules && this.matchInArray(fieldRules, fieldName) === null) return null;
61+
if (!hasArgRules) return undefined;
62+
63+
const fieldArgs = Object.entries(fieldConfig.args).reduce(
64+
(args, [argName, argConfig]) =>
65+
this.matchInArray(argRules, argName) === null ? args : { ...args, [argName]: argConfig },
66+
{}
67+
);
68+
69+
return { ...fieldConfig, args: fieldArgs };
70+
},
71+
}),
72+
});
73+
74+
return pruneSchema(transformedSchema);
75+
}
76+
}

packages/transforms/filter-schema/src/common.ts

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 13 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,17 @@
1-
import { MeshTransform, MeshTransformOptions, YamlConfig } from '@graphql-mesh/types';
2-
import { applyRequestTransforms, applyResultTransforms, applySchemaTransforms } from '@graphql-mesh/utils';
3-
import { DelegationContext, SubschemaConfig, Transform } from '@graphql-tools/delegate';
4-
import { ExecutionResult, pruneSchema, Request } from '@graphql-tools/utils';
5-
import { FilterInputObjectFields, FilterTypes } from '@graphql-tools/wrap';
6-
import { GraphQLSchema } from 'graphql';
7-
import { matcher } from 'micromatch';
8-
9-
import FilterObjectFieldsAndArguments from './FilterObjectFieldsAndArguments';
10-
import { FilterRootFieldsAndArguments } from './FilterRootFieldsAndArguments';
11-
12-
export default class FilterTransform implements MeshTransform {
13-
private transforms: Transform[] = [];
14-
constructor(options: MeshTransformOptions<YamlConfig.Transform['filterSchema']>) {
15-
const { config } = options;
16-
for (const filter of config) {
17-
const [typeName, fieldGlob] = filter.split('.');
18-
const isTypeMatch = matcher(typeName);
19-
if (!fieldGlob) {
20-
this.transforms.push(
21-
new FilterTypes(type => {
22-
return isTypeMatch(type.name);
23-
})
24-
);
25-
} else {
26-
// returns a match where second match = '{' or '!{' and third match being the content of the array
27-
const outerArrayMatch = fieldGlob.match(/^(\{|!{)(.*)(?=\}$)/);
28-
const fieldGlobStripped = outerArrayMatch ? outerArrayMatch[2] : fieldGlob;
29-
30-
// split field parts (might include argument filter)
31-
const fieldPatterns = fieldGlobStripped.match(/[\w-!*?[\]]+(\(.*?\))?/g);
32-
33-
// extract field names only
34-
let fixedFieldGlob = fieldPatterns.map(i => i.split('(')[0].trim()).join(',');
35-
36-
// if input was glob glob array
37-
if (outerArrayMatch) {
38-
// strip array for single field inputs
39-
if (fieldPatterns.length === 1) {
40-
fixedFieldGlob = `${outerArrayMatch[1].replace('{', '')}${fixedFieldGlob}`;
41-
} else {
42-
fixedFieldGlob = `${outerArrayMatch[1]}${fixedFieldGlob}}`;
43-
}
44-
}
45-
46-
const fieldArgsPatternMap = fieldPatterns.reduce((prev, match) => {
47-
const fieldParts = match.split('(');
48-
49-
if (fieldParts.length !== 2) {
50-
return prev;
51-
}
52-
53-
const fieldName = fieldParts[0].replace(/!{|{|}/g, '').trim();
54-
const fixedArgGlob = fieldParts[1]
55-
.split(')')[0]
56-
.split(',')
57-
.map(i => i.trim())
58-
.join(',');
59-
60-
prev[fieldName] = fixedArgGlob;
61-
return prev;
62-
}, {});
63-
64-
const isMatch = matcher(fixedFieldGlob.trim());
65-
this.transforms.push(
66-
new FilterRootFieldsAndArguments(
67-
(rootTypeName, rootFieldName) => {
68-
if (isTypeMatch(rootTypeName)) {
69-
return isMatch(rootFieldName);
70-
}
71-
return true;
72-
},
73-
(fieldName, argName) => {
74-
if (!fieldArgsPatternMap[fieldName]) {
75-
return true;
76-
}
77-
return matcher(fieldArgsPatternMap[fieldName])(argName);
78-
}
79-
)
80-
);
81-
82-
this.transforms.push(
83-
new FilterObjectFieldsAndArguments(
84-
(rootTypeName, rootFieldName) => {
85-
if (isTypeMatch(rootTypeName)) {
86-
return isMatch(rootFieldName);
87-
}
88-
return true;
89-
},
90-
(fieldName, argName) => {
91-
if (!fieldArgsPatternMap[fieldName]) {
92-
return true;
93-
}
94-
return matcher(fieldArgsPatternMap[fieldName])(argName);
95-
}
96-
)
97-
);
98-
99-
this.transforms.push(
100-
new FilterInputObjectFields((inputObjectTypeName, inputObjectFieldName) => {
101-
if (isTypeMatch(inputObjectTypeName)) {
102-
return isMatch(inputObjectFieldName);
103-
}
104-
return true;
105-
})
106-
);
107-
}
108-
}
109-
}
110-
111-
transformSchema(
112-
originalWrappingSchema: GraphQLSchema,
113-
subschemaConfig: SubschemaConfig,
114-
transformedSchema?: GraphQLSchema
115-
) {
116-
const returnedSchema = applySchemaTransforms(
117-
originalWrappingSchema,
118-
subschemaConfig,
119-
transformedSchema,
120-
this.transforms
121-
);
122-
// TODO: Need a better solution
123-
return pruneSchema(returnedSchema, {
124-
skipEmptyCompositeTypePruning: false,
125-
skipEmptyUnionPruning: true,
126-
skipUnimplementedInterfacesPruning: true,
127-
skipUnusedTypesPruning: true,
1+
import { YamlConfig, MeshTransformOptions } from '@graphql-mesh/types';
2+
import WrapFilter from './wrapFilter';
3+
import BareFilter from './bareFilter';
4+
5+
export default function FilterTransform(options: MeshTransformOptions<YamlConfig.Transform['filterSchema']>) {
6+
if (Array.isArray(options.config)) {
7+
return new WrapFilter({
8+
...options,
9+
config: {
10+
mode: 'wrap',
11+
filters: options.config,
12+
},
12813
});
12914
}
13015

131-
transformRequest(
132-
originalRequest: Request,
133-
delegationContext: DelegationContext,
134-
transformationContext: Record<string, any>
135-
) {
136-
return applyRequestTransforms(originalRequest, delegationContext, transformationContext, this.transforms);
137-
}
138-
139-
transformResult(originalResult: ExecutionResult, delegationContext: DelegationContext, transformationContext: any) {
140-
return applyResultTransforms(originalResult, delegationContext, transformationContext, this.transforms);
141-
}
16+
return options.config.mode === 'bare' ? new BareFilter(options) : new WrapFilter(options);
14217
}

0 commit comments

Comments
 (0)