Skip to content

Commit 9ffc9d6

Browse files
committed
Merge pull request #38 from ReactKit/multiple-error-types
Handling multiple error types.
2 parents 262681e + fac9ff6 commit 9ffc9d6

File tree

3 files changed

+250
-14
lines changed

3 files changed

+250
-14
lines changed

SwiftTask.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
1F20250219ADA8FD00DE0495 /* BasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F20250119ADA8FD00DE0495 /* BasicTests.swift */; };
1111
1F218D5B1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */; };
1212
1F218D5C1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */; };
13+
1F29F6421B115EF500F476AF /* MultipleErrorTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */; };
14+
1F29F6431B115EF500F476AF /* MultipleErrorTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */; };
1315
1F46DEDA199EDF1000F97868 /* SwiftTask.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F46DED9199EDF1000F97868 /* SwiftTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
1416
1F46DEFB199EDF8100F97868 /* SwiftTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFA199EDF8100F97868 /* SwiftTask.swift */; };
1517
1F46DEFD199EE2C200F97868 /* _TestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F46DEFC199EE2C200F97868 /* _TestCase.swift */; };
@@ -46,6 +48,7 @@
4648
/* Begin PBXFileReference section */
4749
1F20250119ADA8FD00DE0495 /* BasicTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTests.swift; sourceTree = "<group>"; };
4850
1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoveHandlerTests.swift; sourceTree = "<group>"; };
51+
1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipleErrorTypesTests.swift; sourceTree = "<group>"; };
4952
1F46DED4199EDF1000F97868 /* SwiftTask.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftTask.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5053
1F46DED8199EDF1000F97868 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5154
1F46DED9199EDF1000F97868 /* SwiftTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTask.h; sourceTree = "<group>"; };
@@ -161,6 +164,7 @@
161164
485C31F01A1D619A00040DA3 /* TypeInferenceTests.swift */,
162165
1F218D5A1AFC3FFD00C849FF /* RemoveHandlerTests.swift */,
163166
1F5FA35619A374E600975FB9 /* AlamofireTests.swift */,
167+
1F29F6411B115EF500F476AF /* MultipleErrorTypesTests.swift */,
164168
1F46DEE1199EDF1000F97868 /* Supporting Files */,
165169
);
166170
path = SwiftTaskTests;
@@ -366,6 +370,7 @@
366370
1FF52EB41A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */,
367371
1F218D5B1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */,
368372
1F6A8CA319A4E4F200369A5D /* SwiftTaskTests.swift in Sources */,
373+
1F29F6421B115EF500F476AF /* MultipleErrorTypesTests.swift in Sources */,
369374
1F4C76A41AD8CF40004E47C1 /* AlamofireTests.swift in Sources */,
370375
485C31F11A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */,
371376
48511C5B19C17563002FE03C /* RetainCycleTests.swift in Sources */,
@@ -381,6 +386,7 @@
381386
4822F0DD19D00B2300F5F572 /* BasicTests.swift in Sources */,
382387
1F218D5C1AFC3FFD00C849FF /* RemoveHandlerTests.swift in Sources */,
383388
1FF52EB51A4C395A00B4BA28 /* _InterruptableTask.swift in Sources */,
389+
1F29F6431B115EF500F476AF /* MultipleErrorTypesTests.swift in Sources */,
384390
1F4C76A51AD8CF41004E47C1 /* AlamofireTests.swift in Sources */,
385391
485C31F21A1D619A00040DA3 /* TypeInferenceTests.swift in Sources */,
386392
4822F0DC19D00B2300F5F572 /* _TestCase.swift in Sources */,

SwiftTask/SwiftTask.swift

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
398398
///
399399
/// - e.g. task.then { value, errorInfo -> NextTaskType in ... }
400400
///
401-
public func then<Progress2, Value2>(thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
401+
public func then<Progress2, Value2, Error2>(thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error2>) -> Task<Progress2, Value2, Error2>
402402
{
403403
var dummyCanceller: Canceller? = nil
404404
return self.then(&dummyCanceller, thenClosure)
@@ -410,9 +410,9 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
410410
// - `let canceller = Canceller(); task1.then(&canceller) {...}; canceller.cancel();`
411411
// - `let task2 = task1.then {...}; task2.cancel();`
412412
//
413-
public func then<Progress2, Value2, C: Canceller>(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
413+
public func then<Progress2, Value2, Error2, C: Canceller>(inout canceller: C?, _ thenClosure: (Value?, ErrorInfo?) -> Task<Progress2, Value2, Error2>) -> Task<Progress2, Value2, Error2>
414414
{
415-
return Task<Progress2, Value2, Error> { [unowned self, weak canceller] newMachine, progress, fulfill, _reject, configure in
415+
return Task<Progress2, Value2, Error2> { [unowned self, weak canceller] newMachine, progress, fulfill, _reject, configure in
416416

417417
//
418418
// NOTE:
@@ -470,13 +470,13 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
470470
///
471471
/// - e.g. task.success { value -> NextTaskType in ... }
472472
///
473-
public func success<Progress2, Value2>(successClosure: Value -> Task<Progress2, Value2, Error>) -> Task<Progress2, Value2, Error>
473+
public func success<Progress2, Value2, Error2>(successClosure: Value -> Task<Progress2, Value2, Error2>) -> Task<Progress2, Value2, Error>
474474
{
475475
var dummyCanceller: Canceller? = nil
476476
return self.success(&dummyCanceller, successClosure)
477477
}
478478

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

@@ -521,15 +521,15 @@ public class Task<Progress, Value, Error>: Cancellable, Printable
521521
/// - e.g. task.failure { errorInfo -> NextTaskType in ... }
522522
/// - e.g. task.failure { error, isCancelled -> NextTaskType in ... }
523523
///
524-
public func failure<Progress2>(failureClosure: ErrorInfo -> Task<Progress2, Value, Error>) -> Task<Progress2, Value, Error>
524+
public func failure<Progress2, Error2>(failureClosure: ErrorInfo -> Task<Progress2, Value, Error2>) -> Task<Progress2, Value, Error2>
525525
{
526526
var dummyCanceller: Canceller? = nil
527527
return self.failure(&dummyCanceller, failureClosure)
528528
}
529529

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

534534
let selfMachine = self._machine
535535

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

582582
// MARK: - Helper
583583

584-
internal func _bindInnerTask<Progress2, Value2, Error>(
585-
innerTask: Task<Progress2, Value2, Error>,
584+
internal func _bindInnerTask<Progress2, Value2, Error, Error2>(
585+
innerTask: Task<Progress2, Value2, Error2>,
586586
newMachine: _StateMachine<Progress2, Value2, Error>,
587587
progress: Task<Progress2, Value2, Error>.ProgressHandler,
588588
fulfill: Task<Progress2, Value2, Error>.FulfillHandler,
@@ -595,20 +595,26 @@ internal func _bindInnerTask<Progress2, Value2, Error>(
595595
fulfill(innerTask.value!)
596596
return
597597
case .Rejected, .Cancelled:
598-
_reject(innerTask.errorInfo!)
598+
let (error2, isCancelled) = innerTask.errorInfo!
599+
600+
// NOTE: innerTask's `error2` will be treated as `nil` if not same type as outerTask's `Error` type
601+
_reject((error2 as? Error, isCancelled))
599602
return
600603
default:
601604
break
602605
}
603606

604607
innerTask.progress { _, progressValue in
605608
progress(progressValue)
606-
}.then { (value: Value2?, errorInfo: Task<Progress2, Value2, Error>.ErrorInfo?) -> Void in
609+
}.then { (value: Value2?, errorInfo2: Task<Progress2, Value2, Error2>.ErrorInfo?) -> Void in
607610
if let value = value {
608611
fulfill(value)
609612
}
610-
else if let errorInfo = errorInfo {
611-
_reject(errorInfo)
613+
else if let errorInfo2 = errorInfo2 {
614+
let (error2, isCancelled) = errorInfo2
615+
616+
// NOTE: innerTask's `error2` will be treated as `nil` if not same type as outerTask's `Error` type
617+
_reject((error2 as? Error, isCancelled))
612618
}
613619
}
614620

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
//
2+
// MultipleErrorTypesTests.swift
3+
// SwiftTask
4+
//
5+
// Created by Yasuhiro Inami on 2015/05/24.
6+
// Copyright (c) 2015年 Yasuhiro Inami. All rights reserved.
7+
//
8+
9+
import SwiftTask
10+
import Async
11+
import XCTest
12+
13+
class MultipleErrorTypesTests: _TestCase
14+
{
15+
enum MyEnum: Printable
16+
{
17+
case Default
18+
var description: String { return "MyEnumDefault" }
19+
}
20+
21+
struct Dummy {}
22+
23+
typealias Task1 = Task<String, String, String>
24+
typealias Task2 = Task<MyEnum, MyEnum, MyEnum>
25+
26+
var flow = [Int]()
27+
28+
// delayed task + counting flow 1 & 2
29+
func _task1(#ok: Bool) -> Task1
30+
{
31+
return Task1 { progress, fulfill, reject, configure in
32+
println("[task1] start")
33+
self.flow += [1]
34+
35+
Async.main(after: 0.1) {
36+
println("[task1] end")
37+
self.flow += [2]
38+
39+
ok ? fulfill("OK") : reject("NG")
40+
}
41+
return
42+
}
43+
}
44+
45+
// delayed task + counting flow 4 & 5
46+
func _task2(#ok: Bool) -> Task2
47+
{
48+
return Task2 { progress, fulfill, reject, configure in
49+
println("[task2] start")
50+
self.flow += [4]
51+
52+
Async.main(after: 0.1) {
53+
println("[task2] end")
54+
self.flow += [5]
55+
56+
ok ? fulfill(.Default) : reject(.Default)
57+
}
58+
return
59+
}
60+
}
61+
62+
func testMultipleErrorTypes_then()
63+
{
64+
var expect = self.expectationWithDescription(__FUNCTION__)
65+
66+
self._task1(ok: true)
67+
.then { value, errorInfo -> Task2 in
68+
69+
println("task1.then")
70+
self.flow += [3]
71+
72+
return self._task2(ok: true)
73+
74+
}
75+
.then { value, errorInfo -> Void in
76+
77+
println("task1.then.then (task2 should end at this point)")
78+
self.flow += [6]
79+
80+
XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.")
81+
expect.fulfill()
82+
83+
}
84+
85+
self.wait()
86+
}
87+
88+
func testMultipleErrorTypes_success()
89+
{
90+
var expect = self.expectationWithDescription(__FUNCTION__)
91+
92+
self._task1(ok: true)
93+
.success { value -> Task2 in
94+
95+
println("task1.success")
96+
self.flow += [3]
97+
98+
return self._task2(ok: true)
99+
100+
}
101+
.success { value -> Void in
102+
103+
println("task1.success.success (task2 should end at this point)")
104+
self.flow += [6]
105+
106+
XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.")
107+
expect.fulfill()
108+
109+
}
110+
111+
self.wait()
112+
}
113+
114+
func testMultipleErrorTypes_success_differentErrorType()
115+
{
116+
var expect = self.expectationWithDescription(__FUNCTION__)
117+
118+
self._task1(ok: true)
119+
.success { value -> Task2 in
120+
121+
println("task1.success")
122+
self.flow += [3]
123+
124+
//
125+
// NOTE:
126+
// If Task1 and Task2 have different Error types,
127+
// returning `self._task2(ok: false)` inside `self._task1.success()` will fail error conversion
128+
// (Task2.Error -> Task1.Error).
129+
//
130+
return self._task2(ok: false) // inner rejection with different Error type
131+
132+
}
133+
.then { value, errorInfo -> Void in
134+
135+
println("task1.success.success (task2 should end at this point)")
136+
self.flow += [6]
137+
138+
XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.")
139+
140+
XCTAssertTrue(value == nil)
141+
XCTAssertTrue(errorInfo != nil)
142+
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.")
143+
XCTAssertTrue(errorInfo!.isCancelled == false)
144+
145+
expect.fulfill()
146+
147+
}
148+
149+
self.wait()
150+
}
151+
152+
func testMultipleErrorTypes_success_differentErrorType_conversion()
153+
{
154+
var expect = self.expectationWithDescription(__FUNCTION__)
155+
156+
self._task1(ok: true)
157+
.success { value -> Task<Void, MyEnum, String> in
158+
159+
println("task1.success")
160+
self.flow += [3]
161+
162+
//
163+
// NOTE:
164+
// Since returning `self._task2(ok: false)` inside `self._task1.success()` will fail error conversion
165+
// (Task2.Error -> Task1.Error) as seen in above test case,
166+
// it is **user's responsibility** to add conversion logic to maintain same Error type throughout task-flow.
167+
//
168+
return self._task2(ok: false)
169+
.failure { Task<Void, MyEnum, String>(error: "Mapping errorInfo=\($0.error!) to String") } // error-conversion
170+
171+
}
172+
.then { value, errorInfo -> Void in
173+
174+
println("task1.success.success (task2 should end at this point)")
175+
self.flow += [6]
176+
177+
XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.")
178+
179+
XCTAssertTrue(value == nil)
180+
XCTAssertTrue(errorInfo != nil)
181+
XCTAssertEqual(errorInfo!.error!, "Mapping errorInfo=MyEnumDefault to String",
182+
"Now `self._task2()`'s error is catched by this scope by adding manual error-conversion logic by user side.")
183+
XCTAssertTrue(errorInfo!.isCancelled == false)
184+
185+
expect.fulfill()
186+
187+
}
188+
189+
self.wait()
190+
}
191+
192+
func testMultipleErrorTypes_failure()
193+
{
194+
var expect = self.expectationWithDescription(__FUNCTION__)
195+
196+
self._task1(ok: false)
197+
.failure { errorInfo -> Task<Dummy, String /* must be task1's value type to recover */, Dummy> in
198+
199+
println("task1.failure")
200+
self.flow += [3]
201+
202+
//
203+
// NOTE:
204+
// Returning `Task2` won't work since same Value type as `task1` is required inside `task1.failure()`,
205+
// so use `then()` to promote `Task2` to `Task<..., Task1.Value, ...>`.
206+
//
207+
return self._task2(ok: false).then { value, errorInfo in
208+
return Task<Dummy, String, Dummy>(error: Dummy()) // error task
209+
}
210+
}
211+
.failure { errorInfo -> String /* must be task1's value type to recover */ in
212+
213+
println("task1.failure.failure (task2 should end at this point)")
214+
self.flow += [6]
215+
216+
XCTAssertEqual(self.flow, Array(1...6), "Tasks should flow in order from 1 to 6.")
217+
expect.fulfill()
218+
219+
return "DUMMY"
220+
}
221+
222+
self.wait()
223+
}
224+
}

0 commit comments

Comments
 (0)