Skip to content
Open
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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ AsyncSequences
* [Share](#Share)
* [WithLatestFrom](#WithLatestFrom)
* [EraseToAnyAsyncSequence](#EraseToAnyAsyncSequence)
* [MapError](#MapError)

More operators and extensions are to come. Pull requests are of course welcome.

Expand Down Expand Up @@ -545,3 +546,21 @@ seq1.send(3)
### EraseToAnyAsyncSequence

`eraseToAnyAsyncSequence()` type-erases the async sequence into an AnyAsyncSequence.

### MapError

`mapError(_:)` transforms error from upstream async sequence.

```
struct CustomError: Error { }

let failSequence = AsyncSequences.Fail<Int, Swift.Error>(error: NSError(domain: "", code: 1))

do {
for try await element in failSequence.mapError({ _ in CustomError() }) {
print(element) // will never execute
}
} catch {
print(error) // CustomError()
}
```
97 changes: 97 additions & 0 deletions Sources/Operators/AsyncSequence+MapError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// AsyncSequence+MapError.swift
//
// Created by JCSooHwanCho on 2022/03/24.
//

public extension AsyncSequence {
/// transform error from upstream async sequence
///
/// ```
/// struct CustomError: Error { }
///
/// let failSequence = AsyncSequences.Fail<Int, Swift.Error>(error: NSError(domain: "", code: 1))
///
/// do {
/// for try await element in failSequence.mapError({ _ in CustomError() }) {
/// print(element)
/// }
/// } catch {
/// print(error) // CustomError()
/// }
///
/// ```
///
/// - Parameter transformError: A transform to apply to error that upstream emits.
/// - Returns: The async sequence error transformed
@inlinable
__consuming func mapError(
_ transformError: @Sendable @escaping (Error) async -> Error
) -> AsyncMapErrorSequence<Self> {
return AsyncMapErrorSequence(self, transformError: transformError)
}
}

public struct AsyncMapErrorSequence<Base: AsyncSequence> {
@usableFromInline
let base: Base

@usableFromInline
let transformError: (Error) async -> Error

@usableFromInline
init(
_ base: Base,
transformError: @escaping (Error) async -> Error
) {
self.base = base
self.transformError = transformError
}
}

extension AsyncMapErrorSequence: AsyncSequence {

public typealias Element = Base.Element

public typealias AsyncIterator = Iterator

public struct Iterator: AsyncIteratorProtocol {
@usableFromInline
var baseIterator: Base.AsyncIterator

@usableFromInline
let transformError: (Error) async -> Error

@usableFromInline
init(
_ baseIterator: Base.AsyncIterator,
transformError: @escaping (Error) async -> Error
) {
self.baseIterator = baseIterator
self.transformError = transformError
}

@inlinable
public mutating func next() async rethrows -> Element? {
do {
return try await baseIterator.next()
} catch {
throw await transformError(error)
}
}
}

@inlinable
public __consuming func makeAsyncIterator() -> Iterator {
return Iterator(base.makeAsyncIterator(), transformError: transformError)
}
}

extension AsyncMapErrorSequence: @unchecked Sendable
where Base: Sendable,
Base.Element: Sendable { }

extension AsyncMapErrorSequence.Iterator: @unchecked Sendable
where Base.AsyncIterator: Sendable,
Base.Element: Sendable { }

35 changes: 35 additions & 0 deletions Tests/Operators/AsyncSequence+MapErrorTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// AsyncSequence+MapErrorTests.swift
//
//
// Created by JCSooHwanCho on 2022/03/24.
//

import AsyncExtensions
import XCTest

private struct CustomError: Error, Equatable { }

final class AsyncSequence_MapErrorTests: XCTestCase {
func testMapError_with_CustomError() async throws {
let failSequence = AsyncSequences.Fail<Int>(error: NSError(domain: "", code: 1))

do {
for try await _ in failSequence.mapError({ _ in CustomError() }) {
XCTFail()
}
} catch {
XCTAssertEqual(error as! CustomError, CustomError())
}
}

func testMappError_doesnot_affect_on_normal_finis() async throws {
let intSequence = (1...5).asyncElements

let reduced = try await intSequence
.mapError { _ in CustomError() }
.reduce(into: 0, +=)

XCTAssertEqual(reduced, 15)
}
}