Skip to content

Commit

Permalink
Merge pull request #103 from NeedleInAJayStack/fix/federation-introsp…
Browse files Browse the repository at this point in the history
…ection

Fix: Federation Introspection
  • Loading branch information
NeedleInAJayStack committed Feb 17, 2023
2 parents 14f378c + b86c46a commit d8f83ee
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 65 deletions.
17 changes: 7 additions & 10 deletions Sources/Graphiti/Federation/Queries.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import GraphQL
import NIO

let resolveReferenceFieldName = "__resolveReference"

func serviceQuery(for sdl: String) -> GraphQLField {
return GraphQLField(
type: GraphQLNonNull(serviceType),
Expand All @@ -14,26 +12,25 @@ func serviceQuery(for sdl: String) -> GraphQLField {
)
}

func entitiesQuery(for federatedTypes: [GraphQLObjectType], entityType: GraphQLUnionType, coders: Coders) -> GraphQLField {
func entitiesQuery(
for federatedResolvers: [String: GraphQLFieldResolve],
entityType: GraphQLUnionType,
coders: Coders
) -> GraphQLField {
return GraphQLField(
type: GraphQLNonNull(GraphQLList(entityType)),
description: "Return all entities matching the provided representations.",
args: ["representations": GraphQLArgument(type: GraphQLList(anyType))],
args: ["representations": GraphQLArgument(type: GraphQLNonNull(GraphQLList(GraphQLNonNull(anyType))))],
resolve: { source, args, context, eventLoopGroup, info in
let arguments = try coders.decoder.decode(EntityArguments.self, from: args)
let futures: [EventLoopFuture<Any?>] = try arguments.representations.map { (representationMap: Map) in
let representation = try coders.decoder.decode(
EntityRepresentation.self,
from: representationMap
)
guard let type = federatedTypes.first(where: { value in value.name == representation.__typename }) else {
guard let resolve = federatedResolvers[representation.__typename] else {
throw GraphQLError(message: "Federated type not found: \(representation.__typename)")
}
guard let resolve = type.fields[resolveReferenceFieldName]?.resolve else {
throw GraphQLError(
message: "Federated type has no '__resolveReference' field resolver: \(type.name)"
)
}
return try resolve(
source,
representationMap,
Expand Down
6 changes: 5 additions & 1 deletion Sources/Graphiti/Query/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ public final class Query<Resolver, Context>: Component<Resolver, Context> {
typeProvider.types.append(entity)

// Add subgraph queries (_entities, _service)
queryFields["_entities"] = entitiesQuery(for: federatedTypes, entityType: entity, coders: coders)
queryFields["_entities"] = entitiesQuery(
for: typeProvider.federatedResolvers,
entityType: entity,
coders: coders
)
queryFields["_service"] = serviceQuery(for: sdl)
}

Expand Down
6 changes: 1 addition & 5 deletions Sources/Graphiti/Schema/SchemaTypeProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class SchemaTypeProvider: TypeProvider {
]

var federatedTypes: [GraphQLObjectType] = []
var federatedResolvers: [String: GraphQLFieldResolve] = [:]
var federatedSDL: String? = nil

var query: GraphQLObjectType?
Expand All @@ -28,9 +29,4 @@ final class SchemaTypeProvider: TypeProvider {
try map(type, to: graphQLType)
types.append(graphQLType)
}

func addFederated(type: Any.Type, as graphQLType: GraphQLObjectType) throws {
try add(type: type, as: graphQLType)
federatedTypes.append(graphQLType)
}
}
91 changes: 42 additions & 49 deletions Sources/Graphiti/Type/Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,20 @@ public final class Type<Resolver, Context, ObjectType: Encodable>: TypeComponent
}

override func update(typeProvider: SchemaTypeProvider, coders: Coders) throws {
var fieldDefs = try fields(typeProvider: typeProvider, coders: coders)
let fieldDefs = try fields(typeProvider: typeProvider, coders: coders)
let objectType = try GraphQLObjectType(
name: name,
description: description,
fields: fieldDefs,
interfaces: interfaces.map {
try typeProvider.getInterfaceType(from: $0)
},
isTypeOf: isTypeOf
)

try typeProvider.add(type: ObjectType.self, as: objectType)

// If federation keys are included, validate and create resolver closure
if !keys.isEmpty {
let fieldNames = Array(fieldDefs.keys)
for key in keys {
Expand All @@ -25,58 +37,39 @@ public final class Type<Resolver, Context, ObjectType: Encodable>: TypeComponent
)
}

fieldDefs[resolveReferenceFieldName] = GraphQLField(
type: GraphQLNonNull(GraphQLTypeReference(name)), // Self-referential
description: "Return the entity of this object type that matches the provided representation. Used by Query._entities.",
args: [
"representations": GraphQLArgument(type: GraphQLList(anyType))
],
resolve: { source, args, context, eventLoopGroup, info in
guard let s = source as? Resolver else {
throw GraphQLError(
message: "Expected source type \(ObjectType.self) but got \(type(of: source))"
)
}

guard let c = context as? Context else {
throw GraphQLError(
message: "Expected context type \(Context.self) but got \(type(of: context))"
)
}

let keyMatch = self.keys.first { key in
key.mapMatchesArguments(args, coders: coders)
}
guard let key = keyMatch else {
throw GraphQLError(
message: "No matching key was found for representation \(args)."
)
}

return try key.resolveMap(
resolver: s,
context: c,
map: args,
eventLoopGroup: eventLoopGroup,
coders: coders
let resolve: GraphQLFieldResolve = { source, args, context, eventLoopGroup, info in
guard let s = source as? Resolver else {
throw GraphQLError(
message: "Expected source type \(ObjectType.self) but got \(type(of: source))"
)
}
)
}

let objectType = try GraphQLObjectType(
name: name,
description: description,
fields: fieldDefs,
interfaces: interfaces.map {
try typeProvider.getInterfaceType(from: $0)
},
isTypeOf: isTypeOf
)

try typeProvider.add(type: ObjectType.self, as: objectType)
if !keys.isEmpty {
guard let c = context as? Context else {
throw GraphQLError(
message: "Expected context type \(Context.self) but got \(type(of: context))"
)
}

let keyMatch = self.keys.first { key in
key.mapMatchesArguments(args, coders: coders)
}
guard let key = keyMatch else {
throw GraphQLError(
message: "No matching key was found for representation \(args)."
)
}

return try key.resolveMap(
resolver: s,
context: c,
map: args,
eventLoopGroup: eventLoopGroup,
coders: coders
)
}

typeProvider.federatedTypes.append(objectType)
typeProvider.federatedResolvers[name] = resolve
}
}

Expand Down

0 comments on commit d8f83ee

Please sign in to comment.