Last active
June 18, 2017 10:20
-
-
Save ratkins/246c985350d77cf209fdf50ecfe1b341 to your computer and use it in GitHub Desktop.
Simple, type-safe networking in Swift with zero dependencies
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 | |
enum HTTPMethod { | |
case get | |
case post(data: Data) | |
} | |
protocol Request { | |
associatedtype ResponseType | |
var method: HTTPMethod { get } | |
var path: String { get } | |
var headers: [String: String]? { get } | |
func parse(_ data: Data) throws -> ResponseType | |
} | |
protocol Session { | |
func execute<R: Request>(_ request: R, completion: @escaping (Result<R.ResponseType, AnyError>) -> Void) | |
} |
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 | |
import Result | |
extension Request { | |
var isMethodGET: Bool { | |
switch method { | |
case .get: return true | |
default: return false | |
} | |
} | |
var isMethodPOST: Bool { | |
switch method { | |
case .post: return true | |
default: return false | |
} | |
} | |
var body: Data? { | |
switch method { | |
case .get: return nil | |
case .post(let body): return body | |
} | |
} | |
} | |
class MockSession: Session { | |
var value: Any? | |
var error: AnyError? | |
func execute<R: Request>(_ request: R, completion: @escaping (Result<R.ResponseType, AnyError>) -> Void) { | |
switch (value, error) { | |
case (.some(let value), _): | |
completion(Result(value: value as! R.ResponseType)) | |
case (_, .some(let error)): | |
completion(Result(error: error)) | |
default: | |
preconditionFailure("You must set one of either 'value' or 'error' properties on MockRequester!") | |
} | |
} | |
} |
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 | |
import Result | |
extension URLSession: Session { | |
var baseURL: URL { | |
return URL(string: "http://api.example.com")! // Or get from Info.plist | |
} | |
func execute<R: Request>(_ request: R, completion: @escaping (Result<R.ResponseType, AnyError>) -> Void) { | |
let url = URL(string: request.path, relativeTo: baseURL)! | |
var urlRequest = URLRequest(url: url) | |
urlRequest.allHTTPHeaderFields = request.headers | |
switch request.method { | |
case .get: | |
urlRequest.httpMethod = "GET" | |
case .post(let data): | |
urlRequest.httpMethod = "POST" | |
urlRequest.httpBody = data | |
} | |
let task = dataTask(with: urlRequest) { data, response, error in | |
let statusCode = (response as? HTTPURLResponse)?.statusCode | |
do { | |
switch (data, statusCode, error) { | |
case (let data?, let statusCode?, _) where 200..<300 ~= statusCode: | |
let parsed = try request.parse(data) | |
completion(.success(parsed)) | |
case (_, let statusCode?, _): | |
throw NSError(domain: "RequestErrorDomain", code: statusCode, userInfo: [NSLocalizedDescriptionKey: "Received \(statusCode) status code from server"]) | |
case (_, _, let error?): | |
throw error | |
default: | |
break | |
} | |
} catch (let error) { | |
completion(Result.failure(AnyError(error))) | |
} | |
} | |
task.resume() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment