Skip to content

Commit

Permalink
♻️ Refactor lookupURL, searchURL
Browse files Browse the repository at this point in the history
Resolves unused protocol method lint warnings by moving updated implementations back to StoreSearch. Added `country` to MasStoreSearch, set to fixed fvalue for tests.
  • Loading branch information
phatblat committed Feb 18, 2024
1 parent 5aafd4c commit 521df64
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 71 deletions.
65 changes: 18 additions & 47 deletions Sources/MasKit/Controllers/MasStoreSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,58 +13,25 @@ import Version

/// Manages searching the MAS catalog through the iTunes Search and Lookup APIs.
class MasStoreSearch: StoreSearch {
private let networkManager: NetworkManager
private static let appVersionExpression = Regex(#"\"versionDisplay\"\:\"([^\"]+)\""#)

enum Entity: String {
case macSoftware
case iPadSoftware
case iPhoneSoftware = "software"
}
// CommerceKit and StoreFoundation don't seem to expose the region of the Apple ID signed
// into the App Store. Instead, we'll make an educated guess that it matches the currently
// selected locale in macOS. This obviously isn't always going to match, but it's probably
// better than passing no "country" at all to the iTunes Search API.
// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/
private let country: String?
private let networkManager: NetworkManager

/// Designated initializer.
init(networkManager: NetworkManager = NetworkManager()) {
init(
country: String? = Locale.autoupdatingCurrent.regionCode,
networkManager: NetworkManager = NetworkManager()
) {
self.country = country
self.networkManager = networkManager
}

/// Builds the search URL for an app.
///
/// - Parameter appName: MAS app identifier.
/// - Returns: URL for the search service or nil if appName can't be encoded.
static func searchURL(for appName: String, ofEntity entity: Entity = .macSoftware) -> URL {
guard var components = URLComponents(string: "https://itunes.apple.com/search") else {
fatalError("URLComponents failed to parse URL.")
}

components.queryItems = [
URLQueryItem(name: "media", value: "software"),
URLQueryItem(name: "entity", value: entity.rawValue),
URLQueryItem(name: "term", value: appName),
]
guard let url = components.url else {
fatalError("URLComponents failed to generate URL.")
}

return url
}

/// Builds the lookup URL for an app.
///
/// - Parameter appId: MAS app identifier.
/// - Returns: URL for the lookup service or nil if appId can't be encoded.
static func lookupURL(forApp appId: Int) -> URL {
guard var components = URLComponents(string: "https://itunes.apple.com/lookup") else {
fatalError("URLComponents failed to parse URL.")
}

components.queryItems = [URLQueryItem(name: "id", value: "\(appId)")]
guard let url = components.url else {
fatalError("URLComponents failed to generate URL.")
}

return url
}

/// Searches for an app.
///
/// - Parameter appName: MAS ID of app
Expand All @@ -79,7 +46,9 @@ class MasStoreSearch: StoreSearch {
}

let results = entities.map { entity -> Promise<[SearchResult]> in
let url = MasStoreSearch.searchURL(for: appName, ofEntity: entity)
guard let url = searchURL(for: appName, inCountry: country, ofEntity: entity) else {
fatalError("Failed to build URL for \(appName)")
}
return loadSearchResults(url)
}

Expand All @@ -96,7 +65,9 @@ class MasStoreSearch: StoreSearch {
/// - 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) -> Promise<SearchResult?> {
let url = MasStoreSearch.lookupURL(forApp: appId)
guard let url = lookupURL(forApp: appId, inCountry: country) else {
fatalError("Failed to build URL for \(appId)")
}
return firstly {
loadSearchResults(url)
}.then { results -> Guarantee<SearchResult?> in
Expand Down
31 changes: 12 additions & 19 deletions Sources/MasKit/Controllers/StoreSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,40 @@
// Copyright © 2018 mas-cli. All rights reserved.
//

import PromiseKit
import Foundation
import PromiseKit

/// Protocol for searching the MAS catalog.
protocol StoreSearch {
func lookup(app appId: Int) -> Promise<SearchResult?>
func search(for appName: String) -> Promise<[SearchResult]>
}

enum Entity: String {
case macSoftware
case iPadSoftware
case iPhoneSoftware = "software"
}

// MARK: - Common methods
extension StoreSearch {
/// Builds the search URL for an app.
///
/// - Parameter appName: MAS app identifier.
/// - Returns: URL for the search service or nil if appName can't be encoded.
func searchURL(for appName: String) -> URL? {
func searchURL(for appName: String, inCountry country: String?, ofEntity entity: Entity = .macSoftware) -> URL? {
guard var components = URLComponents(string: "https://itunes.apple.com/search") else {
return nil
}

components.queryItems = [
URLQueryItem(name: "media", value: "software"),
URLQueryItem(name: "entity", value: "macSoftware"),
URLQueryItem(name: "entity", value: entity.rawValue),
URLQueryItem(name: "term", value: appName),
]

if let country {
components.queryItems!.append(country)
components.queryItems!.append(URLQueryItem(name: "country", value: country))
}

return components.url
Expand All @@ -43,7 +49,7 @@ extension StoreSearch {
///
/// - Parameter appId: MAS app identifier.
/// - Returns: URL for the lookup service or nil if appId can't be encoded.
func lookupURL(forApp appId: Int) -> URL? {
func lookupURL(forApp appId: Int, inCountry country: String?) -> URL? {
guard var components = URLComponents(string: "https://itunes.apple.com/lookup") else {
return nil
}
Expand All @@ -54,22 +60,9 @@ extension StoreSearch {
]

if let country {
components.queryItems!.append(country)
components.queryItems!.append(URLQueryItem(name: "country", value: country))
}

return components.url
}

private var country: URLQueryItem? {
// CommerceKit and StoreFoundation don't seem to expose the region of the Apple ID signed
// into the App Store. Instead, we'll make an educated guess that it matches the currently
// selected locale in macOS. This obviously isn't always going to match, but it's probably
// better than passing no "country" at all to the iTunes Search API.
// https://affiliate.itunes.apple.com/resources/documentation/itunes-store-web-service-search-api/
guard let region = Locale.autoupdatingCurrent.regionCode else {
return nil
}

return URLQueryItem(name: "country", value: region)
}
}
13 changes: 8 additions & 5 deletions Tests/MasKitTests/Controllers/MasStoreSearchSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ public class MasStoreSearchSpec: QuickSpec {
describe("url string") {
it("contains the app name") {
let appName = "myapp"
let urlString = MasStoreSearch.searchURL(for: appName).absoluteString
expect(urlString) == "https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appName)"
let urlString = MasStoreSearch().searchURL(for: appName, inCountry: "US")?.absoluteString
expect(urlString) == """
https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appName)&country=US
"""
}
it("contains the encoded app name") {
let appName = "My App"
let appNameEncoded = "My%20App"
let urlString = MasStoreSearch.searchURL(for: appName).absoluteString
expect(urlString)
== "https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appNameEncoded)"
let urlString = MasStoreSearch().searchURL(for: appName, inCountry: "US")?.absoluteString
expect(urlString) == """
https://itunes.apple.com/search?media=software&entity=macSoftware&term=\(appNameEncoded)&country=US
"""
}
}
describe("store") {
Expand Down

0 comments on commit 521df64

Please sign in to comment.