diff --git a/SessionArtist/Info.plist b/SessionArtist/Info.plist
index 8c30608..3f11dbf 100644
--- a/SessionArtist/Info.plist
+++ b/SessionArtist/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 0.6.4
+ 0.6.5
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSPrincipalClass
diff --git a/SessionArtist/Result.swift b/SessionArtist/Result.swift
index 461d4ed..e71e355 100644
--- a/SessionArtist/Result.swift
+++ b/SessionArtist/Result.swift
@@ -2,16 +2,21 @@ import Foundation
+/**
+ An `Either` type wrapping either abitrary value of type `T` or an `Error`.
+ */
public enum Result {
case success(T)
case failure(Error)
+ @available(*, deprecated, message: "Considered harmful. `Result(MyError)` will unexpectedly return a `Result.success`." )
public init(_ value: T) {
self = .success(value)
}
+ @available(*, deprecated, message: "Considered harmful. `Result(MyError)` will unexpectedly return a `Result.success`." )
public init(_ error: Error) {
self = .failure(error)
}
@@ -20,6 +25,26 @@ public enum Result {
public extension Result {
+ var isSuccess: Bool {
+ switch self {
+ case .success:
+ return true
+ case .failure:
+ return false
+ }
+ }
+
+
+ var isFailure: Bool {
+ return !isSuccess
+ }
+
+
+ /**
+ If `self` is a `success`, unwraps the value and maps it to a new value via `transform` which is, itself, wrapped in a new `success`. If `self` is a `failure`, reutrns that failure directly without evaluating `transform`.
+ * note: Implicit here is the idea that `transform` will always succeed. If it can fail, use `flatMap(transform:)` instead so that an error can be thrown or a `failure` returned.
+ * seeAlso: `flatMap(transform:)`
+ */
func map(transform: (T)->U) -> Result {
switch self {
case .success(let t):
@@ -30,6 +55,10 @@ public extension Result {
}
+ /**
+ If `self` is a `success`, unwraps the value and maps it to a new `success` *or* `failure` via `transform`. As a convenience, any errors thrown during `transform` will be mapped to a `failure`. If `self` is already a `failure`, reutrns that failure directly without evaluating `transform`.
+ * seeAlso: `map(transform:)`, `asyncFlatMap(asyncTransform:completion:)`
+ */
func flatMap(transform: (T) throws -> Result) -> Result {
switch self {
case .success(let t):
@@ -44,6 +73,24 @@ public extension Result {
}
+ /**
+ If `self` is a `success`, unwraps the value and maps it to a new `success` *or* `failure` via `transform`, calling `completion` with the result. If `self` is already a `failure`, passes that failure directly `completion` without evaluating `transform`.
+ * note: the transform function is, itself, asynchronous in the CPS-style.
+ * seeAlso: `flatMap(transform:)`
+ */
+ func asyncFlatMap(asyncTransform: (T, @escaping (Result)->Void)->Void, completion: @escaping (Result)->Void) {
+ switch self {
+ case .success(let t):
+ asyncTransform(t) { u in
+ completion(u)
+ }
+ case .failure(let e):
+ completion(.failure(e))
+ }
+ }
+
+
+ /// If `self` is a `success`, unwrap the value and return it. Otherwise, throw the `failure`'s error.
func resolve() throws -> T {
switch self {
case .success(let t):
@@ -52,4 +99,23 @@ public extension Result {
throw e
}
}
+
+
+ /// Compose two `Result` continuations. Unlike the generic `route(continuation:adaptor)`, this version `flatMap`s over results with `adaptor` so we don't have to deal with `failure` branches.
+ static func flatRoute(continuation: @escaping (Result)->Void, adaptor: @escaping (T)->Result) -> (Result)->Void {
+ return { t in
+ continuation(t.flatMap(transform: adaptor))
+ }
+ }
+
+
+ /**
+ Compose two `Result` continuations via an adaptor that is, itself, asynchronous in the CPS-style. Necessary for adaptors that need to call async routines.
+ * seeAlso: `flatRoute(continuation:adaptor:)
+ */
+ static func asyncFlatRoute(continuation: @escaping (Result)->Void, asyncAdaptor: @escaping (T, @escaping (Result)->Void)->Void) -> (Result)->Void {
+ return { t in
+ t.asyncFlatMap(asyncTransform: asyncAdaptor, completion: continuation)
+ }
+ }
}
diff --git a/SessionArtistTests/ResultTests.swift b/SessionArtistTests/ResultTests.swift
index e08385c..a54bf83 100644
--- a/SessionArtistTests/ResultTests.swift
+++ b/SessionArtistTests/ResultTests.swift
@@ -15,6 +15,14 @@ class ResultTests: XCTestCase {
}
+ func testPredicates() {
+ XCTAssert(success.isSuccess)
+ XCTAssert(failure.isFailure)
+ XCTAssertFalse(success.isFailure)
+ XCTAssertFalse(failure.isSuccess)
+ }
+
+
func testInitializer() {
switch Result("foo") {
case .success("foo"):
@@ -83,6 +91,66 @@ class ResultTests: XCTestCase {
}
+ func testAsyncFlatMapToSuccess() {
+ let expectedSuccessFromSuccess = expectation(description: "Waiting for successful result")
+ let expectedFailureFromFailure = expectation(description: "Waiting for failure result")
+
+ let transform: (Int, (Result)->Void)->Void = { i, completion in
+ completion(.success(String(i)))
+ }
+
+ success.asyncFlatMap(asyncTransform: transform) { res in
+ switch res {
+ case .success("42"):
+ expectedSuccessFromSuccess.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ failure.asyncFlatMap(asyncTransform: transform) { res in
+ switch res {
+ case .failure(E.original):
+ expectedFailureFromFailure.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ wait(for: [expectedSuccessFromSuccess, expectedFailureFromFailure], timeout: 0)
+ }
+
+
+ func testAsyncFlatMapToFailure() {
+ let expectedFailureFromSuccess = expectation(description: "Waiting for new failure")
+ let expectedFailureFromFailure = expectation(description: "Waiting for originnal failure")
+
+ let transform: (Int, (Result)->Void)->Void = { i, completion in
+ completion(.failure(E.new))
+ }
+
+ success.asyncFlatMap(asyncTransform: transform) { res in
+ switch res {
+ case .failure(E.new):
+ expectedFailureFromSuccess.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ failure.asyncFlatMap(asyncTransform: transform) { res in
+ switch res {
+ case .failure(E.original):
+ expectedFailureFromFailure.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ wait(for: [expectedFailureFromSuccess, expectedFailureFromFailure], timeout: 0)
+ }
+
+
func testMap() {
switch success.map(transform: { String($0) }) {
case .success("42"):
@@ -134,4 +202,136 @@ class ResultTests: XCTestCase {
XCTFail()
}
}
+
+
+ func testFlatRouteToSuccess() {
+ let expectedSuccessFromSuccess = expectation(description: "Waiting for success.")
+ let expectedFailureFromFailure = expectation(description: "Waiting for failure.")
+
+ let successContinuation: (Result)->Void = { res in
+ switch res {
+ case .success("42"):
+ expectedSuccessFromSuccess.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ let failureContinuation: (Result)->Void = { res in
+ switch res {
+ case .failure(E.original):
+ expectedFailureFromFailure.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ let adaptor: (Int)->Result = { i in
+ return .success(String(i))
+ }
+
+ Result.flatRoute(continuation: successContinuation, adaptor: adaptor)(success)
+ Result.flatRoute(continuation: failureContinuation, adaptor: adaptor)(failure)
+
+ wait(for: [expectedSuccessFromSuccess, expectedFailureFromFailure], timeout: 0)
+ }
+
+
+ func testFlatRouteToFailure() {
+ let expectedFailureFromSuccess = expectation(description: "Waiting for failure from success.")
+ let expectedFailureFromFailure = expectation(description: "Waiting for failure.")
+
+ let successContinuation: (Result)->Void = { res in
+ switch res {
+ case .failure(E.new):
+ expectedFailureFromSuccess.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ let failureContinuation: (Result)->Void = { res in
+ switch res {
+ case .failure(E.original):
+ expectedFailureFromFailure.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ let adaptor: (Int)->Result = { i in
+ return .failure(E.new)
+ }
+
+ Result.flatRoute(continuation: successContinuation, adaptor: adaptor)(success)
+ Result.flatRoute(continuation: failureContinuation, adaptor: adaptor)(failure)
+
+ wait(for: [expectedFailureFromSuccess, expectedFailureFromFailure], timeout: 0)
+ }
+
+
+ func testAsyncFlatRouteToSuccess() {
+ let expectedSuccessFromSuccess = expectation(description: "Waiting for success.")
+ let expectedFailureFromFailure = expectation(description: "Waiting for failure.")
+
+ let successContinuation: (Result)->Void = { res in
+ switch res {
+ case .success("42"):
+ expectedSuccessFromSuccess.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ let failureContinuation: (Result)->Void = { res in
+ switch res {
+ case .failure(E.original):
+ expectedFailureFromFailure.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ let adaptor: (Int, (Result)->Void)->Void = { i, completion in
+ completion(.success(String(i)))
+ }
+
+ Result.asyncFlatRoute(continuation: successContinuation, asyncAdaptor: adaptor)(success)
+ Result.asyncFlatRoute(continuation: failureContinuation, asyncAdaptor: adaptor)(failure)
+
+ wait(for: [expectedSuccessFromSuccess, expectedFailureFromFailure], timeout: 0)
+ }
+
+
+ func testAsyncFlatRouteToFailure() {
+ let expectedFailureFromSuccess = expectation(description: "Waiting for failure from success.")
+ let expectedFailureFromFailure = expectation(description: "Waiting for failure.")
+
+ let successContinuation: (Result)->Void = { res in
+ switch res {
+ case .failure(E.new):
+ expectedFailureFromSuccess.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ let failureContinuation: (Result)->Void = { res in
+ switch res {
+ case .failure(E.original):
+ expectedFailureFromFailure.fulfill()
+ default:
+ XCTFail()
+ }
+ }
+
+ let adaptor: (Int, (Result)->Void)->Void = { i, completion in
+ completion(.failure(E.new))
+ }
+
+ Result.asyncFlatRoute(continuation: successContinuation, asyncAdaptor: adaptor)(success)
+ Result.asyncFlatRoute(continuation: failureContinuation, asyncAdaptor: adaptor)(failure)
+
+ wait(for: [expectedFailureFromSuccess, expectedFailureFromFailure], timeout: 0)
+ }
}