Last active
July 19, 2020 00:24
-
-
Save fmo91/43b7d1c7b73fcaac01da73648287ac65 to your computer and use it in GitHub Desktop.
Toy Networking Layer with functional operators
This file contains hidden or 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 PlaygroundSupport | |
enum NetworkError: Error { | |
case generic | |
} | |
precedencegroup PipeAssociativity { | |
associativity: left | |
} | |
infix operator |> : PipeAssociativity | |
infix operator => : PipeAssociativity | |
func |> <A, B> (v: A, f: (A) -> B) -> B { | |
f(v) | |
} | |
func |> <A, B> (v: A?, f: (A) -> B) -> B? { | |
guard let v = v else { return nil } | |
return f(v) | |
} | |
func => <A> (v: Promise<A>, f: @escaping (Result<A, Error>) -> Void) { | |
v.then(f) | |
} | |
func => <A> (v: Promise<A>?, f: @escaping (Result<A, Error>) -> Void) { | |
v?.then(f) | |
} | |
func |> <A, B> (v: Promise<A>, f: @escaping (A) -> Promise<B>) -> Promise<B> { | |
v.then(f) | |
} | |
func |> <A, B> (v: Promise<A>?, f: @escaping (A) -> Promise<B>) -> Promise<B>? { | |
v?.then(f) | |
} | |
func |> <A, B> (v: Promise<A>, f: @escaping (A) -> B) -> Promise<B> { | |
v.then(f) | |
} | |
func |> <A, B> (v: Promise<A>?, f: @escaping (A) -> B) -> Promise<B>? { | |
v?.then(f) | |
} | |
func headers(_ headers: [String: String]) -> (URLRequest) -> URLRequest { | |
return { req in | |
var reqCopy = req | |
reqCopy.allHTTPHeaderFields = headers | |
return reqCopy | |
} | |
} | |
enum HTTPMethod: String { | |
case get = "GET" | |
case post = "POST" | |
} | |
func method(_ method: HTTPMethod) -> (URLRequest) -> URLRequest { | |
return { req in | |
var reqCopy = req | |
reqCopy.httpMethod = method.rawValue | |
return reqCopy | |
} | |
} | |
func body<T: Codable>(_ bd: T) -> (URLRequest) -> URLRequest { | |
return { req in | |
var reqCopy = req | |
let encoder = JSONEncoder() | |
if let bodyJSON = try? encoder.encode(bd) { | |
reqCopy.httpBody = try? JSONSerialization.data(withJSONObject: bodyJSON, options: []) | |
} | |
return reqCopy | |
} | |
} | |
func toRequest(_ urlString: String) -> URLRequest? { | |
guard let url = URL(string: urlString) else { | |
return nil | |
} | |
return URLRequest(url: url) | |
} | |
func dispatch<T: Codable>(expecting Model: T.Type) -> (URLRequest) -> Promise<T> { | |
return { req in | |
return Promise { observer in | |
let session = URLSession.shared | |
let task = session.dataTask(with: req) { (data: Data?, response: URLResponse?, error: Error?) in | |
if let error = error { | |
DispatchQueue.main.async { | |
observer(.failure(error)) | |
} | |
return | |
} | |
guard let data = data else { | |
DispatchQueue.main.async { | |
observer(.failure(NetworkError.generic)) | |
} | |
return | |
} | |
let decoder = JSONDecoder() | |
guard let model = try? decoder.decode(Model.self, from: data) else { | |
DispatchQueue.main.async { | |
observer(.failure(NetworkError.generic)) | |
} | |
return | |
} | |
DispatchQueue.main.async { | |
observer(.success(model)) | |
} | |
} | |
task.resume() | |
} | |
} | |
} | |
struct Promise<T> { | |
typealias ResultType = Result<T, Error> | |
typealias ResultObserver = (ResultType) -> Void | |
typealias CreatorFunction = (@escaping ResultObserver) -> Void | |
private let creatorFunction: CreatorFunction | |
init(creatorFunction: @escaping CreatorFunction) { | |
self.creatorFunction = creatorFunction | |
} | |
func then<E>(_ f: @escaping (T) -> E) -> Promise<E> { | |
return Promise<E> { observer in | |
self.creatorFunction { r in | |
observer(r.map(f)) | |
} | |
} | |
} | |
func then<E>(_ f: @escaping (T) -> Promise<E>) -> Promise<E> { | |
return Promise<E> { observer in | |
self.creatorFunction { firstResult in | |
switch firstResult { | |
case .success(let successResult): | |
f(successResult).creatorFunction { transformedResult in | |
observer(transformedResult) | |
} | |
case .failure(let error): | |
observer(.failure(error)) | |
} | |
} | |
} | |
} | |
@discardableResult func then(_ f: @escaping (ResultType) -> Void) -> Self { | |
creatorFunction { r in f(r) } | |
return self | |
} | |
} | |
//... | |
struct User: Codable { | |
let name: String | |
let username: String | |
} | |
struct UserCreationData: Codable { | |
let firstName: String | |
let lastName: String | |
} | |
func countUsers(users: [User]) -> Int { | |
return users.count | |
} | |
func map<A, B>(by keyPath: KeyPath<A, B>) -> ([A]) -> [B] { | |
return { array in | |
array.map { $0[keyPath: keyPath] } | |
} | |
} | |
"https://jsonplaceholder.typicode.com/users/" | |
|> toRequest | |
|> method(.get) | |
|> dispatch(expecting: [User].self) | |
|> map(by: \.name) | |
=> { result in | |
switch result { | |
case .success(let usersNames): | |
print(usersNames) | |
case .failure: | |
break | |
} | |
} | |
PlaygroundPage.current.needsIndefiniteExecution = true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment