Skip to content

Instantly share code, notes, and snippets.

@fmo91
Last active July 19, 2020 00:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fmo91/43b7d1c7b73fcaac01da73648287ac65 to your computer and use it in GitHub Desktop.
Save fmo91/43b7d1c7b73fcaac01da73648287ac65 to your computer and use it in GitHub Desktop.
Toy Networking Layer with functional operators
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