Skip to content

Commit

Permalink
Merge pull request #90 from NeedleInAJayStack/fix/wrapped-aliased-typ…
Browse files Browse the repository at this point in the history
…e-resolution

Fix: Wrapped Aliased Type Resolution
  • Loading branch information
NeedleInAJayStack authored Jan 17, 2023
2 parents 7d83cdb + 550fe0f commit b2ebace
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 14 deletions.
28 changes: 21 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: sinoru/actions-setup-swift@v2
with:
swift-version: '5.6.1'
- uses: swift-actions/setup-swift@v1
- name: GitHub Action for SwiftFormat
uses: CassiusPacheco/[email protected]
with:
Expand All @@ -40,17 +38,33 @@ jobs:
coverageCommand: swift test --enable-test-discovery --enable-code-coverage
coverageLocations: ${{ env.codecov_path }}:lcov-json

linux:
name: Build and test ${{ matrix.swift }} on ${{ matrix.os }}
# ubuntu-latest is ubuntu-22.04 currently. Swift versions older than 5.7 don't have builds for 22.04. https://www.swift.org/download/
ubuntu-old:
name: Build ${{ matrix.swift }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-20.04]
swift: ["5.4", "5.5", "5.6"]
steps:
- uses: swift-actions/setup-swift@v1
with:
swift-version: ${{ matrix.swift }}
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Test
run: swift test

ubuntu-latest:
name: Build ${{ matrix.swift }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
swift: ["5.7"]
steps:
- uses: swift-actions/setup-swift@v1
with:
swift-version: ${{ matrix.swift }}
- uses: actions/checkout@v3
- name: Test
run: swift test
1 change: 1 addition & 0 deletions Sources/Graphiti/Component/Component.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ open class Component<Resolver, Context> {
}

func update(typeProvider _: SchemaTypeProvider, coders _: Coders) throws {}
func setGraphQLName(typeProvider _: SchemaTypeProvider) throws {}
}

public extension Component {
Expand Down
25 changes: 23 additions & 2 deletions Sources/Graphiti/Definition/TypeProvider.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import GraphQL

protocol TypeProvider: AnyObject {
var graphQLNameMap: [AnyType: String] { get set }
var graphQLTypeMap: [AnyType: GraphQLType] { get set }
}

Expand All @@ -9,6 +10,22 @@ extension TypeProvider {
graphQLTypeMap[AnyType(type)] != nil
}

func mapName(_ type: Any.Type, to name: String) throws {
guard !(type is Void.Type) else {
return
}

let key = AnyType(type)

guard graphQLNameMap[key] == nil else {
throw GraphQLError(
message: "Duplicate type registration for GraphQL type name \"\(name)\" while trying to register type \(Reflection.name(for: type))"
)
}

graphQLNameMap[key] = name
}

func map(_ type: Any.Type, to graphQLType: GraphQLType) throws {
guard !(type is Void.Type) else {
return
Expand Down Expand Up @@ -50,7 +67,7 @@ extension TypeProvider {
isOptional: isOptional
)
case .reference:
let name = Reflection.name(for: type.wrappedType)
let name = getGraphQLName(of: type.wrappedType)
let referenceType = GraphQLTypeReference(name)

return try getGraphQLOptionalType(from: referenceType, isOptional: isOptional)
Expand All @@ -60,7 +77,7 @@ extension TypeProvider {
return try getGraphQLOptionalType(from: graphQLType, isOptional: isOptional)
} else {
// If we haven't seen this type yet, just store it as a type reference and resolve later.
let name = Reflection.name(for: type)
let name = getGraphQLName(of: type)
let referenceType = GraphQLTypeReference(name)

return try getGraphQLOptionalType(from: referenceType, isOptional: isOptional)
Expand Down Expand Up @@ -238,4 +255,8 @@ extension TypeProvider {

return objectType
}

private func getGraphQLName(of type: Any.Type) -> String {
return graphQLNameMap[AnyType(type)] ?? Reflection.name(for: type)
}
}
4 changes: 4 additions & 0 deletions Sources/Graphiti/Enum/Enum.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public final class Enum<
typeProvider.types.append(enumType)
}

override func setGraphQLName(typeProvider: SchemaTypeProvider) throws {
try typeProvider.mapName(EnumType.self, to: name)
}

private init(
type _: EnumType.Type,
name: String?,
Expand Down
4 changes: 4 additions & 0 deletions Sources/Graphiti/Input/Input.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public final class Input<
typeProvider.types.append(inputObjectType)
}

override func setGraphQLName(typeProvider: SchemaTypeProvider) throws {
try typeProvider.mapName(InputObjectType.self, to: name)
}

func fields(typeProvider: TypeProvider) throws -> InputObjectFieldMap {
var map: InputObjectFieldMap = [:]

Expand Down
4 changes: 4 additions & 0 deletions Sources/Graphiti/Interface/Interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public final class Interface<Resolver, Context, InterfaceType>: Component<Resolv
typeProvider.types.append(interfaceType)
}

override func setGraphQLName(typeProvider: SchemaTypeProvider) throws {
try typeProvider.mapName(InterfaceType.self, to: name)
}

func fields(typeProvider: TypeProvider, coders: Coders) throws -> GraphQLFieldMap {
var map: GraphQLFieldMap = [:]

Expand Down
4 changes: 4 additions & 0 deletions Sources/Graphiti/Scalar/Scalar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ open class Scalar<Resolver, Context, ScalarType: Codable>: Component<Resolver, C
typeProvider.types.append(scalarType)
}

override func setGraphQLName(typeProvider: SchemaTypeProvider) throws {
try typeProvider.mapName(ScalarType.self, to: name)
}

open func serialize(scalar: ScalarType, encoder: MapEncoder) throws -> Map {
try encoder.encode(scalar)
}
Expand Down
6 changes: 6 additions & 0 deletions Sources/Graphiti/Schema/Schema.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ public final class Schema<Resolver, Context> {
) throws {
let typeProvider = SchemaTypeProvider()

// Collect types mappings first
for component in components {
try component.setGraphQLName(typeProvider: typeProvider)
}

// Then build up GraphQLTypes
for component in components {
try component.update(typeProvider: typeProvider, coders: coders)
}
Expand Down
7 changes: 7 additions & 0 deletions Sources/Graphiti/Schema/SchemaTypeProvider.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import GraphQL

final class SchemaTypeProvider: TypeProvider {
var graphQLNameMap: [AnyType: String] = [
AnyType(Int.self): "Int",
AnyType(Double.self): "Float",
AnyType(String.self): "String",
AnyType(Bool.self): "Boolean",
]

var graphQLTypeMap: [AnyType: GraphQLType] = [
AnyType(Int.self): GraphQLInt,
AnyType(Double.self): GraphQLFloat,
Expand Down
4 changes: 4 additions & 0 deletions Sources/Graphiti/Type/Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ public final class Type<Resolver, Context, ObjectType: Encodable>: Component<Res
typeProvider.types.append(objectType)
}

override func setGraphQLName(typeProvider: SchemaTypeProvider) throws {
try typeProvider.mapName(ObjectType.self, to: name)
}

func fields(typeProvider: TypeProvider, coders: Coders) throws -> GraphQLFieldMap {
var map: GraphQLFieldMap = [:]

Expand Down
4 changes: 4 additions & 0 deletions Sources/Graphiti/Union/Union.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public final class Union<Resolver, Context, UnionType>: Component<Resolver, Cont
try typeProvider.map(UnionType.self, to: unionType)
}

override func setGraphQLName(typeProvider: SchemaTypeProvider) throws {
try typeProvider.mapName(UnionType.self, to: name)
}

init(
type _: UnionType.Type,
name: String? = nil,
Expand Down
10 changes: 5 additions & 5 deletions Tests/GraphitiTests/HelloWorldTests/HelloWorldAsyncTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import XCTest

#if compiler(>=5.5) && canImport(_Concurrency)

@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *)
@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
let pubsub = SimplePubSub<User>()

@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *)
@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
extension HelloResolver {
func asyncHello(
context: HelloContext,
Expand Down Expand Up @@ -41,7 +41,7 @@ import XCTest
}
}

@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *)
@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
// Same as the HelloAPI, except with an async query and a few subscription fields
struct HelloAsyncAPI: API {
typealias ContextType = HelloContext
Expand Down Expand Up @@ -122,7 +122,7 @@ import XCTest
}
}

@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *)
@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
class HelloWorldAsyncTests: XCTestCase {
private let api = HelloAsyncAPI()
private var group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
Expand Down Expand Up @@ -320,7 +320,7 @@ import XCTest
}
}

@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *)
@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *)
/// A very simple publish/subscriber used for testing
class SimplePubSub<T> {
private var subscribers: [Subscriber<T>]
Expand Down
69 changes: 69 additions & 0 deletions Tests/GraphitiTests/SchemaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,75 @@ class SchemaTests: XCTestCase {
])
)
}

// Tests that we can resolve type references for named types
func testTypeReferenceForNamedType() throws {
struct LocationObject: Codable {
let id: String
let name: String
}

struct User: Codable {
let id: String
let location: LocationObject?
}

struct TestResolver {
func user(context _: NoContext, arguments _: NoArguments) -> User {
return User(
id: "user1",
location: LocationObject(
id: "location1",
name: "Earth"
)
)
}
}

let testSchema = try Schema<TestResolver, NoContext> {
Type(User.self) {
Field("id", at: \.id)
Field("location", at: \.location)
}
Type(LocationObject.self, as: "Location") {
Field("id", at: \.id)
Field("name", at: \.name)
}
Query {
Field("user", at: TestResolver.user)
}
}
let api = TestAPI<TestResolver, NoContext>(
resolver: TestResolver(),
schema: testSchema
)

let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
defer { try? group.syncShutdownGracefully() }

XCTAssertEqual(
try api.execute(
request: """
query {
user {
location {
name
}
}
}
""",
context: NoContext(),
on: group
).wait(),
GraphQLResult(data: [
"user": [
"location": [
"name": "Earth",
],
],
])
)
}
}

private class TestAPI<Resolver, ContextType>: API {
Expand Down

0 comments on commit b2ebace

Please sign in to comment.