🤞🏼 Rephrase StoreSearch as Promises

This commit is contained in:
Chris Araman 2021-05-06 15:08:56 -07:00
parent a9b018854d
commit 9494dea403
No known key found for this signature in database
GPG key ID: BB4499D9E11B61E0
13 changed files with 48 additions and 119 deletions

View file

@ -38,7 +38,7 @@ public struct HomeCommand: CommandProtocol {
/// Runs the command.
public func run(_ options: HomeOptions) -> Result<Void, MASError> {
do {
guard let result = try storeSearch.lookup(app: options.appId) else {
guard let result = try storeSearch.lookup(app: options.appId).wait() else {
print("No results found")
return .failure(.noSearchResultsFound)
}

View file

@ -29,7 +29,7 @@ public struct InfoCommand: CommandProtocol {
/// Runs the command.
public func run(_ options: InfoOptions) -> Result<Void, MASError> {
do {
guard let result = try storeSearch.lookup(app: options.appId) else {
guard let result = try storeSearch.lookup(app: options.appId).wait() else {
print("No results found")
return .failure(.noSearchResultsFound)
}

View file

@ -45,7 +45,7 @@ public struct LuckyCommand: CommandProtocol {
var appId: Int?
do {
let results = try storeSearch.search(for: options.appName)
let results = try storeSearch.search(for: options.appName).wait()
guard let result = results.first else {
print("No results found")
return .failure(.noSearchResultsFound)

View file

@ -54,7 +54,7 @@ public struct OpenCommand: CommandProtocol {
return .failure(.noSearchResultsFound)
}
guard let result = try storeSearch.lookup(app: appId)
guard let result = try storeSearch.lookup(app: appId).wait()
else {
print("No results found")
return .failure(.noSearchResultsFound)

View file

@ -8,6 +8,8 @@
import Commandant
import Foundation
import PromiseKit
import enum Swift.Result
/// Command which displays a list of installed apps which have available updates
/// ready to be installed from the Mac App Store.
@ -34,19 +36,10 @@ public struct OutdatedCommand: CommandProtocol {
/// Runs the command.
public func run(_: Options) -> Result<Void, MASError> {
var failure: MASError?
let group = DispatchGroup()
for installedApp in appLibrary.installedApps {
group.enter()
storeSearch.lookup(app: installedApp.itemIdentifier.intValue) { storeApp, error in
defer { group.leave() }
guard error == nil else {
// Bubble up MASErrors
failure = error as? MASError ?? .searchFailed
return
}
let promises = appLibrary.installedApps.map { installedApp in
firstly {
storeSearch.lookup(app: installedApp.itemIdentifier.intValue)
}.done { storeApp in
guard let storeApp = storeApp else {
printWarning(
"""
@ -66,12 +59,13 @@ public struct OutdatedCommand: CommandProtocol {
}
}
group.wait()
if let failure = failure {
return .failure(failure)
}
return .success(())
return firstly {
when(fulfilled: promises)
}.map {
Result<Void, MASError>.success(())
}.recover { error in
// Bubble up MASErrors
.value(Result<Void, MASError>.failure(error as? MASError ?? .searchFailed))
}.wait()
}
}

View file

@ -30,7 +30,7 @@ public struct SearchCommand: CommandProtocol {
public func run(_ options: Options) -> Result<Void, MASError> {
do {
let results = try storeSearch.search(for: options.appName)
let results = try storeSearch.search(for: options.appName).wait()
if results.isEmpty {
print("No results found")
return .failure(.noSearchResultsFound)

View file

@ -94,7 +94,7 @@ public struct UpgradeCommand: CommandProtocol {
for installedApp in apps {
// only upgrade apps whose local version differs from the store version
group.enter()
storeSearch.lookup(app: installedApp.itemIdentifier.intValue) { result, _ in
try storeSearch.lookup(app: installedApp.itemIdentifier.intValue).done { result in
defer { group.leave() }
if let storeApp = result, installedApp.isOutdatedWhenComparedTo(storeApp) {
@ -103,7 +103,7 @@ public struct UpgradeCommand: CommandProtocol {
outdated.append(installedApp)
}
}
}.wait()
}
group.wait()

View file

@ -38,7 +38,7 @@ public struct VendorCommand: CommandProtocol {
/// Runs the command.
public func run(_ options: VendorOptions) -> Result<Void, MASError> {
do {
guard let result = try storeSearch.lookup(app: options.appId)
guard let result = try storeSearch.lookup(app: options.appId).wait()
else {
print("No results found")
return .failure(.noSearchResultsFound)

View file

@ -31,41 +31,27 @@ class MasStoreSearch: StoreSearch {
/// - Parameter appName: MAS ID of app
/// - Parameter completion: A closure that receives the search results or an Error if there is a
/// problem with the network request. Results array will be empty if there were no matches.
func search(for appName: String, _ completion: @escaping ([SearchResult]?, Error?) -> Void) {
func search(for appName: String) -> Promise<[SearchResult]> {
guard let url = searchURL(for: appName)
else {
completion(nil, MASError.urlEncoding)
return
return Promise(error: MASError.urlEncoding)
}
firstly {
loadSearchResults(url)
}.done { results in
completion(results, nil)
}.catch { error in
completion(nil, error)
}
return loadSearchResults(url)
}
/// Looks up app details.
///
/// - Parameter appId: MAS ID of app
/// - Parameter completion: A closure that receives the search result record of app, or nil if no apps match the ID,
/// - Returns: A Promise for the search result record of app, or nil if no apps match the ID,
/// or an Error if there is a problem with the network request.
func lookup(app appId: Int, _ completion: @escaping (SearchResult?, Error?) -> Void) {
func lookup(app appId: Int) -> Promise<SearchResult?> {
guard let url = lookupURL(forApp: appId)
else {
completion(nil, MASError.urlEncoding)
return
return Promise(error: MASError.urlEncoding)
}
firstly {
loadSearchResults(url)
}.done { results in
completion(results.first, nil)
}.catch { error in
completion(nil, error)
}
return loadSearchResults(url).map { results in results.first }
}
private func loadSearchResults(_ url: URL) -> Promise<[SearchResult]> {

View file

@ -7,67 +7,16 @@
//
import Foundation
import PromiseKit
/// Protocol for searching the MAS catalog.
protocol StoreSearch {
func lookup(app appId: Int, _ completion: @escaping (SearchResult?, Error?) -> Void)
func search(for appName: String, _ completion: @escaping ([SearchResult]?, Error?) -> Void)
func lookup(app appId: Int) -> Promise<SearchResult?>
func search(for appName: String) -> Promise<[SearchResult]>
}
// MARK: - Common methods
extension StoreSearch {
/// Looks up app details.
///
/// - Parameter appId: MAS ID of app
/// - Returns: Search result record of app or nil if no apps match the ID.
/// - Throws: Error if there is a problem with the network request.
func lookup(app appId: Int) throws -> SearchResult? {
var result: SearchResult?
var error: Error?
let group = DispatchGroup()
group.enter()
lookup(app: appId) {
result = $0
error = $1
group.leave()
}
group.wait()
if let error = error {
throw error
}
return result
}
/// Searches for an app.
///
/// - Parameter appName: MAS ID of app
/// - Returns: Search results. Empty if there were no matches.
/// - Throws: Error if there is a problem with the network request.
func search(for appName: String) throws -> [SearchResult] {
var results: [SearchResult]?
var error: Error?
let group = DispatchGroup()
group.enter()
search(for: appName) {
results = $0
error = $1
group.leave()
}
group.wait()
if let error = error {
throw error
}
return results!
}
/// Builds the search URL for an app.
///
/// - Parameter appName: MAS app identifier.

View file

@ -24,7 +24,7 @@ public class MasStoreSearchSpec: QuickSpec {
var results: [SearchResult]
do {
results = try storeSearch.search(for: "slack")
results = try storeSearch.search(for: "slack").wait()
expect(results.count) == 39
} catch {
let maserror = error as! MASError
@ -43,7 +43,7 @@ public class MasStoreSearchSpec: QuickSpec {
var lookup: SearchResult?
do {
lookup = try storeSearch.lookup(app: appId)
lookup = try storeSearch.lookup(app: appId).wait()
} catch {
let maserror = error as! MASError
if case .jsonParsing(let nserror) = maserror {

View file

@ -6,31 +6,30 @@
// Copyright © 2019 mas-cli. All rights reserved.
//
import PromiseKit
@testable import MasKit
class StoreSearchMock: StoreSearch {
var apps: [Int: SearchResult] = [:]
func search(for appName: String, _ completion: @escaping ([SearchResult]?, Error?) -> Void) {
func search(for appName: String) -> Promise<[SearchResult]> {
let filtered = apps.filter { $1.trackName.contains(appName) }
let results = filtered.map { $1 }
completion(results, nil)
return .value(results)
}
func lookup(app appId: Int, _ completion: @escaping (SearchResult?, Error?) -> Void) {
func lookup(app appId: Int) -> Promise<SearchResult?> {
// Negative numbers are invalid
guard appId > 0 else {
completion(nil, MASError.searchFailed)
return
return Promise(error: MASError.searchFailed)
}
guard let result = apps[appId]
else {
completion(nil, MASError.noSearchResultsFound)
return
return Promise(error: MASError.noSearchResultsFound)
}
completion(result, nil)
return .value(result)
}
func reset() {

View file

@ -5,19 +5,20 @@
// Created by Ben Chatelain on 1/11/19.
// Copyright © 2019 mas-cli. All rights reserved.
//
import Nimble
import Quick
import Nimble
import PromiseKit
import Quick
@testable import MasKit
/// Protocol minimal implementation
struct StoreSearchForTesting: StoreSearch {
func lookup(app _: Int, _ completion: @escaping (SearchResult?, Error?) -> Void) {
completion(nil, nil)
func lookup(app _: Int) -> Promise<SearchResult?> {
.value(nil)
}
func search(for _: String, _ completion: @escaping ([SearchResult]?, Error?) -> Void) {
completion([], nil)
func search(for _: String) -> Promise<[SearchResult]> {
.value([])
}
}