やったこと
AWSAppSyncClientで頻出のfetch, mutate, subscribe処理をRxでラップして、
Single
やObservable
などのオペレータで扱えるようにしました。
AWSAppSyncClientでは内部でApolloClientをラップしているので、
AWSサービスなどを使わずにApolloをGraphQLしてRxで扱うケースと同じようなコードになるかなと思います。
コード
AWSAppSyncClient+Rx.swift
import Foundation
import AWSAppSync
import RxSwift
import SwiftyBeaver
let log = SwiftyBeaver.self
enum AppSyncError: Error {
case gqlErrors([GraphQLError])
case noData
case unknowned
}
extension AWSAppSyncClient: ReactiveCompatible {}
extension Reactive where Base: AWSAppSyncClient {
public func fetch<Query: GraphQLQuery>(
query: Query,
cachePolicy: CachePolicy = .returnCacheDataElseFetch,
queue: DispatchQueue = DispatchQueue.main
) -> Single<Query.Data> {
return Single.create { [weak base] observer in
log.debug("query did called - query: \(query)")
let cancellable = base?.fetch(query: query, cachePolicy: cachePolicy, queue: queue) {(result, error) in
if let error = error {
log.error("client connection error - error: \(error)")
observer(.error(error))
}
switch result {
case .none:
log.error("fetched result is nil")
observer(.error(AppSyncError.noData))
case .some(let result):
if let errors = result.errors {
log.error("GraphQL fetch error - errors: \(errors.map { $0.description } )")
observer(.error(AppSyncError.gqlErrors(errors)))
} else if let data = result.data {
observer(.success(data))
} else {
log.error("Unknowned error. result and error, both values are nil")
observer(.error(AppSyncError.unknowned))
}
}
}
return Disposables.create {
cancellable?.cancel()
}
}
}
public func perform<Mutation: GraphQLMutation>(
mutation: Mutation,
queue: DispatchQueue = .main
) -> Single<Mutation.Data> {
return Single.create { [weak base] observer in
log.debug("mutation did called - mutation: \(mutation)")
let cancellable = base?.perform(mutation: mutation, queue: queue, optimisticUpdate: nil, conflictResolutionBlock: nil, resultHandler: { (result, error) in
if let error = error {
log.error("client connection error - error: \(error)")
observer(.error(error))
}
switch result {
case .none:
assertionFailure("Unexpected error - in case result is nil, should return error before evaluating result value")
observer(.error(AppSyncError.resultIsNil)) // 一応エラーを返しておく
case .some(let result):
if let errors = result.errors {
log.error("GraphQL perform error - errors: \(errors.map { $0.description } )")
observer(.error(AppSyncError.gqlErrors(errors)))
} else if let data = result.data {
observer(.success(data))
} else {
log.error("perform unknowned error. result and error, both values are nil")
observer(.error(AppSyncError.unknowned))
}
}
})
return Disposables.create {
cancellable?.cancel()
}
}
}
public func subscribe<Subscription: GraphQLSubscription>(
subscription: Subscription,
queue: DispatchQueue = .main
) -> Observable<Subscription.Data> {
return Observable.create { [weak base] observer in
log.debug("subscription did called - subscription: \(subscription)")
var cancellable: Cancellable?
do {
cancellable = try base?.subscribe(subscription: subscription, queue: queue, statusChangeHandler: nil, resultHandler: { (result, _, error) in
if let error = error {
log.error("client connection error - error: \(error)")
observer.onError(error)
}
switch result {
case .none:
assertionFailure("Unexpected error - in case result is nil, should return error before evaluating result value")
observer.onError(AppSyncError.resultIsNil) // 一応エラーを返しておく
case .some(let result):
if let data = result.data {
log.debug("subscribed data: - \(data)")
observer.onNext(data)
} else if let errors = result.errors {
log.error("GraphQL subscribe error - errors: \(errors.map { $0.description } )")
observer.onError(AppSyncError.gqlErrors(errors))
}
}
})
} catch {
log.error("subscribe configurations error")
observer.onError(AppSyncError.unknowned) // base?.subscribe内でthrowされるがどんなerrorが投げられているのかは不明
cancellable?.cancel()
}
return Disposables.create {
cancellable?.cancel()
}
}
}
}