Skip to content

Commit 4ba017b

Browse files
committed
refactored union parsing for clarity
1 parent 54013c4 commit 4ba017b

File tree

1 file changed

+140
-93
lines changed

1 file changed

+140
-93
lines changed

src/analysis/buildTypeWeights.ts

Lines changed: 140 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,12 @@ function parseObjectFields(
8989
weight: typeWeights.scalar,
9090
// resolveTo: fields[field].name.toLowerCase(),
9191
};
92-
} else if (isInterfaceType(fieldType) || isEnumType(fieldType) || isObjectType(fieldType)) {
92+
} else if (
93+
isInterfaceType(fieldType) ||
94+
isEnumType(fieldType) ||
95+
isObjectType(fieldType) ||
96+
isUnionType(fieldType)
97+
) {
9398
result.fields[field] = {
9499
resolveTo: fieldType.name.toLocaleLowerCase(),
95100
};
@@ -154,13 +159,6 @@ function parseObjectFields(
154159
}
155160
});
156161
}
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-
result.fields[field] = {
162-
resolveTo: fieldType.name.toLocaleLowerCase(),
163-
};
164162
} else if (isNonNullType(fieldType)) {
165163
// TODO: Implment non-null types
166164
// not throwing and error since it causes typeWeight tests to break
@@ -174,12 +172,14 @@ function parseObjectFields(
174172
}
175173

176174
/**
177-
* Recursively compares two types for type equality based on name
175+
* Recursively compares two types for type equality based on type name
178176
* @param a
179177
* @param b
180-
* @returns
178+
* @returns true if the types are recursively equal.
181179
*/
182180
function compareTypes(a: GraphQLOutputType, b: GraphQLOutputType): boolean {
181+
// Base Case: Object or Scalar => compare type names
182+
// Recursive Case(List / NonNull): compare ofType
183183
return (
184184
(isObjectType(b) && isObjectType(a) && a.name === b.name) ||
185185
(isUnionType(b) && isUnionType(a) && a.name === b.name) ||
@@ -191,110 +191,118 @@ function compareTypes(a: GraphQLOutputType, b: GraphQLOutputType): boolean {
191191
}
192192

193193
/**
194-
* Parses all types in the provided schema object excempt for Query, Mutation
195-
* and built in types that begin with '__' and outputs a new TypeWeightObject
196-
* @param schema
197-
* @param typeWeights
198-
* @returns
194+
*
195+
* @param unionType union type to be parsed
196+
* @param typeWeightObject type weight mapping object that must already contain all of the types in the schema.
197+
* @returns object mapping field names for each union type to their respective weights, resolve type names and resolve type object
199198
*/
200-
function parseTypes(schema: GraphQLSchema, typeWeights: TypeWeightSet): TypeWeightObject {
201-
const typeMap: ObjMap<GraphQLNamedType> = schema.getTypeMap();
202-
203-
const result: TypeWeightObject = {};
199+
function getFieldsForUnionType(
200+
unionType: GraphQLUnionType,
201+
typeWeightObject: TypeWeightObject
202+
): FieldMap[] {
203+
return unionType.getTypes().map((objectType: GraphQLObjectType) => {
204+
// Get the field data for this type
205+
const fields: GraphQLFieldMap<unknown, unknown> = objectType.getFields();
204206

205-
const unions: GraphQLUnionType[] = [];
207+
const fieldMap: FieldMap = {};
208+
Object.keys(fields).forEach((field: string) => {
209+
// Get the weight of this field on from parent type on the root typeWeight object.
210+
// this only exists for scalars and lists (which resolve to a function);
211+
const { weight, resolveTo } =
212+
typeWeightObject[objectType.name.toLowerCase()].fields[field];
206213

207-
// Handle Object, Interface, Enum and Union types
208-
Object.keys(typeMap).forEach((type) => {
209-
const typeName: string = type.toLowerCase();
210-
const currentType: GraphQLNamedType = typeMap[type];
214+
fieldMap[field] = {
215+
type: fields[field].type,
216+
weight, // will only be undefined for object types
217+
resolveTo,
218+
};
219+
});
220+
return fieldMap;
221+
});
222+
}
211223

212-
// Get all types that aren't Query or Mutation or a built in type that starts with '__'
213-
if (!type.startsWith('__')) {
214-
if (isObjectType(currentType) || isInterfaceType(currentType)) {
215-
// Add the type and it's associated fields to the result
216-
result[typeName] = parseObjectFields(currentType, result, typeWeights);
217-
} else if (isEnumType(currentType)) {
218-
result[typeName] = {
219-
fields: {},
220-
weight: typeWeights.scalar,
221-
};
222-
} else if (isUnionType(currentType)) {
223-
unions.push(currentType);
224-
} else {
225-
// FIXME: Scalar types are listed here throw new Error(`ERROR: buildTypeWeight: Unsupported type: ${currentType}`);
226-
// ? what else can get through here
227-
// ? inputTypes?
224+
/**
225+
*
226+
* @param typesInUnion
227+
* @returns a single field map containg information for fields common to the union
228+
*/
229+
function getSharedFieldsFromUnionTypes(typesInUnion: FieldMap[]): FieldMap {
230+
return typesInUnion.reduce((prev: FieldMap, fieldMap: FieldMap): FieldMap => {
231+
// iterate through the field map checking the types for any common field names
232+
const sharedFields: FieldMap = {};
233+
Object.keys(prev).forEach((field: string) => {
234+
if (fieldMap[field]) {
235+
if (compareTypes(prev[field].type, fieldMap[field].type)) {
236+
// they match add the type to the next set
237+
sharedFields[field] = prev[field];
238+
}
228239
}
229-
}
240+
});
241+
return sharedFields;
230242
});
243+
}
231244

232-
unions.forEach((unionType: GraphQLUnionType) => {
233-
/** Start with the fields for the first object. Store fieldname, type, weight and resolve to for later use
234-
* reduce by selecting fields common to each type
235-
* compare both fieldname and output type accounting for lists and non-nulls
236-
* for object
237-
* compare name of output type
238-
* for lists
239-
* compare ofType and ofType name if not onother list/non-null
240-
* for non-nulls
241-
* compare oftype and ofTypeName (if not another non-null)
245+
/**
246+
* Parses the provided union types and returns a type weight object with any fields common to all types
247+
* in a union added to the union type
248+
* @param unionTypes union types to be parsed.
249+
* @param typeWeights object specifying generic type weights.
250+
* @param typeWeightObject original type weight object
251+
* @returns
252+
*/
253+
function parseUnionTypes(
254+
unionTypes: GraphQLUnionType[],
255+
typeWeights: TypeWeightSet,
256+
typeWeightObject: TypeWeightObject
257+
) {
258+
const typeWeightsWithUnions: TypeWeightObject = { ...typeWeightObject };
259+
260+
unionTypes.forEach((unionType: GraphQLUnionType) => {
261+
/**
262+
* 1. For each provided union type. We first obtain the fields for each object that
263+
* is part of the union and store these in an object
264+
* When obtaining types, save:
265+
* - field name
266+
* - type object to which the field resolves. This holds any information for recursive types (lists / not null / unions)
267+
* - weight - for easy lookup later
268+
* - resolveTo type - for easy lookup later
269+
* 2. We then reduce the array of objects from step 1 a single object only containing fields
270+
* common to each type in the union. To determine field "equality" we compare the field names and
271+
* recursively compare the field types:
242272
* */
243273

244274
// types is an array mapping each field name to it's respective output type
245-
const types: FieldMap[] = unionType.getTypes().map((objectType: GraphQLObjectType) => {
246-
const fields: GraphQLFieldMap<unknown, unknown> = objectType.getFields();
247-
248-
const fieldMap: FieldMap = {};
249-
Object.keys(fields).forEach((field: string) => {
250-
// Get the weight of this field on from parent type on the root typeWeight object.
251-
// this only exists for scalars and lists (which resolve to a function);
252-
const { weight, resolveTo } = result[objectType.name.toLowerCase()].fields[field];
253-
254-
fieldMap[field] = {
255-
type: fields[field].type,
256-
weight, // will only be undefined for object types
257-
resolveTo,
258-
};
259-
});
260-
return fieldMap;
261-
});
275+
// const typesInUnion = getFieldsForUnionType(unionType, typeWeightObject);
276+
const typesInUnion: FieldMap[] = getFieldsForUnionType(unionType, typeWeightObject);
262277

263-
const common: FieldMap = types.reduce((prev: FieldMap, fieldMap: FieldMap): FieldMap => {
264-
// iterate through the field map checking the types for any common field names
265-
const commonFields: FieldMap = {};
266-
Object.keys(prev).forEach((field: string) => {
267-
if (fieldMap[field]) {
268-
if (compareTypes(prev[field].type, fieldMap[field].type)) {
269-
// they match add the type to the next set
270-
commonFields[field] = prev[field];
271-
}
272-
}
273-
});
274-
return commonFields;
275-
});
278+
// reduce the data for all the types in the union
279+
const commonFields: FieldMap = getSharedFieldsFromUnionTypes(typesInUnion);
276280

277-
// transform commonFields into the correct format
281+
// transform commonFields into the correct format for the type weight object
278282
const fieldTypes: Fields = {};
279283

280-
Object.keys(common).forEach((field: string) => {
281-
// scalar => weight
282-
// list => resolveTo + weight(function)
283-
// fields that resolve to objects do not need to appear on the union type
284-
const current = common[field].type;
284+
Object.keys(commonFields).forEach((field: string) => {
285+
/**
286+
* The type weight object requires that:
287+
* a. scalars have a weight
288+
* b. lists have a resolveTo and weight property
289+
* c. objects have a resolveTo type.
290+
* */
291+
292+
const current = commonFields[field].type;
285293
if (isScalarType(current)) {
286294
fieldTypes[field] = {
287-
weight: common[field].weight,
295+
weight: commonFields[field].weight,
288296
};
289297
} else if (isObjectType(current) || isInterfaceType(current) || isUnionType(current)) {
290298
fieldTypes[field] = {
291-
resolveTo: common[field].resolveTo,
299+
resolveTo: commonFields[field].resolveTo,
292300
weight: typeWeights.object,
293301
};
294302
} else if (isListType(current)) {
295303
fieldTypes[field] = {
296-
resolveTo: common[field].resolveTo,
297-
weight: common[field].weight,
304+
resolveTo: commonFields[field].resolveTo,
305+
weight: commonFields[field].weight,
298306
};
299307
} else if (isNonNullType(current)) {
300308
throw new Error('non null types not supported on unions');
@@ -303,13 +311,52 @@ function parseTypes(schema: GraphQLSchema, typeWeights: TypeWeightSet): TypeWeig
303311
throw new Error('Unhandled union type. Should never get here');
304312
}
305313
});
306-
result[unionType.name.toLowerCase()] = {
314+
typeWeightsWithUnions[unionType.name.toLowerCase()] = {
307315
fields: fieldTypes,
308316
weight: typeWeights.object,
309317
};
310318
});
311319

312-
return result;
320+
return typeWeightsWithUnions;
321+
}
322+
/**
323+
* Parses all types in the provided schema object excempt for Query, Mutation
324+
* and built in types that begin with '__' and outputs a new TypeWeightObject
325+
* @param schema
326+
* @param typeWeights
327+
* @returns
328+
*/
329+
function parseTypes(schema: GraphQLSchema, typeWeights: TypeWeightSet): TypeWeightObject {
330+
const typeMap: ObjMap<GraphQLNamedType> = schema.getTypeMap();
331+
332+
const result: TypeWeightObject = {};
333+
334+
const unions: GraphQLUnionType[] = [];
335+
336+
// Handle Object, Interface, Enum and Union types
337+
Object.keys(typeMap).forEach((type) => {
338+
const typeName: string = type.toLowerCase();
339+
const currentType: GraphQLNamedType = typeMap[type];
340+
341+
// Get all types that aren't Query or Mutation or a built in type that starts with '__'
342+
if (!type.startsWith('__')) {
343+
if (isObjectType(currentType) || isInterfaceType(currentType)) {
344+
// Add the type and it's associated fields to the result
345+
result[typeName] = parseObjectFields(currentType, result, typeWeights);
346+
} else if (isEnumType(currentType)) {
347+
result[typeName] = {
348+
fields: {},
349+
weight: typeWeights.scalar,
350+
};
351+
} else if (isUnionType(currentType)) {
352+
unions.push(currentType);
353+
} else if (!isScalarType(currentType)) {
354+
throw new Error(`ERROR: buildTypeWeight: Unsupported type: ${currentType}`);
355+
}
356+
}
357+
});
358+
359+
return parseUnionTypes(unions, typeWeights, result);
313360
}
314361

315362
/**

0 commit comments

Comments
 (0)