Skip to content

Commit 8a070de

Browse files
authored
Add mapToResult operator (#79)
1 parent 5b8a0c0 commit 8a070de

File tree

4 files changed

+218
-2
lines changed

4 files changed

+218
-2
lines changed

Package.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
"repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
77
"state": {
88
"branch": null,
9-
"revision": "afc84b6a3639198b7b8b6d79f04eb3c2ee590d29",
10-
"version": "0.1.1"
9+
"revision": "ae2f434e81017bb7de02c168fb0bde83cd8370c1",
10+
"version": "0.3.1"
1111
}
1212
}
1313
]

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ All operators, utilities and helpers respect Combine's publisher contract, inclu
4242
* [nwise(_:) and pairwise()](#nwise)
4343
* [ignoreOutput(setOutputType:)](#ignoreOutputsetOutputType)
4444
* [ignoreFailure](#ignoreFailure)
45+
* [mapToResult](#mapToResult)
4546
* [flatMapBatches(of:)](#flatMapBatchesof)
4647

4748
### Publishers
@@ -697,6 +698,41 @@ subject.send(completion: .failure(.someError))
697698
3
698699
.finished
699700
```
701+
------
702+
703+
### mapToResult
704+
705+
Transforms a publisher of type `AnyPublisher<Output, Failure>` to `AnyPublisher<Result<Output, Failure>, Never>`
706+
707+
```swift
708+
enum AnError: Error {
709+
case someError
710+
}
711+
712+
let subject = PassthroughSubject<Int, AnError>()
713+
714+
let subscription = subject
715+
.mapToResult()
716+
.sink(receiveCompletion: { print("completion: \($0)") },
717+
receiveValue: { print("value: \($0)") })
718+
719+
subject.send(1)
720+
subject.send(2)
721+
subject.send(3)
722+
subject.send(completion: .failure(.someError))
723+
```
724+
725+
#### Output
726+
727+
```none
728+
value: success(1)
729+
value: success(2)
730+
value: success(3)
731+
value: failure(AnError.someError)
732+
completion: finished
733+
```
734+
735+
------
700736

701737
### flatMapBatches(of:)
702738

Sources/Operators/MapToResult.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// MapToResult.swift
3+
// CombineExt
4+
//
5+
// Created by Yurii Zadoianchuk on 05/03/2021.
6+
// Copyright © 2021 Combine Community. All rights reserved.
7+
//
8+
9+
#if canImport(Combine)
10+
import Combine
11+
12+
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
13+
public extension Publisher {
14+
/// Transform a publisher with concrete Output and Failure types
15+
/// to a new publisher that wraps Output and Failure in Result,
16+
/// and has Never for Failure type
17+
/// - Returns: A type-erased publiser of type <Result<Output, Failure>, Never>
18+
func mapToResult() -> AnyPublisher<Result<Output, Failure>, Never> {
19+
map(Result.success)
20+
.catch { Just(.failure($0)) }
21+
.eraseToAnyPublisher()
22+
}
23+
}
24+
25+
#endif

Tests/MapToResultTests.swift

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
//
2+
// MapToResultTests.swift
3+
// CombineExt
4+
//
5+
// Created by Yurii Zadoianchuk on 05/03/2021.
6+
// Copyright © 2021 Combine Community. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
#if !os(watchOS)
12+
import XCTest
13+
import Combine
14+
import CombineExt
15+
16+
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
17+
final class MapToResultTests: XCTestCase {
18+
private var subscription: AnyCancellable!
19+
20+
enum MapToResultError: Error {
21+
case someError
22+
}
23+
24+
func testMapResultNoError() {
25+
let subject = PassthroughSubject<Int, Error>()
26+
let testInt = 5
27+
var completed = false
28+
var results: [Result<Int, Error>] = []
29+
30+
subscription = subject
31+
.mapToResult()
32+
.sink(receiveCompletion: { _ in completed = true },
33+
receiveValue: { results.append($0) })
34+
35+
subject.send(testInt)
36+
XCTAssertFalse(completed)
37+
subject.send(testInt)
38+
subject.send(completion: .finished)
39+
XCTAssertTrue(completed)
40+
XCTAssertEqual(results.count, 2)
41+
let intsCorrect = results
42+
.compactMap { try? $0.get() }
43+
.allSatisfy { $0 == testInt }
44+
XCTAssertTrue(intsCorrect)
45+
}
46+
47+
func testMapCustomError() {
48+
let subject = PassthroughSubject<Int, Error>()
49+
var completed = false
50+
var gotFailure = false
51+
var gotSuccess = false
52+
var result: Result<Int, Error>? = nil
53+
54+
subscription = subject
55+
.tryMap { _ -> Int in throw MapToResultError.someError }
56+
.mapToResult()
57+
.eraseToAnyPublisher()
58+
.sink(receiveCompletion: { _ in completed = true },
59+
receiveValue: { result = $0 })
60+
61+
subject.send(0)
62+
XCTAssertNotNil(result)
63+
64+
do {
65+
_ = try result!.get()
66+
gotSuccess = true
67+
} catch {
68+
gotFailure = true
69+
}
70+
71+
XCTAssertTrue(gotFailure)
72+
XCTAssertFalse(gotSuccess)
73+
XCTAssertTrue(completed)
74+
}
75+
76+
func testCatchDecodeError() {
77+
struct ToDecode: Decodable {
78+
let foo: Int
79+
}
80+
81+
let incorrectJson = """
82+
{
83+
"foo": "1"
84+
}
85+
"""
86+
87+
let subject = PassthroughSubject<Data, Error>()
88+
var completed = false
89+
var gotFailure = false
90+
var gotSuccess = false
91+
var result: Result<ToDecode, Error>? = nil
92+
93+
subscription = subject
94+
.decode(type: ToDecode.self, decoder: JSONDecoder())
95+
.mapToResult()
96+
.eraseToAnyPublisher()
97+
.sink(receiveCompletion: { _ in completed = true },
98+
receiveValue: { result = $0 })
99+
100+
subject.send(incorrectJson.data(using: .utf8)!)
101+
XCTAssertNotNil(result)
102+
103+
do {
104+
_ = try result!.get()
105+
gotSuccess = true
106+
} catch let e {
107+
XCTAssert(e is DecodingError)
108+
gotFailure = true
109+
}
110+
111+
XCTAssertTrue(gotFailure)
112+
XCTAssertFalse(gotSuccess)
113+
XCTAssertTrue(completed)
114+
}
115+
116+
func testMapEncodeError() {
117+
struct ToEncode: Encodable {
118+
let foo: Int
119+
120+
func encode(to encoder: Encoder) throws {
121+
throw EncodingError.invalidValue((), EncodingError.Context(codingPath: [], debugDescription: String()))
122+
}
123+
}
124+
125+
let subject = PassthroughSubject<ToEncode, Error>()
126+
var completed = false
127+
var gotFailure = false
128+
var gotSuccess = false
129+
var result: Result<Data, Error>? = nil
130+
131+
subscription = subject
132+
.encode(encoder: JSONEncoder())
133+
.mapToResult()
134+
.eraseToAnyPublisher()
135+
.sink(receiveCompletion: { _ in completed = true },
136+
receiveValue: { result = $0 })
137+
138+
subject.send(ToEncode(foo: 0))
139+
XCTAssertNotNil(result)
140+
141+
do {
142+
_ = try result!.get()
143+
gotSuccess = true
144+
} catch let e {
145+
XCTAssert(e is EncodingError)
146+
gotFailure = true
147+
}
148+
149+
XCTAssertTrue(gotFailure)
150+
XCTAssertFalse(gotSuccess)
151+
XCTAssertTrue(completed)
152+
}
153+
}
154+
155+
#endif

0 commit comments

Comments
 (0)