diff --git a/Sources/Guarantee.swift b/Sources/Guarantee.swift index d032f9b7e..ce887cdc3 100644 --- a/Sources/Guarantee.swift +++ b/Sources/Guarantee.swift @@ -105,7 +105,7 @@ public extension Guarantee { return rg } - #if swift(>=4) + #if swift(>=4) && !swift(>=5.2) func map(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath) -> Guarantee { let rg = Guarantee(.pending) pipe { value in @@ -117,7 +117,7 @@ public extension Guarantee { } #endif - @discardableResult + @discardableResult func then(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) -> Guarantee) -> Guarantee { let rg = Guarantee(.pending) pipe { value in @@ -169,6 +169,21 @@ public extension Guarantee where T: Sequence { return map(on: on, flags: flags) { $0.map(transform) } } + #if swift(>=4) && !swift(>=5.2) + /** + `Guarantee<[T]>` => `KeyPath` => `Guarantee<[U]>` + + Guarantee.value([Person(name: "Max"), Person(name: "Roman"), Person(name: "John")]) + .mapValues(\.name) + .done { + // $0 => ["Max", "Roman", "John"] + } + */ + func mapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath) -> Guarantee<[U]> { + return map(on: on, flags: flags) { $0.map { $0[keyPath: keyPath] } } + } + #endif + /** `Guarantee<[T]>` => `T` -> `[U]` => `Guarantee<[U]>` @@ -203,6 +218,27 @@ public extension Guarantee where T: Sequence { } } + #if swift(>=4) && !swift(>=5.2) + /** + `Guarantee<[T]>` => `KeyPath` => `Guarantee<[U]>` + + Guarantee.value([Person(name: "Max"), Person(name: "Roman", age: 26), Person(name: "John", age: 23)]) + .compactMapValues(\.age) + .done { + // $0 => [26, 23] + } + */ + func compactMapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath) -> Guarantee<[U]> { + return map(on: on, flags: flags) { foo -> [U] in + #if !swift(>=4.1) + return foo.flatMap { $0[keyPath: keyPath] } + #else + return foo.compactMap { $0[keyPath: keyPath] } + #endif + } + } + #endif + /** `Guarantee<[T]>` => `T` -> `Guarantee` => `Guaranetee<[U]>` @@ -256,6 +292,23 @@ public extension Guarantee where T: Sequence { } } + #if swift(>=4) && !swift(>=5.2) + /** + `Guarantee<[T]>` => `KeyPath` => `Guarantee<[T]>` + + Guarantee.value([Person(name: "Max"), Person(name: "Roman", age: 26, isStudent: false), Person(name: "John", age: 23, isStudent: true)]) + .filterValues(\.isStudent) + .done { + // $0 => [Person(name: "John", age: 23, isStudent: true)] + } + */ + func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath) -> Guarantee<[T.Iterator.Element]> { + return map(on: on, flags: flags) { + $0.filter { $0[keyPath: keyPath] } + } + } + #endif + /** `Guarantee<[T]>` => (`T`, `T`) -> Bool => `Guarantee<[T]>` diff --git a/Sources/Thenable.swift b/Sources/Thenable.swift index 62cc1c179..a44e606c7 100644 --- a/Sources/Thenable.swift +++ b/Sources/Thenable.swift @@ -87,7 +87,7 @@ public extension Thenable { return rp } - #if swift(>=4) + #if swift(>=4) && !swift(>=5.2) /** Similar to func `map(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U) -> Promise`, but accepts a key path instead of a closure. @@ -149,6 +149,38 @@ public extension Thenable { return rp } + #if swift(>=4) && !swift(>=5.2) + /** + Similar to func `compactMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T) throws -> U?) -> Promise`, but accepts a key path instead of a closure. + + - Parameter on: The queue to which the provided key path for value dispatches. + - Parameter keyPath: The key path to the value that is using when this Promise is fulfilled. If the value for `keyPath` is `nil` the resulting promise is rejected with `PMKError.compactMap`. + - Returns: A new promise that is fulfilled with the value for the provided key path. + */ + func compactMap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath) -> Promise { + let rp = Promise(.pending) + pipe { + switch $0 { + case .fulfilled(let value): + on.async(flags: flags) { + do { + if let rv = value[keyPath: keyPath] { + rp.box.seal(.fulfilled(rv)) + } else { + throw PMKError.compactMap(value, U.self) + } + } catch { + rp.box.seal(.rejected(error)) + } + } + case .rejected(let error): + rp.box.seal(.rejected(error)) + } + } + return rp + } + #endif + /** The provided closure is executed when this promise is fulfilled. @@ -314,6 +346,21 @@ public extension Thenable where T: Sequence { return map(on: on, flags: flags){ try $0.map(transform) } } + #if swift(>=4) && !swift(>=5.2) + /** + `Promise<[T]>` => `KeyPath` => `Promise<[U]>` + + firstly { + .value([Person(name: "Max"), Person(name: "Roman"), Person(name: "John")]) + }.mapValues(\.name).done { + // $0 => ["Max", "Roman", "John"] + } + */ + func mapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath) -> Promise<[U]> { + return map(on: on, flags: flags){ $0.map { $0[keyPath: keyPath] } } + } + #endif + /** `Promise<[T]>` => `T` -> `[U]` => `Promise<[U]>` @@ -352,6 +399,27 @@ public extension Thenable where T: Sequence { } } + #if swift(>=4) && !swift(>=5.2) + /** + `Promise<[T]>` => `KeyPath` => `Promise<[U]>` + + firstly { + .value([Person(name: "Max"), Person(name: "Roman", age: 26), Person(name: "John", age: 23)]) + }.compactMapValues(\.age).done { + // $0 => [26, 23] + } + */ + func compactMapValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath) -> Promise<[U]> { + return map(on: on, flags: flags) { foo -> [U] in + #if !swift(>=4.1) + return foo.flatMap { $0[keyPath: keyPath] } + #else + return foo.compactMap { $0[keyPath: keyPath] } + #endif + } + } + #endif + /** `Promise<[T]>` => `T` -> `Promise` => `Promise<[U]>` @@ -404,6 +472,23 @@ public extension Thenable where T: Sequence { $0.filter(isIncluded) } } + + #if swift(>=4) && !swift(>=5.2) + /** + `Promise<[T]>` => `KeyPath` => `Promise<[T]>` + + firstly { + .value([Person(name: "Max"), Person(name: "Roman", age: 26, isStudent: false), Person(name: "John", age: 23, isStudent: true)]) + }.filterValues(\.isStudent).done { + // $0 => [Person(name: "John", age: 23, isStudent: true)] + } + */ + func filterValues(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ keyPath: KeyPath) -> Promise<[T.Iterator.Element]> { + return map(on: on, flags: flags) { + $0.filter { $0[keyPath: keyPath] } + } + } + #endif } public extension Thenable where T: Collection { diff --git a/Tests/CorePromise/GuaranteeTests.swift b/Tests/CorePromise/GuaranteeTests.swift index 33e02e636..9ee2f2847 100644 --- a/Tests/CorePromise/GuaranteeTests.swift +++ b/Tests/CorePromise/GuaranteeTests.swift @@ -26,12 +26,12 @@ class GuaranteeTests: XCTestCase { wait(for: [ex], timeout: 10) } - #if swift(>=4) + #if swift(>=4) && !swift(>=5.2) func testMapByKeyPath() { let ex = expectation(description: "") - Guarantee.value("Hello world").map(\.count).done { - XCTAssertEqual(11, $0) + Guarantee.value(Person(name: "Max")).map(\.name).done { + XCTAssertEqual("Max", $0) ex.fulfill() } @@ -56,6 +56,21 @@ class GuaranteeTests: XCTestCase { wait(for: [ex], timeout: 10) } + #if swift(>=4) && !swift(>=5.2) + func testMapValuesByKeyPath() { + let ex = expectation(description: "") + + Guarantee.value([Person(name: "Max"), Person(name: "Roman"), Person(name: "John")]) + .mapValues(\.name) + .done { values in + XCTAssertEqual(["Max", "Roman", "John"], values) + ex.fulfill() + } + + wait(for: [ex], timeout: 10) + } + #endif + func testFlatMapValues() { let ex = expectation(description: "") @@ -82,6 +97,21 @@ class GuaranteeTests: XCTestCase { wait(for: [ex], timeout: 10) } + #if swift(>=4) && !swift(>=5.2) + func testCompactMapValuesByKeyPath() { + let ex = expectation(description: "") + + Guarantee.value([Person(name: "Max"), Person(name: "Roman", age: 26), Person(name: "John", age: 23)]) + .compactMapValues(\.age) + .done { values in + XCTAssertEqual([26, 23], values) + ex.fulfill() + } + + wait(for: [ex], timeout: 10) + } + #endif + func testThenMap() { let ex = expectation(description: "") @@ -124,6 +154,22 @@ class GuaranteeTests: XCTestCase { wait(for: [ex], timeout: 10) } + #if swift(>=4) && !swift(>=5.2) + func testFilterValuesByKeyPath() { + + let ex = expectation(description: "") + + Guarantee.value([Person(name: "Max"), Person(name: "Roman", age: 26, isStudent: false), Person(name: "John", age: 23, isStudent: true)]) + .filterValues(\.isStudent) + .done { values in + XCTAssertEqual([Person(name: "John", age: 23, isStudent: true)], values) + ex.fulfill() + } + + wait(for: [ex], timeout: 10) + } + #endif + func testSorted() { let ex = expectation(description: "") diff --git a/Tests/CorePromise/ThenableTests.swift b/Tests/CorePromise/ThenableTests.swift index 485833cbd..a75dc89bb 100644 --- a/Tests/CorePromise/ThenableTests.swift +++ b/Tests/CorePromise/ThenableTests.swift @@ -2,6 +2,22 @@ import PromiseKit import Dispatch import XCTest +struct Person: Equatable { + let name: String + let age: Int? + let isStudent: Bool + + init( + name: String = "", + age: Int? = nil, + isStudent: Bool = false + ) { + self.name = name + self.age = age + self.isStudent = isStudent + } +} + class ThenableTests: XCTestCase { func testGet() { let ex1 = expectation(description: "") @@ -27,11 +43,11 @@ class ThenableTests: XCTestCase { wait(for: [ex], timeout: 10) } - #if swift(>=4) + #if swift(>=4) && !swift(>=5.2) func testMapByKeyPath() { let ex = expectation(description: "") - Promise.value("Hello world").map(\.count).done { - XCTAssertEqual($0, 11) + Promise.value(Person(name: "Max")).map(\.name).done { + XCTAssertEqual($0, "Max") ex.fulfill() }.silenceWarning() wait(for: [ex], timeout: 10) @@ -94,6 +110,39 @@ class ThenableTests: XCTestCase { wait(for: [ex], timeout: 10) } + #if swift(>=4) && !swift(>=5.2) + func testCompactMapByKeyPath() { + let ex = expectation(description: "") + Promise.value(Person(name: "Roman", age: 26)).compactMap(\.age).done { + XCTAssertEqual($0, 26) + ex.fulfill() + }.silenceWarning() + wait(for: [ex], timeout: 10) + } + #endif + + func testMapValues() { + let ex = expectation(description: "") + Promise.value([14, 20, 45]).mapValues { + $0 * 2 + }.done { + XCTAssertEqual([28, 40, 90], $0) + ex.fulfill() + }.silenceWarning() + wait(for: [ex], timeout: 10) + } + + #if swift(>=4) && !swift(>=5.2) + func testMapValuesByKeyPath() { + let ex = expectation(description: "") + Promise.value([Person(name: "Max"), Person(name: "Roman"), Person(name: "John")]).mapValues(\.name).done { + XCTAssertEqual(["Max", "Roman", "John"], $0) + ex.fulfill() + }.silenceWarning() + wait(for: [ex], timeout: 10) + } + #endif + func testCompactMapValues() { let ex = expectation(description: "") Promise.value(["1","2","a","4"]).compactMapValues { @@ -105,6 +154,17 @@ class ThenableTests: XCTestCase { wait(for: [ex], timeout: 10) } + #if swift(>=4) && !swift(>=5.2) + func testCompactMapValuesByKeyPath() { + let ex = expectation(description: "") + Promise.value([Person(name: "Max"), Person(name: "Roman", age: 26), Person(name: "John", age: 23)]).compactMapValues(\.age).done { + XCTAssertEqual([26, 23], $0) + ex.fulfill() + }.silenceWarning() + wait(for: [ex], timeout: 10) + } + #endif + func testThenMap() { let ex = expectation(description: "") Promise.value([1,2,3,4]).thenMap { @@ -127,6 +187,28 @@ class ThenableTests: XCTestCase { wait(for: [ex], timeout: 10) } + func testFilterValues() { + let ex = expectation(description: "") + Promise.value([Person(name: "Max"), Person(name: "Roman", age: 26, isStudent: false), Person(name: "John", age: 23, isStudent: true)]).filterValues { + $0.isStudent + }.done { + XCTAssertEqual([Person(name: "John", age: 23, isStudent: true)], $0) + ex.fulfill() + }.silenceWarning() + wait(for: [ex], timeout: 10) + } + + #if swift(>=4) && !swift(>=5.2) + func testFilterValuesByKeyPath() { + let ex = expectation(description: "") + Promise.value([Person(name: "Max"), Person(name: "Roman", age: 26, isStudent: false), Person(name: "John", age: 23, isStudent: true)]).filterValues(\.isStudent).done { + XCTAssertEqual([Person(name: "John", age: 23, isStudent: true)], $0) + ex.fulfill() + }.silenceWarning() + wait(for: [ex], timeout: 10) + } + #endif + func testLastValueForEmpty() { XCTAssertTrue(Promise.value([]).lastValue.isRejected) } diff --git a/Tests/CorePromise/XCTestManifests.swift b/Tests/CorePromise/XCTestManifests.swift index 4d0824cdc..9fbe50243 100644 --- a/Tests/CorePromise/XCTestManifests.swift +++ b/Tests/CorePromise/XCTestManifests.swift @@ -56,12 +56,15 @@ extension GuaranteeTests { // to regenerate. static let __allTests__GuaranteeTests = [ ("testCompactMapValues", testCompactMapValues), + ("testCompactMapValuesByKeyPath", testCompactMapValuesByKeyPath), ("testFilterValues", testFilterValues), + ("testFilterValuesByKeyPath", testFilterValuesByKeyPath), ("testFlatMapValues", testFlatMapValues), ("testInit", testInit), ("testMap", testMap), ("testMapByKeyPath", testMapByKeyPath), ("testMapValues", testMapValues), + ("testMapValuesByKeyPath", testMapValuesByKeyPath), ("testNoAmbiguityForValue", testNoAmbiguityForValue), ("testSorted", testSorted), ("testSortedBy", testSortedBy), @@ -188,14 +191,20 @@ extension ThenableTests { static let __allTests__ThenableTests = [ ("testBarrier", testBarrier), ("testCompactMap", testCompactMap), + ("testCompactMapByKeyPath", testCompactMapByKeyPath), ("testCompactMapThrows", testCompactMapThrows), ("testCompactMapValues", testCompactMapValues), + ("testCompactMapValuesByKeyPath", testCompactMapValuesByKeyPath), ("testDispatchFlagsSyntax", testDispatchFlagsSyntax), + ("testFilterValues", testFilterValues), + ("testFilterValuesByKeyPath", testFilterValuesByKeyPath), ("testFirstValueForEmpty", testFirstValueForEmpty), ("testGet", testGet), ("testLastValueForEmpty", testLastValueForEmpty), ("testMap", testMap), ("testMapByKeyPath", testMapByKeyPath), + ("testMapValues", testMapValues), + ("testMapValuesByKeyPath", testMapValuesByKeyPath), ("testPMKErrorCompactMap", testPMKErrorCompactMap), ("testRejectedPromiseCompactMap", testRejectedPromiseCompactMap), ("testThenFlatMap", testThenFlatMap),