A helper for easily unwrapping Optional values in an EventLoopFuture (#1656)

Motivation:

Unwrapping an `Optional` value from an `EventLoopFuture` is a fairly
common requirement that currently involves the client writing
boilerplate code, for example:

```
extension EventLoopFuture {
    func unwrapOptional<T>(orError error: Swift.Error) -> EventLoopFuture<T> where Value == T? {
        self.flatMapThrowing { value in
            guard let value = value else {
                throw error
            }
            return value
        }
    }
}
```

As this is a fairly common requirement adding an extension of
`EventLoopFuture` to unwrap `Optional` values would remove this
burden from clients.

Modifications:

Added Extension to `EventLoopFuture` containing the following functions:

- `unwrap<NewValue>(orError: Error)`: Unwraps a future returning a new
  `EventLoopFuture` with the same value as the resolved future when
  its value is Optional.some(...)`, otherwise the `Error` passed in
  the `orError` parameter is thrown

- func unwrap<NewValue>(orReplace: NewValue)`: Unwraps a future returning a new
  `EventLoopFuture` with either: the value passed in the `orReplace`
  parameter when the future resolved with value `Optional.none`, or
  the same value otherwise.

- func unwrap<NewValue>(orElse: @escaping ()- > NewValue): Unwraps a future
  returning a new `EventLoopFuture` with either: the value returned
  by the closure passed in the `orElse` parameter when the future
  resolved with value `Optional.none`, or the same value otherwise.

Added new unit tests for each new `unwrap(orXXX:)` function.

Result:

Client's no longer have to write their own boilerplate code.
This commit is contained in:
Graeme Jenkinson 2020-09-30 13:47:14 +01:00 committed by GitHub
parent 934de6a284
commit 7c42e5a45d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 140 additions and 0 deletions

View File

@ -1325,3 +1325,80 @@ extension EventLoopFuture {
return self
}
}
// MARK: unwrap
extension EventLoopFuture {
/// Unwrap an `EventLoopFuture` where its type parameter is an `Optional`.
///
/// Unwrap a future returning a new `EventLoopFuture`. When the resolved future's value is `Optional.some(...)`
/// the new future is created with the identical value. Otherwise the `Error` passed in the `orError` parameter
/// is thrown. For example:
/// ```
/// do {
/// try promise.futureResult.unwrap(orError: ErrorToThrow).wait()
/// } catch ErrorToThrow {
/// ...
/// }
/// ```
///
/// - parameters:
/// - orError: the `Error` that is thrown when then resolved future's value is `Optional.none`.
/// - returns: an new `EventLoopFuture` with new type parameter `NewValue` and the same value as the resolved
/// future.
/// - throws: the `Error` passed in the `orError` parameter when the resolved future's value is `Optional.none`.
@inlinable
public func unwrap<NewValue>(orError error: Error) -> EventLoopFuture<NewValue> where Value == Optional<NewValue> {
return self.flatMapThrowing { (value) throws -> NewValue in
guard let value = value else {
throw error
}
return value
}
}
/// Unwrap an `EventLoopFuture` where its type parameter is an `Optional`.
///
/// Unwraps a future returning a new `EventLoopFuture` with either: the value passed in the `orReplace`
/// parameter when the future resolved with value Optional.none, or the same value otherwise. For example:
/// ```
/// promise.futureResult.unwrap(orReplace: 42).wait()
/// ```
///
/// - parameters:
/// - orReplace: the value of the returned `EventLoopFuture` when then resolved future's value is `Optional.some()`.
/// - returns: an new `EventLoopFuture` with new type parameter `NewValue` and the value passed in the `orReplace` parameter.
@inlinable
public func unwrap<NewValue>(orReplace replacement: NewValue) -> EventLoopFuture<NewValue> where Value == Optional<NewValue> {
return self.map { (value) -> NewValue in
guard let value = value else {
return replacement
}
return value
}
}
/// Unwrap an `EventLoopFuture` where its type parameter is an `Optional`.
///
/// Unwraps a future returning a new `EventLoopFuture` with either: the value returned by the closure passed in
/// the `orElse` parameter when the future resolved with value Optional.none, or the same value otherwise. For example:
/// ```
/// var x = 2
/// promise.futureResult.unwrap(orElse: { x * 2 }).wait()
/// ```
///
/// - parameters:
/// - orElse: a closure that returns the value of the returned `EventLoopFuture` when then resolved future's value
/// is `Optional.some()`.
/// - returns: an new `EventLoopFuture` with new type parameter `NewValue` and with the value returned by the closure
/// passed in the `orElse` parameter.
@inlinable
public func unwrap<NewValue>(orElse callback: @escaping () -> NewValue) -> EventLoopFuture<NewValue> where Value == Optional<NewValue> {
return self.map { (value) -> NewValue in
guard let value = value else {
return callback()
}
return value
}
}
}

View File

@ -84,6 +84,12 @@ extension EventLoopFutureTest {
("testAndAllCompleteWithMixOfPreSuccededAndNotYetCompletedFutures", testAndAllCompleteWithMixOfPreSuccededAndNotYetCompletedFutures),
("testWhenAllCompleteWithMixOfPreSuccededAndNotYetCompletedFutures", testWhenAllCompleteWithMixOfPreSuccededAndNotYetCompletedFutures),
("testRepeatedTaskOffEventLoopGroupFuture", testRepeatedTaskOffEventLoopGroupFuture),
("testEventLoopFutureOrErrorNoThrow", testEventLoopFutureOrErrorNoThrow),
("testEventLoopFutureOrThrows", testEventLoopFutureOrThrows),
("testEventLoopFutureOrNoReplacement", testEventLoopFutureOrNoReplacement),
("testEventLoopFutureOrReplacement", testEventLoopFutureOrReplacement),
("testEventLoopFutureOrNoElse", testEventLoopFutureOrNoElse),
("testEventLoopFutureOrElse", testEventLoopFutureOrElse),
]
}
}

View File

@ -1207,5 +1207,62 @@ class EventLoopFutureTest : XCTestCase {
try exitPromise.futureResult.wait()
}
func testEventLoopFutureOrErrorNoThrow() {
let eventLoop = EmbeddedEventLoop()
let promise = eventLoop.makePromise(of: Int?.self)
let result: Result<Int?, Error> = .success(42)
promise.completeWith(result)
XCTAssertEqual(try promise.futureResult.unwrap(orError: EventLoopFutureTestError.example).wait(), 42)
}
func testEventLoopFutureOrThrows() {
let eventLoop = EmbeddedEventLoop()
let promise = eventLoop.makePromise(of: Int?.self)
let result: Result<Int?, Error> = .success(nil)
promise.completeWith(result)
XCTAssertThrowsError(try _ = promise.futureResult.unwrap(orError: EventLoopFutureTestError.example).wait()) { (error) -> Void in
XCTAssertEqual(error as! EventLoopFutureTestError, EventLoopFutureTestError.example)
}
}
func testEventLoopFutureOrNoReplacement() {
let eventLoop = EmbeddedEventLoop()
let promise = eventLoop.makePromise(of: Int?.self)
let result: Result<Int?, Error> = .success(42)
promise.completeWith(result)
XCTAssertEqual(try! promise.futureResult.unwrap(orReplace: 41).wait(), 42)
}
func testEventLoopFutureOrReplacement() {
let eventLoop = EmbeddedEventLoop()
let promise = eventLoop.makePromise(of: Int?.self)
let result: Result<Int?, Error> = .success(nil)
promise.completeWith(result)
XCTAssertEqual(try! promise.futureResult.unwrap(orReplace: 42).wait(), 42)
}
func testEventLoopFutureOrNoElse() {
let eventLoop = EmbeddedEventLoop()
let promise = eventLoop.makePromise(of: Int?.self)
let result: Result<Int?, Error> = .success(42)
promise.completeWith(result)
XCTAssertEqual(try! promise.futureResult.unwrap(orElse: { 41 } ).wait(), 42)
}
func testEventLoopFutureOrElse() {
let eventLoop = EmbeddedEventLoop()
let promise = eventLoop.makePromise(of: Int?.self)
let result: Result<Int?, Error> = .success(4)
promise.completeWith(result)
let x = 2
XCTAssertEqual(try! promise.futureResult.unwrap(orElse: { x * 2 } ).wait(), 4)
}
}