Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions SwiftTask.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -46,6 +48,7 @@
/* Begin PBXFileReference section */
1F20250119ADA8FD00DE0495 /* BasicTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTests.swift; sourceTree = "<group>"; };
1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoveHandlerTests.swift; sourceTree = "<group>"; };
1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleErrorTypesTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
1F46DED9199EDF1000F97868 /* SwiftTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTask.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -161,6 +164,7 @@
485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */,
1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */,
1F5FA35619A374E600975FB9 /* AlamofireTests.swift */,
1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */,
1F46DEE1199EDF1000F97868 /* Supporting Files */,
);
path = SwiftTaskTests;
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand Down
34 changes: 20 additions & 14 deletions SwiftTask/SwiftTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
///
/// - e.g. task.then { value, errorInfo -> NextTaskType in ... }
///
public func then<Progress2, Value2>(thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
public func then<Progress2, Value2, Error2>(thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error2>) -> Task<Progress2, Value2, Error2>
{
var dummyCanceller: Canceller? = nil
return self.then(&dummyCanceller, thenClosure)
Expand All @@ -410,9 +410,9 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
// - `let canceller = Canceller(); task1.then(&canceller) {...}; canceller.cancel();`
// - `let task2 = task1.then {...}; task2.cancel();`
//
public func then<Progress2, Value2, C: Canceller>(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
public func then<Progress2, Value2, Error2, C: Canceller>(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error2>) -> Task<Progress2, Value2, Error2>
{
return Task<Progress2, Value2, Error> { [unowned self, weak canceller] newMachine, progress, fulfill, _reject, configure in
return Task<Progress2, Value2, Error2> { [unowned self, weak canceller] newMachine, progress, fulfill, _reject, configure in

//
// NOTE:
Expand Down Expand Up @@ -470,13 +470,13 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
///
/// - e.g. task.success { value -> NextTaskType in ... }
///
public func success<Progress2, Value2>(successClosure: Value -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
public func success<Progress2, Value2, Error2>(successClosure: Value -> Task<Progress2, Value2, Error2>) -> Task<Progress2, Value2, Error>
{
var dummyCanceller: Canceller? = nil
return self.success(&dummyCanceller, successClosure)
}

public func success<Progress2, Value2, C: Canceller>(inout canceller: C?, _ successClosure: Value -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
public func success<Progress2, Value2, Error2, C: Canceller>(inout canceller: C?, _ successClosure: Value -> Task<Progress2, Value2, Error2>) -> Task<Progress2, Value2, Error>
{
return Task<Progress2, Value2, Error> { [unowned self] newMachine, progress, fulfill, _reject, configure in

Expand Down Expand Up @@ -521,15 +521,15 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
/// - e.g. task.failure { errorInfo -> NextTaskType in ... }
/// - e.g. task.failure { error, isCancelled -> NextTaskType in ... }
///
public func failure<Progress2>(failureClosure: ErrorInfo -> Task<Progress2, Value, Error>) -> Task<Progress2, Value, Error>
public func failure<Progress2, Error2>(failureClosure: ErrorInfo -> Task<Progress2, Value, Error2>) -> Task<Progress2, Value, Error2>
{
var dummyCanceller: Canceller? = nil
return self.failure(&dummyCanceller, failureClosure)
}

public func failure<Progress2, C: Canceller>(inout canceller: C?, _ failureClosure: ErrorInfo -> Task<Progress2, Value, Error>) -> Task<Progress2, Value, Error>
public func failure<Progress2, Error2, C: Canceller>(inout canceller: C?, _ failureClosure: ErrorInfo -> Task<Progress2, Value, Error2>) -> Task<Progress2, Value, Error2>
{
return Task<Progress2, Value, Error> { [unowned self] newMachine, progress, fulfill, _reject, configure in
return Task<Progress2, Value, Error2> { [unowned self] newMachine, progress, fulfill, _reject, configure in

let selfMachine = self._machine

Expand Down Expand Up @@ -581,8 +581,8 @@ public class Task<Progress, Value, Error>: Cancellable, Printable

// MARK: - Helper

internal func _bindInnerTask<Progress2, Value2, Error>(
innerTask: Task<Progress2, Value2, Error>,
internal func _bindInnerTask<Progress2, Value2, Error, Error2>(
innerTask: Task<Progress2, Value2, Error2>,
newMachine: _StateMachine<Progress2, Value2, Error>,
progress: Task<Progress2, Value2, Error>.ProgressHandler,
fulfill: Task<Progress2, Value2, Error>.FulfillHandler,
Expand All @@ -595,20 +595,26 @@ internal func _bindInnerTask<Progress2, Value2, Error>(
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
}

innerTask.progress { _, progressValue in
progress(progressValue)
}.then { (value: Value2?, errorInfo: Task<Progress2, Value2, Error>.ErrorInfo?) -> Void in
}.then { (value: Value2?, errorInfo2: Task<Progress2, Value2, Error2>.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))
}
}

Expand Down
224 changes: 224 additions & 0 deletions SwiftTaskTests/MultipleErrorTypesTests.swift
Original file line number Diff line number Diff line change
@@ -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<String, String, String>
typealias Task2 = Task<MyEnum, MyEnum, MyEnum>

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<Void, MyEnum, String> 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<Void, MyEnum, String>(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<Dummy, String /* must be task1's value type to recover */, Dummy> 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<Dummy, String, Dummy>(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()
}
}