From 283cc361d23d572920dec782967caeffb3d0db43 Mon Sep 17 00:00:00 2001 From: Jay Herron Date: Thu, 29 Sep 2022 18:30:47 -0600 Subject: [PATCH 1/2] test: Adds type reference resolution tests --- .../TypeTests/GraphQLSchemaTests.swift | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift b/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift index bc7c8b49..9de467a4 100644 --- a/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift +++ b/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift @@ -157,4 +157,56 @@ class GraphQLSchemaTests: XCTestCase { ) } } + + func testAssertSchemaCircularReference() throws { + let object1 = try GraphQLObjectType( + name: "Object1", + fields: [ + "object2": GraphQLField( + type: GraphQLTypeReference("Object2") + ), + ] + ) + let object2 = try GraphQLObjectType( + name: "Object2", + fields: [ + "object1": GraphQLField( + type: GraphQLTypeReference("Object1") + ), + ] + ) + let query = try GraphQLObjectType( + name: "Query", + fields: [ + "object1": GraphQLField(type: GraphQLTypeReference("Object1")), + "object2": GraphQLField(type: GraphQLTypeReference("Object2")), + ] + ) + + let schema = try GraphQLSchema(query: query, types: [object1, object2]) + for (_, graphQLNamedType) in schema.typeMap { + XCTAssertFalse(graphQLNamedType is GraphQLTypeReference) + } + } + + func testAssertSchemaFailsWhenObjectNotDefined() throws { + let object1 = try GraphQLObjectType( + name: "Object1", + fields: [ + "object2": GraphQLField( + type: GraphQLTypeReference("Object2") + ), + ] + ) + let query = try GraphQLObjectType( + name: "Query", + fields: [ + "object1": GraphQLField(type: GraphQLTypeReference("Object1")), + ] + ) + + XCTAssertThrowsError( + _ = try GraphQLSchema(query: query, types: [object1]) + ) + } } From d6a94dd6dfa481a9541aca67584154dcd9691c30 Mon Sep 17 00:00:00 2001 From: Jay Herron Date: Fri, 30 Sep 2022 10:18:12 -0600 Subject: [PATCH 2/2] fix: Fixes TypeReference resolutions --- Sources/GraphQL/Type/Schema.swift | 39 ++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/Sources/GraphQL/Type/Schema.swift b/Sources/GraphQL/Type/Schema.swift index 55d2ad82..d08f76bf 100644 --- a/Sources/GraphQL/Type/Schema.swift +++ b/Sources/GraphQL/Type/Schema.swift @@ -235,16 +235,28 @@ func typeMapReducer(typeMap: TypeMap, type: GraphQLType) throws -> TypeMap { return typeMap // Should never happen } - guard typeMap[type.name] == nil || typeMap[type.name] is GraphQLTypeReference else { - guard typeMap[type.name]! == type || type is GraphQLTypeReference else { - throw GraphQLError( - message: - "Schema must contain unique named types but contains multiple " + - "types named \"\(type.name)\"." - ) + if let existingType = typeMap[type.name] { + if existingType is GraphQLTypeReference { + if type is GraphQLTypeReference { + // Just short circuit because they're both type references + return typeMap + } + // Otherwise, fall through and override the type reference + } else { + if type is GraphQLTypeReference { + // Just ignore the reference and keep the concrete one + return typeMap + } else if !(existingType == type) { + throw GraphQLError( + message: + "Schema must contain unique named types but contains multiple " + + "types named \"\(type.name)\"." + ) + } else { + // Otherwise, it's already been defined so short circuit + return typeMap + } } - - return typeMap } typeMap[type.name] = type @@ -363,6 +375,15 @@ func replaceTypeReferences(typeMap: TypeMap) throws { try typeReferenceContainer.replaceTypeReferences(typeMap: typeMap) } } + + // Check that no type names map to TypeReferences. That is, they have all been resolved to actual types. + for (typeName, graphQLNamedType) in typeMap { + if graphQLNamedType is GraphQLTypeReference { + throw GraphQLError( + message: "Type \"\(typeName)\" was referenced but not defined." + ) + } + } } func resolveTypeReference(type: GraphQLType, typeMap: TypeMap) throws -> GraphQLType {