Skip to content

Commit

Permalink
Merge pull request #5 from wmcginty/delegatedNetworkService
Browse files Browse the repository at this point in the history
Abstract NetworkService functionality
  • Loading branch information
wmcginty authored Jan 28, 2018
2 parents 43ccb2f + b463037 commit 533f839
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 41 deletions.
10 changes: 10 additions & 0 deletions Sources/Hyperspace/HTTP/HTTP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ public struct HTTP {
public var dataString: String? {
return data.flatMap { String(data: $0, encoding: .utf8) }
}

/// Initialize a new HTTP.Response with any given HTTP status code and Data.
///
/// - Parameters:
/// - code: The raw HTTP status code for this response.
/// - data: The raw Data associated with the HTTP response, if any data was provided.
public init(code: Int, data: Data?) {
self.code = code
self.data = data
}
}
}
// swiftlint:enable nesting
Expand Down
4 changes: 2 additions & 2 deletions Sources/Hyperspace/Service/Backend/BackendService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class BackendService {
// MARK: - BackendService Conformance to BackendServiceProtocol

extension BackendService: BackendServiceProtocol {
public func execute<T: NetworkRequest, U>(request: T, completion: @escaping BackendServiceCompletion<U>) where T.ResponseType == U {
public func execute<T: NetworkRequest>(request: T, completion: @escaping BackendServiceCompletion<T.ResponseType>) {
networkService.execute(request: request.urlRequest) { [weak self] (result) in
switch result {
case .success(let result):
Expand All @@ -46,7 +46,7 @@ extension BackendService: BackendServiceProtocol {
networkService.cancelTask(for: request)
}

private func handleResponseData<T: NetworkRequest, U>(_ data: Data, for request: T, completion: @escaping BackendServiceCompletion<U>) where T.ResponseType == U {
private func handleResponseData<T: NetworkRequest>(_ data: Data, for request: T, completion: @escaping BackendServiceCompletion<T.ResponseType>) {
let transformResult = request.transformData(data)

DispatchQueue.main.async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ public protocol BackendServiceProtocol {
/// - Parameters:
/// - request: The NetworkRequest to be executed.
/// - completion: The completion block to invoke when execution has finished.
func execute<T: NetworkRequest, U>(request: T, completion: @escaping BackendServiceCompletion<U>) where T.ResponseType == U
func execute<T: NetworkRequest>(request: T, completion: @escaping BackendServiceCompletion<T.ResponseType>)

/// Executes the NetworkRequest, calling the provided typed error completion block when finished.
///
/// - Parameters:
/// - request: The NetworkRequest to be executed.
/// - completion: The completion block (with a specifcally typed error) to invoke when execution has finished.
func execute<T: NetworkRequest, U, E: BackendServiceErrorInitializable>(request: T, completion: @escaping TypedBackendServiceCompletion<U, E>) where T.ResponseType == U, T.ErrorType == E
func execute<T: NetworkRequest, E: BackendServiceErrorInitializable>(request: T, completion: @escaping TypedBackendServiceCompletion<T.ResponseType, E>) where T.ErrorType == E

/// Cancels the task for the given request (if it is currently running).
func cancelTask(for request: URLRequest)
Expand All @@ -57,8 +57,8 @@ public protocol BackendServiceProtocol {
// MARK: - Default Typed Implementation

extension BackendServiceProtocol {
public func execute<T: NetworkRequest, U, E: BackendServiceErrorInitializable>(request: T, completion: @escaping TypedBackendServiceCompletion<U, E>) where T.ResponseType == U, T.ErrorType == E {
execute(request: request) { (result: Result<U, BackendServiceError>) in
public func execute<T: NetworkRequest, E: BackendServiceErrorInitializable>(request: T, completion: @escaping TypedBackendServiceCompletion<T.ResponseType, E>) where T.ErrorType == E {
execute(request: request) { (result: Result<T.ResponseType, BackendServiceError>) in
completion(result.flatMapError { .failure(E($0)) })
}
}
Expand Down
93 changes: 63 additions & 30 deletions Sources/Hyperspace/Service/Network/NetworkService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
//

import Foundation
import Result

/// Adopts the NetworkServiceProtocol to perform HTTP communication via the execution of URLRequests.
public class NetworkService {
Expand All @@ -43,41 +44,17 @@ public class NetworkService {

extension NetworkService: NetworkServiceProtocol {
public func execute(request: URLRequest, completion: @escaping NetworkServiceCompletion) {
let task = session.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
let task = session.dataTask(with: request) { (data, response, error) in
guard let statusCode = (response as? HTTPURLResponse)?.statusCode else {
let responseError = self?.invalidHTTPResponseError(for: error) ?? .unknownError
let result = NetworkServiceFailure(error: responseError, response: nil)

completion(.failure(result))
let networkFailure = NetworkServiceHelper.networkServiceFailure(for: error)
completion(.failure(networkFailure))
return
}

let httpResponse = HTTP.Response(code: statusCode, data: data)

switch httpResponse.status {
case .unknown:
let result = NetworkServiceFailure(error: .unknownStatusCode, response: httpResponse)
completion(.failure(result))
case .success:
guard let data = data else {
let result = NetworkServiceFailure(error: .noData, response: httpResponse)
completion(.failure(result))
return
}

let result = NetworkServiceSuccess(data: data, response: httpResponse)
completion(.success(result))
case .redirection:
let result = NetworkServiceFailure(error: .redirection, response: httpResponse)
completion(.failure(result))
case .clientError(let clientError):
let result = NetworkServiceFailure(error: .clientError(clientError), response: httpResponse)
completion(.failure(result))
case .serverError(let serverError):
let result = NetworkServiceFailure(error: .serverError(serverError), response: httpResponse)
completion(.failure(result))
}
})
let networkResult = NetworkServiceHelper.networkServiceResult(for: httpResponse)
completion(networkResult)
}

tasks[request] = task
task.resume()
Expand All @@ -91,3 +68,59 @@ extension NetworkService: NetworkServiceProtocol {
tasks.forEach { cancelTask(for: $0.key) }
}
}

/// A small helper struct which deals with the conversion of Data, URLResponse and Error objects to NetworkService successes and failures.
public struct NetworkServiceHelper {

/// Used to convert a client error, such as those returned by a NetworkSessionDataTask, into a NetworkServiceResult.
///
/// - Parameter clientError: The error returned by the NetworkSessionDataTask.
/// - Returns: The outcome NetworkServiceResult dictated by the error.
public static func networkServiceFailure(for clientError: Error?) -> NetworkServiceFailure {
let responseError = invalidHTTPResponseError(for: clientError)
return NetworkServiceFailure(error: responseError, response: nil)
}

/// Used to convert a valid HTTP.Response object, such as those returned by a NetworkSessionDataTask, into a NetworkServiceResult.
///
/// - Parameter response: The HTTP.Response object returned by the NetworkSessionDataTask.
/// - Returns: The outcome NetworkServiceResult dictated by the HTTP.Response.
public static func networkServiceResult(for response: HTTP.Response) -> NetworkServiceResult {
switch response.status {
case .unknown:
return .failure(NetworkServiceFailure(error: .unknownStatusCode, response: response))
case .success:
guard let data = response.data else {
return .failure(NetworkServiceFailure(error: .noData, response: response))
}

return .success(NetworkServiceSuccess(data: data, response: response))
case .redirection:
return .failure(NetworkServiceFailure(error: .redirection, response: response))
case .clientError(let clientError):
return .failure(NetworkServiceFailure(error: .clientError(clientError), response: response))
case .serverError(let serverError):
return .failure(NetworkServiceFailure(error: .serverError(serverError), response: response))
}
}
}

private extension NetworkServiceHelper {

private static func invalidHTTPResponseError(for error: Error?) -> NetworkServiceError {
let networkError: NetworkServiceError = (error as NSError?).flatMap {
switch ($0.domain, $0.code) {
case (NSURLErrorDomain, NSURLErrorNotConnectedToInternet):
return .noInternetConnection
case (NSURLErrorDomain, NSURLErrorTimedOut):
return .timedOut
case (NSURLErrorDomain, NSURLErrorCancelled):
return .cancelled
default:
return .unknownError
}
} ?? .unknownError

return networkError
}
}
13 changes: 8 additions & 5 deletions Sources/Hyperspace/Service/Network/NetworkServiceProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,21 @@ public enum NetworkServiceError: Error {

/// Represents the successful result of executing a NetworkRequest using a NetworkService.
public struct NetworkServiceSuccess {
let data: Data
let response: HTTP.Response
public let data: Data
public let response: HTTP.Response
}

/// Represents the failed result of executing a NetworkRequest using a NetworkService.
public struct NetworkServiceFailure: Error {
let error: NetworkServiceError
let response: HTTP.Response?
public let error: NetworkServiceError
public let response: HTTP.Response?
}

/// Represents the possible resulting values of a NetworkRequest using a NetworkService.
public typealias NetworkServiceResult = Result<NetworkServiceSuccess, NetworkServiceFailure>

/// Upon completion of executing a NetworkRequest using a NetworkService, the NetworkServiceResult is returned.
public typealias NetworkServiceCompletion = (Result<NetworkServiceSuccess, NetworkServiceFailure>) -> Void
public typealias NetworkServiceCompletion = (NetworkServiceResult) -> Void

/// Represents something that can execute a URLRequest.
public protocol NetworkServiceProtocol {
Expand Down

0 comments on commit 533f839

Please sign in to comment.