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 { 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]) + ) + } }