Created
December 7, 2019 13:21
-
-
Save shawnthroop/f32793406d00034959b74666e5c9fbbb to your computer and use it in GitHub Desktop.
A simple type safe wrapper around URLSession and URLRequest.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public enum Method : String, Hashable { | |
case options, get, head, post, put, delete, trace, connect | |
} | |
public struct HeaderField : Hashable { | |
public let rawValue: String | |
public init(_ rawValue: String) { | |
self.rawValue = rawValue | |
} | |
} | |
public struct Request : Equatable { | |
public var method: Method = .get | |
public var components: URLComponents | |
public var header: [HeaderField: String] = [:] | |
public var body: Data? = nil | |
} | |
public protocol Endpoint { | |
/// The request describing the reciever. | |
var request: Request { get } | |
/// A handler called when a response is recieved from the server | |
func onResponse(_ response: URLResponse, data: Data) throws | |
} | |
public protocol ResourceEndpoint : Endpoint { | |
/// The type of resource. | |
associatedtype Resource | |
/// Creates a Resource value from requested data. | |
func createResource(from data: Data) throws -> Resource | |
} | |
public extension Endpoint { | |
func onResponse(_ response: URLResponse, data: Data) throws {} | |
} | |
public extension ResourceEndpoint where Resource == Data { | |
func resource(from data: Data) throws -> Resource { data } | |
} | |
public class Network { | |
public typealias Handler<T> = (Result<T, Error>) -> Void | |
public init(session: URLSession = .shared) { | |
self.session = session | |
} | |
private let session: URLSession | |
public func fetchResource<T>(at endpoint: T, completion: @escaping Handler<T.Resource>) -> URLSessionDataTask where T : ResourceEndpoint { | |
fetchData(at: endpoint) { response in | |
completion(response.flatMap { data in | |
Result { try endpoint.createResource(from: data) } | |
}) | |
} | |
} | |
public func fetchData(at endpoint: Endpoint, completion: @escaping Handler<Data>) -> URLSessionDataTask { | |
session.dataTask(with: URLRequest(endpoint.request)) { data, response, error in | |
if let error = error { | |
completion(.failure(error)) | |
} else if let response = response { | |
let data = data ?? Data() | |
completion(Result(catching: { try endpoint.onResponse(response, data: data) }).map { data }) | |
} else { | |
fatalError("response, and error are all nil") | |
} | |
} | |
} | |
} | |
private extension URLRequest { | |
init(_ request: Request) { | |
guard let url = request.components.url else { | |
fatalError("cannot create url from components: \(request.components)") | |
} | |
self.init(url: url) | |
httpMethod = request.method.rawValue.uppercased() | |
request.header.forEach { field, value in | |
addValue(value, forHTTPHeaderField: field.rawValue) | |
} | |
httpBody = request.body | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment