Files
2022-04-12 07:02:02 +00:00

200 lines
7.9 KiB
Swift

//
// NetworkRouter.swift
// Cyberlock
//
// Created by Jura on 8/21/19.
// Copyright © 2019 Omicronmedia. All rights reserved.
//
import Foundation
public typealias NetworkResultCompletionClosure = (_ result: Result<Response, Error>) -> Void
public typealias NetworkResultDecodableCompletionClosure<T: Decodable> = (_ result: Result<(T, Response), Error>) -> Void
public enum StubBehavior {
case never
case immediate
case delayed(seconds: TimeInterval)
}
let responseContext = CodingUserInfoKey(rawValue: "response")!
final public class NetworkRouter {
private static var globalPlugins: [NetworkPluginType] = []
public var customHeaders: HTTPHeaders?
internal var environment: NetworkEnvironment?
internal var mirrorProvider = MirrorProvider()
private let plugins: [NetworkPluginType]
private var requests = [CancellationToken: Cancellable]()
public init(environment: NetworkEnvironment?, plugins: [NetworkPluginType] = []) {
self.environment = environment
self.plugins = plugins
}
convenience public init() {
self.init(environment: nil)
}
public static func addPlugin(_ plugin: NetworkPluginType) {
NetworkRouter.globalPlugins.append(plugin)
}
@discardableResult
public func request(_ service: ServiceType, stubBehavior: StubBehavior = .never, completion: @escaping NetworkResultCompletionClosure) -> CancellationToken? {
self.mirrorProvider.reset()
let mainThreadCompletion = { result in
DispatchQueue.main.async { completion(result) }
}
return self.sendRequest(service, stubBehavior: stubBehavior, completion: mainThreadCompletion)
}
@discardableResult
public func requestDecodable<T: Decodable>(_ decodableType: T.Type, keyPath: String? = nil, service: ServiceType, stubBehavior: StubBehavior = .never, completion: @escaping NetworkResultDecodableCompletionClosure<T>) -> CancellationToken? {
self.mirrorProvider.reset()
return self.sendRequest(service, stubBehavior: self.environment?.stub ?? stubBehavior) { result in
result.flatMap { response -> (T, Response) in
let decoder = JSONDecoder()
decoder.userInfo[responseContext] = response
if let keyPath = keyPath, keyPath.count > 0 {
decoder.userInfo[decodingContext] = keyPath
let decoded = try decoder.decode(ResponseResult<T>.self, from: response.data)
return (decoded.result, response)
} else {
let decoded = try decoder.decode(decodableType, from: response.data)
return (decoded, response)
}
} >>> { result in
DispatchQueue.main.async { completion(result) }
}
}
}
public func cancel(_ token: CancellationToken) {
guard let cancellable = self.requests[token] else { return }
synced(self) {
self.requests.removeValue(forKey: token)
}
cancellable.cancel()
}
public func cancelAllRequests() {
let cancellables = self.requests.values
synced(self) {
self.requests.removeAll()
}
cancellables.forEach { $0.cancel() }
}
// swiftlint:disable function_body_length
@discardableResult
private func sendRequest(_ service: ServiceType, stubBehavior: StubBehavior, token: String? = nil, completion: @escaping NetworkResultCompletionClosure) -> CancellationToken? {
let request: URLRequest
do {
request = try self.buildRequest(service, env: self.environment)
} catch {
completion(Result.failure(NetworkRouterError.invalidRequest))
return nil
}
let result: Result<URLRequest, NetworkRouterError> = NetworkRouter.networkShouldProcess(priority: NetworkRouter.globalPlugins,
secondary: self.plugins,
service: service,
data: request)
if let error = result.maybeError {
completion(Result.failure(error))
return nil
}
let modified: Result<URLRequest, Error> = NetworkRouter.networkModifyRequest(priority: NetworkRouter.globalPlugins,
secondary: self.plugins,
service: service,
request: request)
guard let netRequest = modified.optional else {
if let error = modified.maybeError {
completion(Result.failure(error))
} else {
completion(Result.failure(NetworkRouterError.cancelled))
}
return nil
}
let cancellable = NetworkTask(request: netRequest, token: token) { [weak self] token, result in
guard let self = self else {
completion(Result.failure(NetworkRouterError.cancelled))
return
}
synced(self) {
self.requests.removeValue(forKey: token)
}
guard let modified: Result<Response, Error> = NetworkRouter.networkModifyResponse(priority: self.plugins,
secondary: NetworkRouter.globalPlugins,
service: service,
result: result) else {
completion(Result.failure(NetworkRouterError.cancelled))
return
}
let processed = NetworkRouter.networkShouldProcess(priority: self.plugins, secondary: NetworkRouter.globalPlugins, service: service, data: modified)
if let error = processed.maybeError {
if let env = self.mirrorProvider.nextMirror() {
self.environment = env
self.sendRequest(service, stubBehavior: stubBehavior, token: token, completion: completion)
return
} else {
self.mirrorProvider.blockMirrors = true
completion(Result.failure(error))
return
}
}
guard let netResult = processed.optional else {
if let error = processed.maybeError {
completion(Result.failure(error))
} else {
completion(Result.failure(NetworkRouterError.cancelled))
}
return
}
completion(netResult)
}
synced(self) {
self.requests[cancellable.token] = cancellable
}
cancellable.start(with: stubBehavior, sampleResponse: self.environment?.sampleData(for: service))
return cancellable.token
}
// swiftlint:enable function_body_length
}
// MARK: - Synchronization
private func synced(_ lock: Any?, closure: () -> Void) {
guard let lock = lock else {
return
}
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}