Skip to content

Commit ec74172

Browse files
fix: Adjusts schema init to js reference
1 parent c43d273 commit ec74172

File tree

2 files changed

+54
-129
lines changed

2 files changed

+54
-129
lines changed

Sources/GraphQL/Type/Schema.swift

Lines changed: 54 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public final class GraphQLSchema {
4141
public let subscriptionType: GraphQLObjectType?
4242
public let directives: [GraphQLDirective]
4343
public let typeMap: TypeMap
44-
public let implementations: [String: InterfaceImplementations]
44+
public internal(set) var implementations: [String: InterfaceImplementations]
4545
private var subTypeMap: [String: [String: Bool]] = [:]
4646

4747
public init(
@@ -70,58 +70,92 @@ public final class GraphQLSchema {
7070
// Provide specified directives (e.g. @include and @skip) by default.
7171
self.directives = directives.isEmpty ? specifiedDirectives : directives
7272

73-
// Build type map now to detect any errors within this schema.
74-
75-
var typeMap = TypeMap()
76-
7773
// To preserve order of user-provided types, we add first to add them to
7874
// the set of "collected" types, so `collectReferencedTypes` ignore them.
75+
var allReferencedTypes = TypeMap()
7976
for type in types {
80-
typeMap[type.name] = type
77+
allReferencedTypes[type.name] = type
8178
}
8279
if !types.isEmpty {
8380
for type in types {
8481
// When we ready to process this type, we remove it from "collected" types
8582
// and then add it together with all dependent types in the correct position.
86-
typeMap[type.name] = nil
87-
typeMap = try typeMapReducer(typeMap: typeMap, type: type)
83+
allReferencedTypes[type.name] = nil
84+
allReferencedTypes = try typeMapReducer(typeMap: allReferencedTypes, type: type)
8885
}
8986
}
9087

9188
if let query = queryType {
92-
typeMap = try typeMapReducer(typeMap: typeMap, type: query)
89+
allReferencedTypes = try typeMapReducer(typeMap: allReferencedTypes, type: query)
9390
}
9491

9592
if let mutation = mutationType {
96-
typeMap = try typeMapReducer(typeMap: typeMap, type: mutation)
93+
allReferencedTypes = try typeMapReducer(typeMap: allReferencedTypes, type: mutation)
9794
}
9895

9996
if let subscription = subscriptionType {
100-
typeMap = try typeMapReducer(typeMap: typeMap, type: subscription)
97+
allReferencedTypes = try typeMapReducer(typeMap: allReferencedTypes, type: subscription)
10198
}
10299

103100
for directive in self.directives {
104101
for arg in directive.args {
105-
typeMap = try typeMapReducer(typeMap: typeMap, type: arg.type)
102+
allReferencedTypes = try typeMapReducer(typeMap: allReferencedTypes, type: arg.type)
106103
}
107104
}
108105

109-
typeMap = try typeMapReducer(typeMap: typeMap, type: __Schema)
106+
allReferencedTypes = try typeMapReducer(typeMap: allReferencedTypes, type: __Schema)
107+
try replaceTypeReferences(typeMap: allReferencedTypes)
110108

111-
self.typeMap = typeMap
112-
try replaceTypeReferences(typeMap: typeMap)
109+
// Storing the resulting map for reference by the schema.
110+
var typeMap = TypeMap()
113111

114112
// Keep track of all implementations by interface name.
115113
implementations = try collectImplementations(types: Array(typeMap.values))
116114

117-
// Enforce correct interface implementations.
118-
for (_, type) in typeMap {
119-
if let object = type as? GraphQLObjectType {
120-
for interface in try object.getInterfaces() {
121-
try assert(object: object, implementsInterface: interface, schema: self)
115+
for namedType in allReferencedTypes.values {
116+
let typeName = namedType.name
117+
if typeMap[typeName] != nil {
118+
throw GraphQLError(
119+
message:
120+
"Schema must contain uniquely named types but contains multiple types named \"\(typeName)\"."
121+
)
122+
}
123+
typeMap[typeName] = namedType
124+
125+
if let namedType = namedType as? GraphQLInterfaceType {
126+
// Store implementations by interface.
127+
for iface in try namedType.getInterfaces() {
128+
let implementations = self.implementations[iface.name] ?? .init(
129+
objects: [],
130+
interfaces: []
131+
)
132+
133+
var interfaces = implementations.interfaces
134+
interfaces.append(namedType)
135+
self.implementations[iface.name] = .init(
136+
objects: implementations.objects,
137+
interfaces: interfaces
138+
)
139+
}
140+
} else if let namedType = namedType as? GraphQLObjectType {
141+
// Store implementations by objects.
142+
for iface in try namedType.getInterfaces() {
143+
let implementations = self.implementations[iface.name] ?? .init(
144+
objects: [],
145+
interfaces: []
146+
)
147+
148+
var objects = implementations.objects
149+
objects.append(namedType)
150+
self.implementations[iface.name] = .init(
151+
objects: objects,
152+
interfaces: implementations.interfaces
153+
)
122154
}
123155
}
124156
}
157+
158+
self.typeMap = typeMap
125159
}
126160

127161
convenience init(config: GraphQLSchemaNormalizedConfig) throws {
@@ -356,75 +390,6 @@ func typeMapReducer(typeMap: TypeMap, type: GraphQLType) throws -> TypeMap {
356390
return typeMap
357391
}
358392

359-
func assert(
360-
object: GraphQLObjectType,
361-
implementsInterface interface: GraphQLInterfaceType,
362-
schema _: GraphQLSchema
363-
) throws {
364-
let objectFieldMap = try object.getFields()
365-
let interfaceFieldMap = try interface.getFields()
366-
367-
for (fieldName, interfaceField) in interfaceFieldMap {
368-
guard let objectField = objectFieldMap[fieldName] else {
369-
throw GraphQLError(
370-
message:
371-
"\(interface.name) expects field \(fieldName) " +
372-
"but \(object.name) does not provide it."
373-
)
374-
}
375-
376-
// // Assert interface field type is satisfied by object field type, by being
377-
// // a valid subtype. (covariant)
378-
// guard try isTypeSubTypeOf(schema, objectField.type, interfaceField.type) else {
379-
// throw GraphQLError(
380-
// message:
381-
// "\(interface.name).\(fieldName) expects type \"\(interfaceField.type)\" " +
382-
// "but " +
383-
// "\(object.name).\(fieldName) provides type \"\(objectField.type)\"."
384-
// )
385-
// }
386-
387-
// Assert each interface field arg is implemented.
388-
for interfaceArg in interfaceField.args {
389-
let argName = interfaceArg.name
390-
guard let objectArg = objectField.args.find({ $0.name == argName }) else {
391-
throw GraphQLError(
392-
message:
393-
"\(interface.name).\(fieldName) expects argument \"\(argName)\" but " +
394-
"\(object.name).\(fieldName) does not provide it."
395-
)
396-
}
397-
398-
// Assert interface field arg type matches object field arg type.
399-
// (invariant)
400-
guard isEqualType(interfaceArg.type, objectArg.type) else {
401-
throw GraphQLError(
402-
message:
403-
"\(interface.name).\(fieldName)(\(argName):) expects type " +
404-
"\"\(interfaceArg.type)\" but " +
405-
"\(object.name).\(fieldName)(\(argName):) provides type " +
406-
"\"\(objectArg.type)\"."
407-
)
408-
}
409-
}
410-
411-
// Assert additional arguments must not be required.
412-
for objectArg in objectField.args {
413-
let argName = objectArg.name
414-
if
415-
interfaceField.args.find({ $0.name == argName }) == nil,
416-
isRequiredArgument(objectArg)
417-
{
418-
throw GraphQLError(
419-
message:
420-
"\(object.name).\(fieldName) includes required argument (\(argName):) that is missing " +
421-
"from the Interface field \(interface.name).\(fieldName)."
422-
)
423-
}
424-
}
425-
}
426-
}
427-
428393
func replaceTypeReferences(typeMap: TypeMap) throws {
429394
for type in typeMap {
430395
if let typeReferenceContainer = type.value as? GraphQLTypeReferenceContainer {

Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -118,46 +118,6 @@ class GraphQLSchemaTests: XCTestCase {
118118
_ = try GraphQLSchema(query: object, types: [interface, object])
119119
}
120120

121-
func testAssertObjectImplementsInterfaceFailsWhenObjectFieldHasRequiredArgumentMissingInInterface(
122-
) throws {
123-
let interface = try GraphQLInterfaceType(
124-
name: "Interface",
125-
fields: [
126-
"fieldWithoutArg": GraphQLField(
127-
type: GraphQLInt,
128-
args: [:]
129-
),
130-
]
131-
)
132-
133-
let object = try GraphQLObjectType(
134-
name: "Object",
135-
fields: [
136-
"fieldWithoutArg": GraphQLField(
137-
type: GraphQLInt,
138-
args: [
139-
"addedRequiredArg": GraphQLArgument(type: GraphQLNonNull(GraphQLInt)),
140-
]
141-
),
142-
],
143-
interfaces: [interface],
144-
isTypeOf: { _, _, _ -> Bool in
145-
preconditionFailure("Should not be called")
146-
}
147-
)
148-
149-
do {
150-
_ = try GraphQLSchema(query: object, types: [interface, object])
151-
XCTFail("Expected errors when creating schema")
152-
} catch {
153-
let graphQLError = try XCTUnwrap(error as? GraphQLError)
154-
XCTAssertEqual(
155-
graphQLError.message,
156-
"Object.fieldWithoutArg includes required argument (addedRequiredArg:) that is missing from the Interface field Interface.fieldWithoutArg."
157-
)
158-
}
159-
}
160-
161121
func testAssertSchemaCircularReference() throws {
162122
let object1 = try GraphQLObjectType(
163123
name: "Object1"

0 commit comments

Comments
 (0)