From 80ff785b82e6d31a6e6afb2241f337861769b3a4 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 13 Oct 2017 13:05:39 -0500 Subject: [PATCH 1/3] add another test for type inference recursion, convert checking to use Set() --- ...fer-graphql-input-type-from-fields-test.js | 31 +++++++++++++++++++ .../infer-graphql-input-fields-from-fields.js | 8 ++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-from-fields-test.js b/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-from-fields-test.js index 50a67b53863d7..1c01e193d57c3 100644 --- a/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-from-fields-test.js +++ b/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-from-fields-test.js @@ -133,6 +133,37 @@ describe(`GraphQL Input args from fields, test-only`, () => { isStringInput(innerObjFields.foo.type) }) + it(`handles lists within lists`, async () => { + const Row = new GraphQLObjectType({ + name: `Row`, + fields: () => { + return { + cells: typeField(new GraphQLList(Cell)), + } + }, + }) + + const Cell = new GraphQLObjectType({ + name: `Cell`, + fields: () => { + return { + value: typeField(GraphQLInt), + } + }, + }) + + const fields = { + rows: typeField(new GraphQLList(Row)), + } + + expect(() => { + inferInputObjectStructureFromFields({ + fields, + typeName: `ListTypes`, + }) + }).not.toThrow() + }) + it(`protects against infinite recursion on circular definitions`, async () => { const TypeA = new GraphQLObjectType({ name: `TypeA`, diff --git a/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js b/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js index 28fc4d1f0bbab..b62ab91bbde7a 100644 --- a/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js +++ b/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js @@ -37,13 +37,13 @@ function makeNullable(type: GraphQLInputType): GraphQLNullableInputType { function convertToInputType( type: GraphQLType, - typeMap: any + typeMap: Set ): ?GraphQLInputType { // track types already processed in current tree, to avoid infinite recursion - if (typeMap[type.name]) { + if (typeMap.has(type)) { return null } - const nextTypeMap = { ...typeMap, [type.name]: true } + const nextTypeMap = new Set([...typeMap, type]) if (type instanceof GraphQLScalarType || type instanceof GraphQLEnumType) { return type @@ -169,7 +169,7 @@ export function inferInputObjectStructureFromFields({ const sort = [] _.each(fields, (fieldConfig, key) => { - const inputType = convertToInputType(fieldConfig.type, {}) + const inputType = convertToInputType(fieldConfig.type, new Set()) const inputFilter = inputType && convertToInputFilter(_.upperFirst(key), inputType) From 39e04be1fdde0e9016856e8d18c0689a98df3cde Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 13 Oct 2017 14:12:01 -0500 Subject: [PATCH 2/3] also test type inference recursion for objects with no further keys, and ID scalar --- ...fer-graphql-input-type-from-fields-test.js | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-from-fields-test.js b/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-from-fields-test.js index 1c01e193d57c3..1eaccefcc9a4f 100644 --- a/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-from-fields-test.js +++ b/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-from-fields-test.js @@ -2,6 +2,7 @@ const { GraphQLBoolean, GraphQLFloat, GraphQLInt, + GraphQLID, GraphQLNonNull, GraphQLString, GraphQLObjectType, @@ -23,6 +24,14 @@ function isIntInput(type) { }) } +function isIdInput(type) { + expect(type instanceof GraphQLInputObjectType).toBeTruthy() + expect(type.getFields()).toEqual({ + eq: { name: `eq`, type: GraphQLID }, + ne: { name: `ne`, type: GraphQLID }, + }) +} + function isStringInput(type) { expect(type instanceof GraphQLInputObjectType).toBeTruthy() expect(type.getFields()).toEqual({ @@ -169,7 +178,6 @@ describe(`GraphQL Input args from fields, test-only`, () => { name: `TypeA`, fields: () => { return { - foo: typeField(GraphQLInt), typeb: typeField(TypeB), } }, @@ -179,7 +187,7 @@ describe(`GraphQL Input args from fields, test-only`, () => { name: `TypeB`, fields: () => { return { - bar: typeField(GraphQLInt), + bar: typeField(GraphQLID), typea: typeField(TypeA), } }, @@ -206,17 +214,20 @@ describe(`GraphQL Input args from fields, test-only`, () => { expect(entryPointA instanceof GraphQLInputObjectType).toBeTruthy() expect(entryPointB instanceof GraphQLInputObjectType).toBeTruthy() - isIntInput(entryPointAFields.foo.type) - isIntInput(entryPointBFields.bar.type) + isIdInput(entryPointBFields.bar.type) // next level should also work, ie. typeA -> type B const childAB = entryPointAFields.typeb.type const childABFields = childAB.getFields() expect(childAB instanceof GraphQLInputObjectType).toBeTruthy() - isIntInput(childABFields.bar.type) + isIdInput(childABFields.bar.type) // circular level should not be here, ie. typeA -> typeB -> typeA expect(childABFields.typea).toBeUndefined() + + // in the other direction, from entryPointB -> typeA, the latter shouldn't exist, + // due to having no further non-circular fields to filter + expect(entryPointBFields.typea).toBeUndefined() }) it(`recovers from unknown output types`, async () => { From 3e96d498cc42b0268c7aa8e113236e0346eab5b2 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 13 Oct 2017 14:12:32 -0500 Subject: [PATCH 3/3] handle ID scalars in type inference, and ensure no fieldless object types --- .../infer-graphql-input-fields-from-fields.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js b/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js index b62ab91bbde7a..1a2aae6a945b1 100644 --- a/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js +++ b/packages/gatsby/src/schema/infer-graphql-input-fields-from-fields.js @@ -6,6 +6,7 @@ const { GraphQLString, GraphQLFloat, GraphQLInt, + GraphQLID, GraphQLList, GraphQLEnumType, GraphQLNonNull, @@ -48,12 +49,16 @@ function convertToInputType( if (type instanceof GraphQLScalarType || type instanceof GraphQLEnumType) { return type } else if (type instanceof GraphQLObjectType) { + const fields = _.transform(type.getFields(), (out, fieldConfig, key) => { + const type = convertToInputType(fieldConfig.type, nextTypeMap) + if (type) out[key] = { type } + }) + if (Object.keys(fields).length===0) { + return null + } return new GraphQLInputObjectType({ name: createTypeName(`${type.name}InputObject`), - fields: _.transform(type.getFields(), (out, fieldConfig, key) => { - const type = convertToInputType(fieldConfig.type, nextTypeMap) - if (type) out[key] = { type } - }), + fields, }) } else if (type instanceof GraphQLList) { let innerType = convertToInputType(type.ofType, nextTypeMap) @@ -85,6 +90,10 @@ const scalarFilterMap = { eq: { type: GraphQLFloat }, ne: { type: GraphQLFloat }, }, + ID: { + eq: { type: GraphQLID }, + ne: { type: GraphQLID }, + }, String: { eq: { type: GraphQLString }, ne: { type: GraphQLString },