Skip to content

Commit

Permalink
Adding standardconnection protocol API for paging
Browse files Browse the repository at this point in the history
  • Loading branch information
nerdsupremacist committed Mar 23, 2020
1 parent bca3a09 commit a6bc373
Show file tree
Hide file tree
Showing 7 changed files with 391 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

import Foundation
import NIO
import GraphQL
import ContextKit

public protocol ConnectionProtocol: OutputResolvable, ConcreteResolvable {
associatedtype Node
associatedtype Edge: EdgeProtocol = StandardEdge<Node> where Edge.Node == Node

func totalCount() -> EventLoopFuture<Int>
func pageInfo(first: Int?, after: String?, last: Int?, before: String?, eventLoop: EventLoopGroup) -> EventLoopFuture<PageInfo>
func edges(first: Int?, after: String?, last: Int?, before: String?, eventLoop: EventLoopGroup) -> EventLoopFuture<[Edge?]?>
}

extension ConnectionProtocol {

public static var concreteTypeName: String {
return "\(Node.concreteTypeName)\(String(describing: Self.self))"
}

public static var additionalArguments: [String : InputResolvable.Type] {
return ConnectionWrapper<Self>.additionalArguments
}

public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType {
return try context.resolve(type: ConnectionWrapper<Self>.self)
}

public func resolve(source: Any,
arguments: [String : Map],
context: MutableContext,
eventLoop: EventLoopGroup) throws -> EventLoopFuture<Any?> {

let first = try arguments["first"]?.intValue(converting: true)
let after = try arguments["after"]?.stringValue(converting: true)
let last = try arguments["last"]?.intValue(converting: true)
let before = try arguments["before"]?.stringValue(converting: true)

let connection = ConnectionWrapper(connection: self,
eventLoop: eventLoop,
first: first,
after: after,
last: last,
before: before)

return eventLoop.next().makeSucceededFuture(connection)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

import Foundation
import NIO
import GraphQL
import ContextKit

class ConnectionWrapper<Connection: ConnectionProtocol> {
private let connection: Connection
private let eventLoop: EventLoopGroup

private let first: Int?
private let after: String?
private let last: Int?
private let before: String?

init(connection: Connection,
eventLoop: EventLoopGroup,
first: Int?,
after: String?,
last: Int?,
before: String?) {

self.connection = connection
self.eventLoop = eventLoop
self.first = first
self.after = after
self.last = last
self.before = before
}
}

extension ConnectionWrapper {

private func pageInfo() -> EventLoopFuture<PageInfo> {
return connection.pageInfo(first: first, after: after, last: last, before: before, eventLoop: eventLoop)
}

private func edges() -> EventLoopFuture<[Connection.Edge?]?> {
return connection.edges(first: first, after: after, last: last, before: before, eventLoop: eventLoop)
}

private func totalCount() -> EventLoopFuture<Int> {
return connection.totalCount()
}

}

extension ConnectionWrapper: ConcreteResolvable {

static var concreteTypeName: String {
return Connection.concreteTypeName
}

}

extension ConnectionWrapper: OutputResolvable {

static var additionalArguments: [String : InputResolvable.Type] {
return [
"first" : Int?.self,
"after" : String?.self,
"last" : Int?.self,
"before" : String?.self,
]
}

static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType {
context.append(type: GraphQLNonNull(GraphQLTypeReference(concreteTypeName)), as: concreteTypeName)

let fields = [
"pageInfo" : GraphQLField(type: try context.resolve(type: PageInfo.self)) { (receiver, args, context, eventLoop, _) -> Future<Any?> in
return (receiver as! ConnectionWrapper<Connection>)
.pageInfo()
.resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop)
},
"edges" : GraphQLField(type: try context.resolve(type: [Connection.Edge?]?.self)) { (receiver, args, context, eventLoop, _) -> Future<Any?> in
return (receiver as! ConnectionWrapper<Connection>)
.edges()
.resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop)
},
"totalCount" : GraphQLField(type: try context.resolve(type: Int.self)) { (receiver, args, context, eventLoop, _) -> Future<Any?> in
return (receiver as! ConnectionWrapper<Connection>)
.totalCount()
.resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop)
},
]

return GraphQLNonNull(
try GraphQLObjectType(name: concreteTypeName, fields: fields)
)
}

func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> EventLoopFuture<Any?> {
return eventLoop.next().makeSucceededFuture(self)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

import Foundation
import NIO
import GraphQL
import ContextKit

public protocol ContextBasedConnection: class, OutputResolvable, ConcreteResolvable {
associatedtype Node
associatedtype Edge: EdgeProtocol = StandardEdge<Node> where Edge.Node == Node
associatedtype Context

func context(first: Int?, after: String?, last: Int?, before: String?, eventLoop: EventLoopGroup) -> EventLoopFuture<Context>
func totalCount(context: Context, eventLoop: EventLoopGroup) -> EventLoopFuture<Int>
func pageInfo(context: Context, eventLoop: EventLoopGroup) -> EventLoopFuture<PageInfo>
func edges(context: Context, eventLoop: EventLoopGroup) -> EventLoopFuture<[Edge?]?>
}

extension ContextBasedConnection {

public static var concreteTypeName: String {
return "\(Node.concreteTypeName)\(String(describing: Self.self))"
}

public static var additionalArguments: [String : InputResolvable.Type] {
return ContextBasedConnectionWrapper<Self>.additionalArguments
}

public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType {
return try context.resolve(type: ContextBasedConnectionWrapper<Self>.self)
}

public func resolve(source: Any,
arguments: [String : Map],
context: MutableContext,
eventLoop: EventLoopGroup) throws -> EventLoopFuture<Any?> {

let first = try arguments["first"]?.intValue(converting: true)
let after = try arguments["after"]?.stringValue(converting: true)
let last = try arguments["last"]?.intValue(converting: true)
let before = try arguments["before"]?.stringValue(converting: true)

let connection = ContextBasedConnectionWrapper(connection: self,
eventLoop: eventLoop,
first: first,
after: after,
last: last,
before: before)

return eventLoop.next().makeSucceededFuture(connection)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@

import Foundation
import NIO
import GraphQL
import ContextKit

class ContextBasedConnectionWrapper<Connection: ContextBasedConnection> {
private let connection: Connection
private let eventLoop: EventLoopGroup

private var cachedContext: EventLoopFuture<Connection.Context>?
private let first: Int?
private let after: String?
private let last: Int?
private let before: String?

init(connection: Connection,
eventLoop: EventLoopGroup,
first: Int?,
after: String?,
last: Int?,
before: String?) {

self.connection = connection
self.eventLoop = eventLoop
self.first = first
self.after = after
self.last = last
self.before = before
}
}

extension ContextBasedConnectionWrapper {

private func context() -> EventLoopFuture<Connection.Context> {
if let context = cachedContext {
return context
}

let context = connection.context(first: first, after: after, last: last, before: before, eventLoop: eventLoop)
cachedContext = context
return context
}

private func pageInfo() -> EventLoopFuture<PageInfo> {
return context().flatMap { self.connection.pageInfo(context: $0, eventLoop: self.eventLoop) }
}

private func edges() -> EventLoopFuture<[Connection.Edge?]?> {
return context().flatMap { self.connection.edges(context: $0, eventLoop: self.eventLoop) }
}

private func totalCount() -> EventLoopFuture<Int> {
return context().flatMap { self.connection.totalCount(context: $0, eventLoop: self.eventLoop) }
}

}

extension ContextBasedConnectionWrapper: ConcreteResolvable {

static var concreteTypeName: String {
return Connection.concreteTypeName
}

}

extension ContextBasedConnectionWrapper: OutputResolvable {

static var additionalArguments: [String : InputResolvable.Type] {
return [
"first" : Int?.self,
"after" : String?.self,
"last" : Int?.self,
"before" : String?.self,
]
}

static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType {
context.append(type: GraphQLNonNull(GraphQLTypeReference(concreteTypeName)), as: concreteTypeName)

let fields = [
"pageInfo" : GraphQLField(type: try context.resolve(type: PageInfo.self)) { (receiver, args, context, eventLoop, _) -> Future<Any?> in
return (receiver as! ContextBasedConnectionWrapper<Connection>)
.pageInfo()
.resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop)
},
"edges" : GraphQLField(type: try context.resolve(type: [Connection.Edge?]?.self)) { (receiver, args, context, eventLoop, _) -> Future<Any?> in
return (receiver as! ContextBasedConnectionWrapper<Connection>)
.edges()
.resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop)
},
"totalCount" : GraphQLField(type: try context.resolve(type: Int.self)) { (receiver, args, context, eventLoop, _) -> Future<Any?> in
return (receiver as! ContextBasedConnectionWrapper<Connection>)
.totalCount()
.resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop)
},
]

return GraphQLNonNull(
try GraphQLObjectType(name: concreteTypeName, fields: fields)
)
}

func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> EventLoopFuture<Any?> {
return eventLoop.next().makeSucceededFuture(self)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

import Foundation

public protocol EdgeProtocol: OutputResolvable {
associatedtype Node: OutputResolvable & ConcreteResolvable

var node: Node? { get }
var cursor: String { get }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

import Foundation

public class PageInfo: GraphQLObject {
let hasNextPage: Bool
let hasPreviousPage: Bool

let startCursor: String?
let endCursor: String?

public init(hasNextPage: Bool,
hasPreviousPage: Bool,
startCursor: String?,
endCursor: String?) {

self.hasNextPage = hasNextPage
self.hasPreviousPage = hasPreviousPage
self.startCursor = startCursor
self.endCursor = endCursor
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

import Foundation
import NIO
import GraphQL
import ContextKit

public struct StandardEdge<Node: OutputResolvable & ConcreteResolvable>: EdgeProtocol {
public let node: Node?
public let cursor: String

public init(node: Node?, cursor: String) {
self.node = node
self.cursor = cursor
}
}

extension StandardEdge {
public static var concreteTypeName: String {
return "\(Node.concreteTypeName)Edge"
}
}

extension StandardEdge {

public static var additionalArguments: [String : InputResolvable.Type] {
return Node?.additionalArguments
}

public static func resolve(using context: inout Resolution.Context) throws -> GraphQLOutputType {
context.append(type: GraphQLNonNull(GraphQLTypeReference(concreteTypeName)), as: concreteTypeName)

let fields = [
"node" : GraphQLField(type: try context.resolve(type: Optional<Node>.self)) { (receiver, args, context, eventLoop, _) -> Future<Any?> in
return try (receiver as! StandardEdge<Node>)
.node
.resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext, eventLoop: eventLoop)
},
"cursor" : GraphQLField(type: try context.resolve(type: String.self)) { (receiver, args, context, eventLoop, _) -> Future<Any?> in
return (receiver as! StandardEdge<Node>)
.cursor
.resolve(source: receiver, arguments: try args.dictionaryValue(), context: context as! MutableContext,eventLoop: eventLoop)
},
]

return GraphQLNonNull(
try GraphQLObjectType(name: concreteTypeName, fields: fields)
)
}

public func resolve(source: Any, arguments: [String : Map], context: MutableContext, eventLoop: EventLoopGroup) throws -> EventLoopFuture<Any?> {
return eventLoop.next().makeSucceededFuture(self)
}

}

0 comments on commit a6bc373

Please sign in to comment.