Skip to content

Commit 7365a0d

Browse files
committed
handling basic union types. no support for list weights or non-null types
1 parent 24754d4 commit 7365a0d

File tree

1 file changed

+127
-14
lines changed

1 file changed

+127
-14
lines changed

src/analysis/buildTypeWeights.ts

Lines changed: 127 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import {
33
GraphQLArgument,
44
GraphQLNamedType,
55
GraphQLObjectType,
6-
GraphQLScalarType,
76
GraphQLInterfaceType,
8-
GraphQLList,
97
GraphQLOutputType,
108
isCompositeType,
119
isEnumType,
@@ -17,8 +15,10 @@ import {
1715
isUnionType,
1816
Kind,
1917
ValueNode,
18+
GraphQLUnionType,
19+
GraphQLFieldMap,
20+
GraphQLField,
2021
} from 'graphql';
21-
import { Maybe } from 'graphql/jsutils/Maybe';
2222
import { ObjMap } from 'graphql/jsutils/ObjMap';
2323
import { GraphQLSchema } from 'graphql/type/schema';
2424
import {
@@ -27,6 +27,7 @@ import {
2727
TypeWeightObject,
2828
Variables,
2929
Type,
30+
Fields,
3031
} from '../@types/buildTypeWeights';
3132

3233
export const KEYWORDS = ['first', 'last', 'limit'];
@@ -86,13 +87,9 @@ function parseObjectFields(
8687
) {
8788
result.fields[field] = {
8889
weight: typeWeights.scalar,
90+
// resolveTo: fields[field].name.toLowerCase(),
8991
};
90-
} else if (
91-
isInterfaceType(fieldType) ||
92-
isUnionType(fieldType) ||
93-
isEnumType(fieldType) ||
94-
isObjectType(fieldType)
95-
) {
92+
} else if (isInterfaceType(fieldType) || isEnumType(fieldType) || isObjectType(fieldType)) {
9693
result.fields[field] = {
9794
resolveTo: fieldType.name.toLocaleLowerCase(),
9895
};
@@ -157,14 +154,43 @@ function parseObjectFields(
157154
}
158155
});
159156
}
157+
} else if (isUnionType(fieldType)) {
158+
// Users must query union types using inline fragments to resolve field specific to one of the types in the union
159+
// however, if a type is shared by all types in the union it can be queried outside of the inline fragment
160+
// any common fields should be added to fields on the union type itself in addition to the comprising types
161+
// Get all types in the union
162+
// iterate through all types creating a set of type names
163+
// add resulting set to fields
164+
// FIXME: What happens if two types share a name that resolve to different types => invalid query?
165+
result.fields[field] = {
166+
resolveTo: fieldType.name.toLocaleLowerCase(),
167+
};
160168
} else {
161169
// ? what else can get through here
170+
throw new Error(`ERROR: buildTypeWeight: Unsupported field type: ${fieldType}`);
162171
}
163172
});
164173

165174
return result;
166175
}
167176

177+
/**
178+
* Recursively compares two types for type equality based on name
179+
* @param a
180+
* @param b
181+
* @returns
182+
*/
183+
function compareTypes(a: GraphQLOutputType, b: GraphQLOutputType): boolean {
184+
return (
185+
(isObjectType(b) && isObjectType(a) && a.name === b.name) ||
186+
(isUnionType(b) && isUnionType(a) && a.name === b.name) ||
187+
(isInterfaceType(b) && isInterfaceType(a) && a.name === b.name) ||
188+
(isScalarType(b) && isScalarType(a) && a.name === b.name) ||
189+
(isListType(b) && isListType(a) && compareTypes(b.ofType, a.ofType)) ||
190+
(isNonNullType(b) && isNonNullType(a) && compareTypes(a.ofType, b.ofType))
191+
);
192+
}
193+
168194
/**
169195
* Parses all types in the provided schema object excempt for Query, Mutation
170196
* and built in types that begin with '__' and outputs a new TypeWeightObject
@@ -177,6 +203,8 @@ function parseTypes(schema: GraphQLSchema, typeWeights: TypeWeightSet): TypeWeig
177203

178204
const result: TypeWeightObject = {};
179205

206+
const unions: GraphQLUnionType[] = [];
207+
180208
// Handle Object, Interface, Enum and Union types
181209
Object.keys(typeMap).forEach((type) => {
182210
const typeName: string = type.toLowerCase();
@@ -193,18 +221,103 @@ function parseTypes(schema: GraphQLSchema, typeWeights: TypeWeightSet): TypeWeig
193221
weight: typeWeights.scalar,
194222
};
195223
} else if (isUnionType(currentType)) {
196-
// FIXME: will need information on fields inorder calculate comlpextiy
197-
result[typeName] = {
198-
fields: {},
199-
weight: typeWeights.object,
200-
};
224+
unions.push(currentType);
201225
} else {
226+
// FIXME: Scalar types are listed here throw new Error(`ERROR: buildTypeWeight: Unsupported type: ${currentType}`);
202227
// ? what else can get through here
203228
// ? inputTypes?
204229
}
205230
}
206231
});
207232

233+
type FieldMap = { [index: string]: GraphQLOutputType };
234+
type CommonFields = { [index: string]: Type };
235+
236+
unions.forEach((unionType: GraphQLUnionType) => {
237+
/** Start with the fields for the first object. Store fieldnamd and type
238+
* reduce by selecting fields common to each type
239+
* compare both fieldname and output type accounting for lists and non-nulls
240+
* for object
241+
* compare name of output type
242+
* for lists
243+
* compare ofType and ofType name if not onother list/non-null
244+
* for non-nulls
245+
* compare oftype and ofTypeName (if not another non-null)
246+
* */
247+
248+
// types is an array mapping each field name to it's respective output type
249+
const types: FieldMap[] = unionType.getTypes().map((objectType: GraphQLObjectType) => {
250+
const fields: GraphQLFieldMap<any, any> = objectType.getFields();
251+
252+
const fieldMap: { [index: string]: GraphQLOutputType } = {};
253+
Object.keys(fields).forEach((field: string) => {
254+
fieldMap[field] = fields[field].type;
255+
});
256+
return fieldMap;
257+
});
258+
259+
const common: FieldMap = types.reduce((prev: FieldMap, fieldMap: FieldMap): FieldMap => {
260+
// iterate through the field map checking the types for any common field names
261+
const commonFields: FieldMap = {};
262+
Object.keys(prev).forEach((field: string) => {
263+
if (fieldMap[field]) {
264+
if (compareTypes(prev[field], fieldMap[field])) {
265+
// they match add the type to the next set
266+
commonFields[field] = prev[field];
267+
}
268+
}
269+
});
270+
return commonFields;
271+
});
272+
273+
// transform commonFields into the correct format
274+
const fieldTypes: Fields = {};
275+
276+
Object.keys(common).forEach((field: string) => {
277+
// if a scalar => weight
278+
// object => resolveTo
279+
// list => // resolveTo + weight(function)
280+
const current = common[field];
281+
if (isScalarType(current)) {
282+
fieldTypes[field] = {
283+
weight: typeWeights.scalar,
284+
};
285+
}
286+
// else if (isObjectType(current)) {
287+
// fieldTypes[field] = {
288+
// resolveTo: current.name,
289+
// };
290+
// }
291+
else if (isListType(current)) {
292+
throw new Error('list types not supported on unions');
293+
fieldTypes[field] = {
294+
resolveTo: 'test', // get resolve type problem is recursive data structure (i.e. list of lists)
295+
// weight: TODO: Get the function for resolving
296+
};
297+
} else if (isNonNullType(current)) {
298+
throw new Error('non null types not supported on unions');
299+
// TODO: also a recursive data structure
300+
} else {
301+
throw new Error('Unandled union type. Should never get here');
302+
}
303+
});
304+
result[unionType.name.toLowerCase()] = {
305+
fields: fieldTypes,
306+
weight: typeWeights.object,
307+
};
308+
309+
//
310+
// objects are not. they exist at the root.
311+
// FIXME: Is it worth adding objects as a field?
312+
// yes, I think so => refactor fieldNode parser
313+
// if it resolves to object then add commonFields set
314+
// i think we have this already.
315+
// double check the non-null tests
316+
// commonFields.add({
317+
// weight,
318+
// });
319+
});
320+
208321
return result;
209322
}
210323

0 commit comments

Comments
 (0)