diff --git a/SwiftTask.xcodeproj/project.pbxproj b/SwiftTask.xcodeproj/project.pbxproj index 37639a7..fe45051 100644 --- a/SwiftTask.xcodeproj/project.pbxproj +++ b/SwiftTask.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 1F20250219ADA8FD00DE0495 /* BasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F20250119ADA8FD00DE0495 /* BasicTests.swift */; }; 1F218D5B1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */; }; 1F218D5C1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */; }; + 1F29F6421B115EF500F476AF /* MultipleErrorTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */; }; + 1F29F6431B115EF500F476AF /* MultipleErrorTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */; }; 1F46DEDA199EDF1000F97868 /* SwiftTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F46DED9199EDF1000F97868 /* SwiftTask.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1F46DEFB199EDF8100F97868 /* SwiftTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFA199EDF8100F97868 /* SwiftTask.swift */; }; 1F46DEFD199EE2C200F97868 /* _TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFC199EE2C200F97868 /* _TestCase.swift */; }; @@ -46,6 +48,7 @@ /* Begin PBXFileReference section */ 1F20250119ADA8FD00DE0495 /* BasicTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTests.swift; sourceTree = ""; }; 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoveHandlerTests.swift; sourceTree = ""; }; + 1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleErrorTypesTests.swift; sourceTree = ""; }; 1F46DED4199EDF1000F97868 /* SwiftTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftTask.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1F46DED8199EDF1000F97868 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1F46DED9199EDF1000F97868 /* SwiftTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTask.h; sourceTree = ""; }; @@ -161,6 +164,7 @@ 485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */, 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */, 1F5FA35619A374E600975FB9 /* AlamofireTests.swift */, + 1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */, 1F46DEE1199EDF1000F97868 /* Supporting Files */, ); path = SwiftTaskTests; @@ -366,6 +370,7 @@ 1FF52EB41A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */, 1F218D5B1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */, 1F6A8CA319A4E4F200369A5D /* SwiftTaskTests.swift in Sources */, + 1F29F6421B115EF500F476AF /* MultipleErrorTypesTests.swift in Sources */, 1F4C76A41AD8CF40004E47C1 /* AlamofireTests.swift in Sources */, 485C31F11A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */, 48511C5B19C17563002FE03C /* RetainCycleTests.swift in Sources */, @@ -381,6 +386,7 @@ 4822F0DD19D00B2300F5F572 /* BasicTests.swift in Sources */, 1F218D5C1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */, 1FF52EB51A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */, + 1F29F6431B115EF500F476AF /* MultipleErrorTypesTests.swift in Sources */, 1F4C76A51AD8CF41004E47C1 /* AlamofireTests.swift in Sources */, 485C31F21A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */, 4822F0DC19D00B2300F5F572 /* _TestCase.swift in Sources */, diff --git a/SwiftTask/SwiftTask.swift b/SwiftTask/SwiftTask.swift index 736f0c7..710d7d2 100644 --- a/SwiftTask/SwiftTask.swift +++ b/SwiftTask/SwiftTask.swift @@ -398,7 +398,7 @@ public class Task: Cancellable, Printable /// /// - e.g. task.then { value, errorInfo -> NextTaskType in ... } /// - public func then(thenClosure: (Value?, ErrorInfo?) -> Task) -> Task + public func then(thenClosure: (Value?, ErrorInfo?) -> Task) -> Task { var dummyCanceller: Canceller? = nil return self.then(&dummyCanceller, thenClosure) @@ -410,9 +410,9 @@ public class Task: Cancellable, Printable // - `let canceller = Canceller(); task1.then(&canceller) {...}; canceller.cancel();` // - `let task2 = task1.then {...}; task2.cancel();` // - public func then(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Task) -> Task + public func then(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Task) -> Task { - return Task { [unowned self, weak canceller] newMachine, progress, fulfill, _reject, configure in + return Task { [unowned self, weak canceller] newMachine, progress, fulfill, _reject, configure in // // NOTE: @@ -470,13 +470,13 @@ public class Task: Cancellable, Printable /// /// - e.g. task.success { value -> NextTaskType in ... } /// - public func success(successClosure: Value -> Task) -> Task + public func success(successClosure: Value -> Task) -> Task { var dummyCanceller: Canceller? = nil return self.success(&dummyCanceller, successClosure) } - public func success(inout canceller: C?, _ successClosure: Value -> Task) -> Task + public func success(inout canceller: C?, _ successClosure: Value -> Task) -> Task { return Task { [unowned self] newMachine, progress, fulfill, _reject, configure in @@ -521,15 +521,15 @@ public class Task: Cancellable, Printable /// - e.g. task.failure { errorInfo -> NextTaskType in ... } /// - e.g. task.failure { error, isCancelled -> NextTaskType in ... } /// - public func failure(failureClosure: ErrorInfo -> Task) -> Task + public func failure(failureClosure: ErrorInfo -> Task) -> Task { var dummyCanceller: Canceller? = nil return self.failure(&dummyCanceller, failureClosure) } - public func failure(inout canceller: C?, _ failureClosure: ErrorInfo -> Task) -> Task + public func failure(inout canceller: C?, _ failureClosure: ErrorInfo -> Task) -> Task { - return Task { [unowned self] newMachine, progress, fulfill, _reject, configure in + return Task { [unowned self] newMachine, progress, fulfill, _reject, configure in let selfMachine = self._machine @@ -581,8 +581,8 @@ public class Task: Cancellable, Printable // MARK: - Helper -internal func _bindInnerTask( - innerTask: Task, +internal func _bindInnerTask( + innerTask: Task, newMachine: _StateMachine, progress: Task.ProgressHandler, fulfill: Task.FulfillHandler, @@ -595,7 +595,10 @@ internal func _bindInnerTask( fulfill(innerTask.value!) return case .Rejected, .Cancelled: - _reject(innerTask.errorInfo!) + let (error2, isCancelled) = innerTask.errorInfo! + + // NOTE: innerTask's `error2` will be treated as `nil` if not same type as outerTask's `Error` type + _reject((error2 as? Error, isCancelled)) return default: break @@ -603,12 +606,15 @@ internal func _bindInnerTask( innerTask.progress { _, progressValue in progress(progressValue) - }.then { (value: Value2?, errorInfo: Task.ErrorInfo?) -> Void in + }.then { (value: Value2?, errorInfo2: Task.ErrorInfo?) -> Void in if let value = value { fulfill(value) } - else if let errorInfo = errorInfo { - _reject(errorInfo) + else if let errorInfo2 = errorInfo2 { + let (error2, isCancelled) = errorInfo2 + + // NOTE: innerTask's `error2` will be treated as `nil` if not same type as outerTask's `Error` type + _reject((error2 as? Error, isCancelled)) } } diff --git a/SwiftTaskTests/MultipleErrorTypesTests.swift b/SwiftTaskTests/MultipleErrorTypesTests.swift new file mode 100644 index 0000000..3339d8d --- /dev/null +++ b/SwiftTaskTests/MultipleErrorTypesTests.swift @@ -0,0 +1,224 @@ +// +// MultipleErrorTypesTests.swift +// SwiftTask +// +// Created by Yasuhiro Inami on 2015/05/24. +// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved. +// + +import SwiftTask +import Async +import XCTest + +class MultipleErrorTypesTests: _TestCase +{ + enum MyEnum: Printable + { + case Default + var description: String { return "MyEnumDefault" } + } + + struct Dummy {} + + typealias Task1 = Task + typealias Task2 = Task + + var flow = [Int]() + + // delayed task + counting flow 1 & 2 + func _task1(#ok: Bool) -> Task1 + { + return Task1 { progress, fulfill, reject, configure in + println("[task1] start") + self.flow += [1] + + Async.main(after: 0.1) { + println("[task1] end") + self.flow += [2] + + ok ? fulfill("OK") : reject("NG") + } + return + } + } + + // delayed task + counting flow 4 & 5 + func _task2(#ok: Bool) -> Task2 + { + return Task2 { progress, fulfill, reject, configure in + println("[task2] start") + self.flow += [4] + + Async.main(after: 0.1) { + println("[task2] end") + self.flow += [5] + + ok ? fulfill(.Default) : reject(.Default) + } + return + } + } + + func testMultipleErrorTypes_then() + { + var expect = self.expectationWithDescription(__FUNCTION__) + + self._task1(ok: true) + .then { value, errorInfo -> Task2 in + + println("task1.then") + self.flow += [3] + + return self._task2(ok: true) + + } + .then { value, errorInfo -> Void in + + println("task1.then.then (task2 should end at this point)") + self.flow += [6] + + XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.") + expect.fulfill() + + } + + self.wait() + } + + func testMultipleErrorTypes_success() + { + var expect = self.expectationWithDescription(__FUNCTION__) + + self._task1(ok: true) + .success { value -> Task2 in + + println("task1.success") + self.flow += [3] + + return self._task2(ok: true) + + } + .success { value -> Void in + + println("task1.success.success (task2 should end at this point)") + self.flow += [6] + + XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.") + expect.fulfill() + + } + + self.wait() + } + + func testMultipleErrorTypes_success_differentErrorType() + { + var expect = self.expectationWithDescription(__FUNCTION__) + + self._task1(ok: true) + .success { value -> Task2 in + + println("task1.success") + self.flow += [3] + + // + // NOTE: + // If Task1 and Task2 have different Error types, + // returning `self._task2(ok: false)` inside `self._task1.success()` will fail error conversion + // (Task2.Error -> Task1.Error). + // + return self._task2(ok: false) // inner rejection with different Error type + + } + .then { value, errorInfo -> Void in + + println("task1.success.success (task2 should end at this point)") + self.flow += [6] + + XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.") + + XCTAssertTrue(value == nil) + XCTAssertTrue(errorInfo != nil) + XCTAssertTrue(errorInfo!.error == nil, "Though `errorInfo` will still be non-nil, `errorInfo!.error` will become as `nil` if Task1 and Task2 have different Error types.") + XCTAssertTrue(errorInfo!.isCancelled == false) + + expect.fulfill() + + } + + self.wait() + } + + func testMultipleErrorTypes_success_differentErrorType_conversion() + { + var expect = self.expectationWithDescription(__FUNCTION__) + + self._task1(ok: true) + .success { value -> Task in + + println("task1.success") + self.flow += [3] + + // + // NOTE: + // Since returning `self._task2(ok: false)` inside `self._task1.success()` will fail error conversion + // (Task2.Error -> Task1.Error) as seen in above test case, + // it is **user's responsibility** to add conversion logic to maintain same Error type throughout task-flow. + // + return self._task2(ok: false) + .failure { Task(error: "Mapping errorInfo=\($0.error!) to String") } // error-conversion + + } + .then { value, errorInfo -> Void in + + println("task1.success.success (task2 should end at this point)") + self.flow += [6] + + XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.") + + XCTAssertTrue(value == nil) + XCTAssertTrue(errorInfo != nil) + XCTAssertEqual(errorInfo!.error!, "Mapping errorInfo=MyEnumDefault to String", + "Now `self._task2()`'s error is catched by this scope by adding manual error-conversion logic by user side.") + XCTAssertTrue(errorInfo!.isCancelled == false) + + expect.fulfill() + + } + + self.wait() + } + + func testMultipleErrorTypes_failure() + { + var expect = self.expectationWithDescription(__FUNCTION__) + + self._task1(ok: false) + .failure { errorInfo -> Task in + + println("task1.failure") + self.flow += [3] + + // + // NOTE: + // Returning `Task2` won't work since same Value type as `task1` is required inside `task1.failure()`, + // so use `then()` to promote `Task2` to `Task<..., Task1.Value, ...>`. + // + return self._task2(ok: false).then { value, errorInfo in + return Task(error: Dummy()) // error task + } + } + .failure { errorInfo -> String /* must be task1's value type to recover */ in + + println("task1.failure.failure (task2 should end at this point)") + self.flow += [6] + + XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.") + expect.fulfill() + + return "DUMMY" + } + + self.wait() + } +} \ No newline at end of file