diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c0987c9d..8ba2282c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Guide: https://keepachangelog.com/en/1.0.0/ ## Unreleased +- [Details] Add `SearchEngine.retrieve(mapboxID: String, options: DetailsOptions)` function. + ## 2.7.1 - [Core] Update dependencies. diff --git a/MapboxSearch.xcodeproj/project.pbxproj b/MapboxSearch.xcodeproj/project.pbxproj index 152988229..8283383de 100644 --- a/MapboxSearch.xcodeproj/project.pbxproj +++ b/MapboxSearch.xcodeproj/project.pbxproj @@ -13,6 +13,10 @@ 042477C52B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; }; 042477C62B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; }; 042477C72B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; }; + 0427854F2C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */ = {isa = PBXBuildFile; fileRef = 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */; }; + 042785502C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */ = {isa = PBXBuildFile; fileRef = 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */; }; + 042785512C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */ = {isa = PBXBuildFile; fileRef = 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */; }; + 042785582C79008300A0934D /* SearchEngineTests+Details.swift in Sources */ = {isa = PBXBuildFile; fileRef = 042785572C79008300A0934D /* SearchEngineTests+Details.swift */; }; 042BEB172C2DDFAA0004CD7B /* MapboxCommon in Frameworks */ = {isa = PBXBuildFile; productRef = 042BEB162C2DDFAA0004CD7B /* MapboxCommon */; }; 042BEB1A2C2DE30E0004CD7B /* MapboxMaps in Frameworks */ = {isa = PBXBuildFile; productRef = 042BEB192C2DE30E0004CD7B /* MapboxMaps */; }; 043339CD2C61295D001650FA /* Atlantis in Frameworks */ = {isa = PBXBuildFile; productRef = 043339CC2C61295D001650FA /* Atlantis */; }; @@ -51,6 +55,7 @@ 04970F8D2B7A97C900213763 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 04970F8C2B7A97C900213763 /* PrivacyInfo.xcprivacy */; }; 0498A7442CB486AE008F8903 /* ForwardExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0498A7422CB48683008F8903 /* ForwardExampleViewController.swift */; }; 049FE3A82C6A50BB00F54FB2 /* RetrieveOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 049FE3A72C6A50BB00F54FB2 /* RetrieveOptions.swift */; }; + 04A26C652C7535BD00443527 /* DetailsOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04A26C642C7535BD00443527 /* DetailsOptions.swift */; }; 04A972CC2C938B8800C5F1A8 /* UserRecordsLayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04A972C62C923C5300C5F1A8 /* UserRecordsLayerTests.swift */; }; 04AB0B4B2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */; }; 04AB0B4C2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */; }; @@ -558,6 +563,8 @@ 0405809C2BA8E67D00A54CB9 /* OwningObjectDeallocatedErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwningObjectDeallocatedErrorTests.swift; sourceTree = ""; }; 042477C12B7290E700D870D5 /* SearchEngineGeocodingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEngineGeocodingIntegrationTests.swift; sourceTree = ""; }; 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "geocoding-reverse-geocoding.json"; sourceTree = ""; }; + 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "search-box-retrieve-mapbox-id.json"; sourceTree = ""; }; + 042785572C79008300A0934D /* SearchEngineTests+Details.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchEngineTests+Details.swift"; sourceTree = ""; }; 04340D1F2C6BD06500B4F242 /* ResultChildMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultChildMetadata.swift; sourceTree = ""; }; 043A3D4C2B30F38300DB681B /* CoreAddress+AddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreAddress+AddressComponents.swift"; sourceTree = ""; }; 044A6B722BA8933200A9F2A2 /* PreviewCategoriesFavoritesSegmentControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewCategoriesFavoritesSegmentControl.swift; sourceTree = ""; }; @@ -578,6 +585,7 @@ 04970F8C2B7A97C900213763 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 0498A7422CB48683008F8903 /* ForwardExampleViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardExampleViewController.swift; sourceTree = ""; }; 049FE3A72C6A50BB00F54FB2 /* RetrieveOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrieveOptions.swift; sourceTree = ""; }; + 04A26C642C7535BD00443527 /* DetailsOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsOptions.swift; sourceTree = ""; }; 04A972C62C923C5300C5F1A8 /* UserRecordsLayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRecordsLayerTests.swift; sourceTree = ""; }; 04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = mapbox.places.san.francisco.json; sourceTree = ""; }; 04AB0B792B6AF37800FDE7D5 /* DiscoverIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverIntegrationTests.swift; sourceTree = ""; }; @@ -1048,6 +1056,14 @@ path = autofill; sourceTree = ""; }; + 042785562C79006E00A0934D /* Details */ = { + isa = PBXGroup; + children = ( + 042785572C79008300A0934D /* SearchEngineTests+Details.swift */, + ); + path = Details; + sourceTree = ""; + }; 046818D22B87F2810082B188 /* search-box */ = { isa = PBXGroup; children = ( @@ -1071,6 +1087,7 @@ 046818D62B87FA450082B188 /* search-box */ = { isa = PBXGroup; children = ( + 0427854E2C77899700A0934D /* search-box-retrieve-mapbox-id.json */, 04DFB41B2B8CFEC100231830 /* search-box-retrieve-san-francisco.json */, 046818DA2B87FAB20082B188 /* search-box-category.json */, 046818E02B87FF5C0082B188 /* search-box-suggestions-minsk.json */, @@ -1115,6 +1132,15 @@ path = Resources; sourceTree = ""; }; + 04A26C632C75359B00443527 /* SearchRequest */ = { + isa = PBXGroup; + children = ( + 049FE3A72C6A50BB00F54FB2 /* RetrieveOptions.swift */, + 04A26C642C7535BD00443527 /* DetailsOptions.swift */, + ); + path = SearchRequest; + sourceTree = ""; + }; 0498A7412CB4866E008F8903 /* Forward */ = { isa = PBXGroup; children = ( @@ -1288,6 +1314,7 @@ 141789F0287C30450000AE79 /* Use Cases */ = { isa = PBXGroup; children = ( + 042785562C79006E00A0934D /* Details */, 14FA6577295347E700056E5B /* Place Autocomplete */, 14FA6576295347CB00056E5B /* Address Autofill */, ); @@ -1641,9 +1668,9 @@ FEEDD2E32508DFE400DC0A98 /* AbstractSearchEngine.swift */, 04C127542B62F6BC00884325 /* ApiType.swift */, FEEDD2D82508DFE400DC0A98 /* SearchEngine.swift */, - 049FE3A72C6A50BB00F54FB2 /* RetrieveOptions.swift */, FEEDD2EB2508DFE400DC0A98 /* CategorySearchEngine.swift */, 04C0848C2B4C82F3002F9C69 /* SdkInformation.swift */, + 04A26C632C75359B00443527 /* SearchRequest */, ); path = Engine; sourceTree = ""; @@ -2345,6 +2372,7 @@ F9B62CCB264BCC2600492999 /* suggestions-empty.json in Resources */, 048823492B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */, 04AB0B4C2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */, + 042785502C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */, 04DFB4112B8CF46D00231830 /* search-box-retrieve-categories.json in Resources */, 04DFB4212B8D0ABE00231830 /* search-box-retrieve-recursion.json in Resources */, 046818DC2B87FAB20082B188 /* search-box-category.json in Resources */, @@ -2381,6 +2409,7 @@ 04DFB4162B8CFD4700231830 /* search-box-suggestions-san-francisco.json in Resources */, F94FFA4826453D410019ED9B /* reverse-geocoding-sbs.json in Resources */, 04DFB4222B8D0ABE00231830 /* search-box-retrieve-recursion.json in Resources */, + 042785512C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */, 046818E32B87FF5C0082B188 /* search-box-suggestions-minsk.json in Resources */, 2CD8AC4B29F1D38800C47BE4 /* suggestions-with-mixed-coordinates.json in Resources */, 0477904B2B890A8500A99528 /* search-box-recursion.json in Resources */, @@ -2415,6 +2444,7 @@ F9C557A52670CB0400BE8B94 /* suggestions-empty.json in Resources */, 048823482B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */, 04AB0B4B2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */, + 0427854F2C77899700A0934D /* search-box-retrieve-mapbox-id.json in Resources */, 04DFB4102B8CF46D00231830 /* search-box-retrieve-categories.json in Resources */, 04DFB4202B8D0ABE00231830 /* search-box-retrieve-recursion.json in Resources */, 046818DB2B87FAB20082B188 /* search-box-category.json in Resources */, @@ -2686,6 +2716,7 @@ F914EE642743E4F400D4F173 /* CoreAliases.swift in Sources */, F998AED825D17DFF00230F34 /* SearchResponseInfo.swift in Sources */, FEEDD3082508DFE400DC0A98 /* HistoryRecord.swift in Sources */, + 04A26C652C7535BD00443527 /* DetailsOptions.swift in Sources */, FEEDD3032508DFE400DC0A98 /* SearchRequestOptions.swift in Sources */, FEEDD2F82508DFE400DC0A98 /* RecordsProviderInteractorNativeCore.swift in Sources */, F97E9A84268C7BBE00F6353D /* DefaultStringInterpolation+Extensions.swift in Sources */, @@ -2823,6 +2854,7 @@ FE489B1B2566D839002BF730 /* SearchNavigationProfileTests.swift in Sources */, F9ACA6152642BEE100F50CD4 /* MockResponse.swift in Sources */, F9ACA6142642BEDD00F50CD4 /* MockWebServer.swift in Sources */, + 042785582C79008300A0934D /* SearchEngineTests+Details.swift in Sources */, FE8F8215256D46F400A100D4 /* SearchResultMetadata+Samples.swift in Sources */, FECB50E3255410BC0026302E /* CLLocationCoordinate2D+Samples.swift in Sources */, 14173C3A28784ABC00B20E1C /* NonEmptyArray+Tests.swift in Sources */, diff --git a/Sources/Demo/ResultDetailViewController.swift b/Sources/Demo/ResultDetailViewController.swift index 5381bdfcc..119ada7b9 100644 --- a/Sources/Demo/ResultDetailViewController.swift +++ b/Sources/Demo/ResultDetailViewController.swift @@ -37,7 +37,8 @@ class ResultDetailViewController: UIViewController { feedbackButton.addTarget(self, action: #selector(showFeedbackAlert), for: .touchUpInside) } - @objc func showFeedbackAlert() { + @objc + func showFeedbackAlert() { let alert = UIAlertController( title: "Submit Feedback?", message: nil, diff --git a/Sources/Demo/UIComponents/PointAnnotation+Search.swift b/Sources/Demo/UIComponents/PointAnnotation+Search.swift index 06f0eb03b..7552b3556 100644 --- a/Sources/Demo/UIComponents/PointAnnotation+Search.swift +++ b/Sources/Demo/UIComponents/PointAnnotation+Search.swift @@ -4,11 +4,11 @@ import UIKit extension PointAnnotation { static func pointAnnotation(_ searchResult: SearchResult) -> Self { - Self.pointAnnotation(coordinate: searchResult.coordinate, name: searchResult.name) + pointAnnotation(coordinate: searchResult.coordinate, name: searchResult.name) } static func pointAnnotation(_ searchResult: AddressAutofill.Result) -> Self { - Self.pointAnnotation(coordinate: searchResult.coordinate, name: searchResult.name) + pointAnnotation(coordinate: searchResult.coordinate, name: searchResult.name) } static func pointAnnotation(_ searchResult: PlaceAutocomplete.Result) -> Self? { diff --git a/Sources/MapboxSearch/InternalAPI/CoreAliases.swift b/Sources/MapboxSearch/InternalAPI/CoreAliases.swift index 17f6ce700..4f8f3ff8f 100644 --- a/Sources/MapboxSearch/InternalAPI/CoreAliases.swift +++ b/Sources/MapboxSearch/InternalAPI/CoreAliases.swift @@ -11,6 +11,7 @@ typealias CoreBoundingBox = MapboxCoreSearch.LonLatBBox typealias CoreSearchResult = MapboxCoreSearch.SearchResult typealias CoreAttributeSet = MapboxCoreSearch.AttributeSet typealias CoreRetrieveOptions = MapboxCoreSearch.RetrieveOptions +typealias CoreDetailsOptions = MapboxCoreSearch.DetailsOptions typealias CoreRoutablePoint = MapboxCoreSearch.RoutablePoint typealias CoreResultMetadata = MapboxCoreSearch.ResultMetadata typealias CoreRequestOptions = MapboxCoreSearch.RequestOptions diff --git a/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift b/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift index 742faf109..fc4e0cab2 100644 --- a/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift +++ b/Sources/MapboxSearch/InternalAPI/CoreSearchEngineProtocol.swift @@ -1,6 +1,8 @@ import CoreLocation import Foundation +typealias CoreSearchResponseCompletion = (CoreSearchResponseProtocol?) -> Void + protocol CoreSearchEngineProtocol { /** ------------------------------------------------------------------------------------------- @@ -79,12 +81,11 @@ protocol CoreSearchEngineProtocol { completion: @escaping (CoreSearchResponseProtocol?) -> Void ) - @discardableResult func forward( query: String, options: CoreSearchOptions, completion: @escaping (CoreSearchResponseProtocol?) -> Void - ) -> UInt64 + ) func setTileStore(_ tileStore: MapboxCommon.TileStore, completion: (() -> Void)?) @@ -95,6 +96,12 @@ protocol CoreSearchEngineProtocol { func addOfflineIndexObserver(for observer: CoreOfflineIndexObserver) func removeOfflineIndexObserver(for observer: CoreOfflineIndexObserver) + + func retrieveDetails( + for mapboxId: String, + options: CoreDetailsOptions, + completion: @escaping CoreSearchResponseCompletion + ) } extension CoreSearchEngine: CoreSearchEngineProtocol { @@ -273,12 +280,23 @@ extension CoreSearchEngine: CoreSearchEngineProtocol { }) } - @discardableResult + func retrieveDetails( + for mapboxId: String, + options: CoreDetailsOptions, + completion: @escaping CoreSearchResponseCompletion + ) { + retrieveDetails(forMapboxId: mapboxId, options: options) { response in + DispatchQueue.main.async { + completion(response) + } + } + } + func forward( query: String, options: CoreSearchOptions, completion: @escaping (CoreSearchResponseProtocol?) -> Void - ) -> UInt64 { + ) { forward(forQuery: query, options: options, callback: { response in DispatchQueue.main.async { completion(response) diff --git a/Sources/MapboxSearch/InternalAPI/Telemetry/EventsServiceProtocol.swift b/Sources/MapboxSearch/InternalAPI/Telemetry/EventsServiceProtocol.swift index 2e5669496..98925d0f0 100644 --- a/Sources/MapboxSearch/InternalAPI/Telemetry/EventsServiceProtocol.swift +++ b/Sources/MapboxSearch/InternalAPI/Telemetry/EventsServiceProtocol.swift @@ -5,4 +5,4 @@ protocol EventsServiceProtocol { func sendEvent(for event: Event, callback: EventsServiceResponseCallback?) } -extension EventsService: EventsServiceProtocol { } +extension EventsService: EventsServiceProtocol {} diff --git a/Sources/MapboxSearch/PublicAPI/DefaultLocationProvider.swift b/Sources/MapboxSearch/PublicAPI/DefaultLocationProvider.swift index 5abc1a0c1..d4a60ba79 100644 --- a/Sources/MapboxSearch/PublicAPI/DefaultLocationProvider.swift +++ b/Sources/MapboxSearch/PublicAPI/DefaultLocationProvider.swift @@ -109,7 +109,7 @@ extension DefaultLocationProvider: LocationProvider { } private class DefaultLocationManagerDelegate: NSObject, CLLocationManagerDelegate { - unowned var locationProvider: DefaultLocationProvider + unowned(unsafe) var locationProvider: DefaultLocationProvider init(unownedLocationProvider: DefaultLocationProvider) { self.locationProvider = unownedLocationProvider diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift index 46a757d1b..57644b4c6 100644 --- a/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchEngine.swift @@ -209,6 +209,8 @@ public class SearchEngine: AbstractSearchEngine { offlineMode == .disabled ? engine.reverseGeocoding : engine.reverseGeocodingOffline } + /// Concrete implementation for a typical ``select(suggestions:)`` invocation. Performs the work of a Mapbox API + /// retrieve/ endpoint request. func retrieve( suggestion: SearchSuggestion, retrieveOptions: RetrieveOptions? @@ -336,6 +338,37 @@ public class SearchEngine: AbstractSearchEngine { } } + /// Process a single response from ``retrieve(mapboxID:options:)`` + /// - Parameters: + /// - coreResponse: The API response + /// - mapboxID: The ID used to create the original request. + private func processDetailsResponse(_ coreResponse: CoreSearchResponseProtocol?, mapboxID: String) { + assert(offlineMode == .disabled) + guard let response = preProcessResponse(coreResponse) else { + return + } + + switch response.process() { + case .success(let responseResult): + // Response Info is not supported for retrieving by Details API at this time. + responseInfo = nil + + guard let result = responseResult.results.first else { + let errorMessage = "Could not retrieve details result." + delegate?.searchErrorHappened( + searchError: .internalSearchRequestError(message: errorMessage), + searchEngine: self + ) + return + } + delegate?.resultResolved(result: result, searchEngine: self) + + case .failure(let searchError): + eventsManager.reportError(searchError) + delegate?.searchErrorHappened(searchError: searchError, searchEngine: self) + } + } + private func resolveServerRetrieveResponse(_ coreResponse: CoreSearchResponseProtocol?) -> SearchResult? { guard let response = preProcessRetrieveResponse(coreResponse) else { return nil @@ -576,6 +609,27 @@ extension SearchEngine { } } +// MARK: - Public Details API + +extension SearchEngine { + /// Retrieve a POI by its Mapbox ID. + /// When you already have a Mapbox ID, you can retrieve the associated POI data directly rather than using + /// ``search(query:options:)`` and ``select(suggestions:)`` + /// For example, to refresh the data for a _cached POI_ , simply query ``retrieve(mapboxID:options:)``, instead of + /// using new `search` and `select` invocations. + /// - Parameters: + /// - mapboxID: The Mapbox ID for a known POI. Mapbox IDs will be returned in Search responses and may be cached. + /// - detailsOptions: Options to configure this query. May be nil. + public func retrieve(mapboxID: String, options detailsOptions: DetailsOptions = DetailsOptions()) { + assert(offlineMode == .disabled) + + let detailsOptions = detailsOptions + engine.retrieveDetails(for: mapboxID, options: detailsOptions.toCore()) { [weak self] serverResponse in + self?.processDetailsResponse(serverResponse, mapboxID: mapboxID) + } + } +} + // MARK: - IndexableDataResolver extension SearchEngine: IndexableDataResolver { diff --git a/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift new file mode 100644 index 000000000..93d60ab75 --- /dev/null +++ b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/DetailsOptions.swift @@ -0,0 +1,36 @@ +// Copyright © 2024 Mapbox. All rights reserved. + +import Foundation + +/// Options to configure a request for ``SearchEngine/retrieve(mapboxID:options:)`` +public struct DetailsOptions: Sendable { + /// Besides the basic metadata attributes, developers can request additional + /// metadata with the ``AttributeSet`` parameter for example &attribute_sets=basic,photos,visit. + /// The requested attributes will be provided in metadata object in the response. + public var attributeSets: [AttributeSet] + + /// The ISO language code to be returned. If not provided, the default is English. + public var language: String? + + /// The two digit ISO country code (such as 'JP') to request a worldview for the location data, if applicable data + /// is available. + /// This parameters will only be applicable for Boundaries and Places feature types. + public var worldview: String? + + public init(attributeSets: [AttributeSet]? = nil, language: String? = nil, worldview: String? = nil) { + // Always make sure that the basic attribute set is present + var attributeSets: Set = Set(attributeSets ?? [.basic]) + attributeSets.insert(.basic) + self.attributeSets = Array(attributeSets) + self.language = language + self.worldview = worldview + } + + func toCore() -> CoreDetailsOptions { + CoreDetailsOptions( + attributeSets: attributeSets.map { NSNumber(value: $0.coreValue.rawValue) }, + language: language, + worldview: worldview + ) + } +} diff --git a/Sources/MapboxSearch/PublicAPI/Engine/RetrieveOptions.swift b/Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/RetrieveOptions.swift similarity index 100% rename from Sources/MapboxSearch/PublicAPI/Engine/RetrieveOptions.swift rename to Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/RetrieveOptions.swift diff --git a/Sources/MapboxSearch/PublicAPI/Search Results/SearchResultSuggestionImpl.swift b/Sources/MapboxSearch/PublicAPI/Search Results/SearchResultSuggestionImpl.swift index e8b6a7e02..1bc94b42d 100644 --- a/Sources/MapboxSearch/PublicAPI/Search Results/SearchResultSuggestionImpl.swift +++ b/Sources/MapboxSearch/PublicAPI/Search Results/SearchResultSuggestionImpl.swift @@ -53,7 +53,7 @@ class SearchResultSuggestionImpl: SearchResultSuggestion, CoreResponseProvider { self.id = coreResult.id self.mapboxId = coreResult.mapboxId - self.name = coreResult.names[0] + self.name = coreResult.names.first ?? "" self.address = coreResult.addresses?.first.map(Address.init) self.iconName = coreResult.icon self.serverIndex = coreResult.serverIndex?.intValue diff --git a/Sources/MapboxSearch/PublicAPI/Search Results/ServerSearchResult.swift b/Sources/MapboxSearch/PublicAPI/Search Results/ServerSearchResult.swift index 22bacdc19..be04cfb29 100644 --- a/Sources/MapboxSearch/PublicAPI/Search Results/ServerSearchResult.swift +++ b/Sources/MapboxSearch/PublicAPI/Search Results/ServerSearchResult.swift @@ -68,7 +68,7 @@ class ServerSearchResult: SearchResult, SearchResultSuggestion, CoreResponseProv self.id = coreResult.id self.mapboxId = coreResult.mapboxId - self.name = coreResult.names[0] + self.name = coreResult.names.first ?? "" self.matchingName = coreResult.matchingName self.iconName = coreResult.icon self.estimatedTime = coreResult.estimatedTime @@ -92,6 +92,5 @@ class ServerSearchResult: SearchResult, SearchResultSuggestion, CoreResponseProv assert(!id.isEmpty) assert(!name.isEmpty) - assert(address != nil) } } diff --git a/Sources/MapboxSearch/PublicAPI/SearchOptions.swift b/Sources/MapboxSearch/PublicAPI/SearchOptions.swift index 74b3499b1..afc126b2d 100644 --- a/Sources/MapboxSearch/PublicAPI/SearchOptions.swift +++ b/Sources/MapboxSearch/PublicAPI/SearchOptions.swift @@ -37,7 +37,7 @@ public struct SearchOptions { /// Limit results to only those contained within the supplied bounding box. /// The bounding box cannot cross the 180th meridian. public var boundingBox: BoundingBox? - + /// In case ``SearchOptions/boundingBox`` was applied, places search will look though all available tiles, /// ignoring the bounding box. Other search types (Address, POI, Category) will no be affected by this setting. /// In case ``SearchOptions/boundingBox`` was not applied - this param will not be used. @@ -266,7 +266,8 @@ public struct SearchOptions { timeDeviation: timeDeviation, addonAPI: unsafeParameters, offlineSearchPlacesOutsideBbox: offlineSearchPlacesOutsideBbox, - ensureResultsPerCategory: nil, // TODO: Support multiple categories search and ability to ensure results per category. + ensureResultsPerCategory: nil, + // TODO: Support multiple categories search and ability to ensure results per category. attributeSets: nil ) } diff --git a/Sources/MapboxSearch/PublicAPI/ServiceProvider.swift b/Sources/MapboxSearch/PublicAPI/ServiceProvider.swift index aa5d9f93a..0f2d70f54 100644 --- a/Sources/MapboxSearch/PublicAPI/ServiceProvider.swift +++ b/Sources/MapboxSearch/PublicAPI/ServiceProvider.swift @@ -41,8 +41,9 @@ public class ServiceProvider: ServiceProviderProtocol { /// Customize API host URL with a value from the Info.plist /// Also supports reading a process argument when in non-Release UITest builds /// Read-only property. - /// To change the customBaseURL for an engine programmatically, use the - /// `ServiceProvider.createEngine(apiType:accessToken:locationProvider:customBaseURL:)` function. + /// To change the customBaseURL for an engine programmatically, you would typically use the + /// ``AbstractSearchEngine/init(accessToken:locationProvider:defaultSearchOptions:apiType:baseURL:)`` function on a + /// SearchEngine subclass type. public static var customBaseURL: String? { #if !RELEASE if ProcessInfo.processInfo.arguments.contains(where: { $0 == "--uitesting" }) { diff --git a/Sources/MapboxSearch/PublicAPI/Telemetry/EventsManager.swift b/Sources/MapboxSearch/PublicAPI/Telemetry/EventsManager.swift index e162f8735..fa1fccf80 100644 --- a/Sources/MapboxSearch/PublicAPI/Telemetry/EventsManager.swift +++ b/Sources/MapboxSearch/PublicAPI/Telemetry/EventsManager.swift @@ -43,12 +43,15 @@ public class EventsManager: NSObject { // This unhandled parameter must be removed to match the event scheme. commonEventAttributes.removeValue(forKey: "mapboxId") - let commonEvent = Event(priority: autoFlush ? .immediate : .queued, - attributes: commonEventAttributes, - deferredOptions: nil) + let commonEvent = Event( + priority: autoFlush ? .immediate : .queued, + attributes: commonEventAttributes, + deferredOptions: nil + ) eventsService.sendEvent(for: commonEvent) { expected in if expected.isError() { - _Logger.searchSDK.error("Failed to send the event \(event.rawValue) due to error: \(expected.error.message)") + _Logger.searchSDK + .error("Failed to send the event \(event.rawValue) due to error: \(expected.error.message)") } else if expected.isValue() { _Logger.searchSDK.debug("Sent the event \(event.rawValue)") } diff --git a/Tests/Demo.xctestplan b/Tests/Demo.xctestplan index fb3762472..7dcacae8b 100644 --- a/Tests/Demo.xctestplan +++ b/Tests/Demo.xctestplan @@ -44,9 +44,6 @@ } }, { - "skippedTests" : [ - "SearchEngineTests\/testRetrieveDetailsFunction()" - ], "target" : { "containerPath" : "container:MapboxSearch.xcodeproj", "identifier" : "3A0D7E54233522D4006D81BB", diff --git a/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift b/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift index 4c9220524..cc437d1a5 100644 --- a/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift +++ b/Tests/MapboxSearchIntegrationTests/search-box/SearchBox_SearchEngineIntegrationTests.swift @@ -158,4 +158,72 @@ class SearchBox_SearchEngineIntegrationTests: MockServerIntegrationTestCase (URL, CGSize)? in + guard let urlString = data["url"] as? String, + let url = URL(string: urlString), + let width = data["width"] as? Int, + let height = data["height"] as? Int + else { + return nil + } + return (url, CGSize(width: width, height: height)) + } + .map(Image.SizedImage.init) + let comparisonOtherImages = [Image(sizes: otherImages)] + XCTAssertEqual(metadata.otherImages?.count, comparisonOtherImages.count) + XCTAssertEqual(metadata.otherImages, comparisonOtherImages) + + XCTAssertNotNil(metadata.phone) + XCTAssertNotNil(metadata.averageRating) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.facebookId) + XCTAssertNotNil(metadata.twitter) + } } diff --git a/Tests/MapboxSearchTests/Common/Extensions/LotLan+Extensions.swift b/Tests/MapboxSearchTests/Common/Extensions/LotLan+Extensions.swift index ef199bda7..c25f613a6 100644 --- a/Tests/MapboxSearchTests/Common/Extensions/LotLan+Extensions.swift +++ b/Tests/MapboxSearchTests/Common/Extensions/LotLan+Extensions.swift @@ -9,4 +9,4 @@ extension CoreBoundingBox { return false } } -} +} diff --git a/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift b/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift index 6a9c251b8..3abdc5da5 100644 --- a/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift +++ b/Tests/MapboxSearchTests/Common/Stubs&Models/CoreSearchEngineStub.swift @@ -34,6 +34,12 @@ class CoreSearchEngineStub { } extension CoreSearchEngineStub: CoreSearchEngineProtocol { + func retrieveDetails( + for mapboxId: String, + options: MapboxSearch.CoreDetailsOptions, + completion: @escaping MapboxSearch.CoreSearchResponseCompletion + ) {} + func addOfflineIndexObserver(for observer: MapboxSearch.CoreOfflineIndexObserver) {} func removeOfflineIndexObserver(for observer: CoreOfflineIndexObserver) {} @@ -199,13 +205,9 @@ extension CoreSearchEngineStub: CoreSearchEngineProtocol { reverseGeocoding(for: options, completion: completion) } - @discardableResult func forward( query: String, options: CoreSearchOptions, completion: @escaping (CoreSearchResponseProtocol?) -> Void - ) -> UInt64 { - // TODO: - return UInt64.max - } + ) {} } diff --git a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift index bdf1716b8..523362bd9 100644 --- a/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift +++ b/Tests/MapboxSearchTests/Legacy/SearchEngineTests.swift @@ -532,7 +532,7 @@ class SearchEngineTests: XCTestCase { let metadata = try XCTUnwrap(result.metadata) switch attribute { case .basic: - XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.primaryImage) XCTAssertNil(metadata.otherImages) XCTAssertNil(metadata.phone) XCTAssertNil(metadata.website) @@ -548,15 +548,15 @@ class SearchEngineTests: XCTestCase { XCTAssertNil(metadata.averageRating) XCTAssertNil(metadata.openHours) case .venue: - XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.primaryImage) XCTAssertNil(metadata.otherImages) XCTAssertNil(metadata.phone) XCTAssertNil(metadata.website) XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") - XCTAssertNil(metadata.averageRating) + XCTAssertEqual(5.0, metadata.averageRating) XCTAssertNil(metadata.openHours) case .visit: - XCTAssertNotNil(metadata.primaryImage) + XCTAssertNil(metadata.primaryImage) XCTAssertNil(metadata.otherImages) XCTAssertNotNil(metadata.phone) XCTAssertNotNil(metadata.website) diff --git a/Tests/MapboxSearchTests/Telemetry/EventsManagerTests.swift b/Tests/MapboxSearchTests/Telemetry/EventsManagerTests.swift index 6ef432196..d1af1eda7 100644 --- a/Tests/MapboxSearchTests/Telemetry/EventsManagerTests.swift +++ b/Tests/MapboxSearchTests/Telemetry/EventsManagerTests.swift @@ -1,5 +1,5 @@ -@testable import MapboxSearch import MapboxCommon_Private +@testable import MapboxSearch import XCTest final class EventsServiceMock: EventsServiceProtocol { @@ -44,6 +44,5 @@ final class EventsManagerTests: XCTestCase { eventsManager.sendEvent(.feedback, attributes: attributes, autoFlush: false) let sentEvent2 = eventsService.events[1] XCTAssertEqual(sentEvent2.priority, .queued) - } } diff --git a/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift new file mode 100644 index 000000000..a2cd72e5b --- /dev/null +++ b/Tests/MapboxSearchTests/Use Cases/Details/SearchEngineTests+Details.swift @@ -0,0 +1,269 @@ +import CoreLocation +import CwlPreconditionTesting +@testable import MapboxSearch +import XCTest + +extension SearchEngineTests { + // MARK: - Querying each AttributeSet individually + + /// NOTE: Although this test uses separate fetches for each attribute set this is purely for testing coverage + /// purposes. It is recommended to request as many attribute sets as desired in one RequestOptions array. + /// Ex: You should use RetrieveOptions(attributeSets: [.visit, .photos]) in one select() call rather than two calls. + func testRetrieveDetailsByEachAttributeSet() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + + func retrieve(for mapboxID: String, options: DetailsOptions) throws -> SearchResult { + let successExpectation = delegate.successExpectation + searchEngine.retrieve(mapboxID: mapboxID, options: options) + wait(for: [successExpectation], timeout: 200) + return try XCTUnwrap(delegate.resolvedResult) + } + + let dolcezzaGelatoMapboxID = "dXJuOm1ieHBvaTo5MjQ2ZWMyYy04YTMwLTQ5YjUtODUxOS0zYWNhMjZkYjM2ZGY" + let resultsByAttribute = try AttributeSet.allCases.map { attributeSet in + let result = try retrieve( + for: dolcezzaGelatoMapboxID, + options: DetailsOptions(attributeSets: [attributeSet]) + ) + + XCTAssertNotNil( + result.metadata, + "\(attributeSet) metadata should not be nil for MapboxID \(String(describing: result.mapboxId))" + ) + + return (attributeSet, result) + } + + XCTAssertNotNil(resultsByAttribute) + for (attribute, result) in resultsByAttribute { + let metadata = try XCTUnwrap(result.metadata) + switch attribute { + case .basic: + XCTAssertNil(metadata.primaryImage) + XCTAssertNil(metadata.otherImages) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + XCTAssertNil(metadata.reviewCount) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.openHours) + case .photos: + XCTAssertNil(metadata.primaryImage) // TODO: SSDK-1055 + XCTAssertNil(metadata.otherImages) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + XCTAssertNil(metadata.reviewCount) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.openHours) + case .venue: + XCTAssertTrue(metadata.delivery ?? false) + XCTAssertNotNil(metadata.popularity) + XCTAssertNil(metadata.priceLevel) + XCTAssertNil(metadata.reservable) + XCTAssertNil(metadata.servesBrunch) + case .visit: + XCTAssertNil(metadata.instagram) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNil(metadata.twitter) + XCTAssertNotNil(metadata.website) + } + } + + XCTAssertNil(delegate.error) + } + + func testSelectAndSuggestDetailsByEachAttributeSet() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let updateExpectation = delegate.updateExpectation + + let searchOptions = SearchOptions( + limit: 100, + origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), + filterTypes: [.poi] + ) + + searchEngine.search(query: "dolcezza gelato", options: searchOptions) + wait(for: [updateExpectation], timeout: 200) + let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) + + func select(for suggestion: SearchSuggestion, options: RetrieveOptions) throws -> SearchResult { + let successExpectation = delegate.successExpectation + searchEngine.select(suggestion: suggestion, options: options) + wait(for: [successExpectation], timeout: 200) + return try XCTUnwrap(delegate.resolvedResult) + } + + let resultsByAttribute = try AttributeSet.allCases.map { attributeSet in + let result = try select( + for: suggestion, + options: RetrieveOptions(attributeSets: [attributeSet]) + ) + + XCTAssertNotNil(result.metadata, "\(attributeSet) metadata should not be nil") + + return (attributeSet, result) + } + + XCTAssertNotNil(resultsByAttribute) + for (attribute, result) in resultsByAttribute { + let metadata = try XCTUnwrap(result.metadata) + switch attribute { + case .basic: + XCTAssertNil(metadata.primaryImage) + XCTAssertNil(metadata.otherImages) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + XCTAssertNil(metadata.reviewCount) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.openHours) + case .photos: + XCTAssertNotNil(metadata.primaryImage) // TODO: SSDK-1055 + XCTAssertNotNil(metadata.otherImages) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + XCTAssertNil(metadata.reviewCount) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.openHours) + case .venue: + XCTAssertTrue(metadata.delivery ?? false) + XCTAssertNil(metadata.parkingAvailable) + XCTAssertNotNil(metadata.popularity) + XCTAssertNotNil(metadata.priceLevel) + XCTAssertNil(metadata.reservable) + XCTAssertNil(metadata.servesBrunch) + XCTAssertNil(metadata.servesVegetarian) + XCTAssertNil(metadata.streetParking) + case .visit: + XCTAssertNil(metadata.instagram) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNil(metadata.twitter) + XCTAssertNotNil(metadata.website) + } + } + + XCTAssertNil(delegate.error) + } + + // MARK: - Using AttributeSet.allCases + + func testSuggestAndSelectWithAllAttributeSets() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let updateExpectation = delegate.updateExpectation + + let searchOptions = SearchOptions( + limit: 100, + origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), + filterTypes: [.poi] + ) + + searchEngine.search(query: "planet word", options: searchOptions) + wait(for: [updateExpectation], timeout: 200) + let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) + + let successExpectation = delegate.successExpectation + searchEngine.select(suggestion: suggestion, options: RetrieveOptions(attributeSets: AttributeSet.allCases)) + wait(for: [successExpectation], timeout: 200) + + let result = try XCTUnwrap(delegate.resolvedResult) + let metadata = try XCTUnwrap(result.metadata) + XCTAssertNotNil(metadata.averageRating) + XCTAssertNotNil(metadata.otherImages) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.website) + XCTAssertNil(delegate.error) + } + + /// retrieve(mapboxID:) with all attribute set cases + func testRetrieveDetailsByMapboxIdWithAttributeSetAllCases() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let successExpectation = delegate.successExpectation + + let planetWordMapboxID = "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk" + let detailsOptions = DetailsOptions(attributeSets: AttributeSet.allCases, language: "en") + searchEngine.retrieve(mapboxID: planetWordMapboxID, options: detailsOptions) + wait(for: [successExpectation], timeout: 200) + XCTAssertNil(delegate.resolvedSuggestions) + let result = try XCTUnwrap(delegate.resolvedResult) + let metadata = try XCTUnwrap(result.metadata) + XCTAssertNotNil(metadata.averageRating) + XCTAssertNotNil(metadata.otherImages) + XCTAssertNotNil(metadata.openHours) + XCTAssertNotNil(metadata.phone) + XCTAssertNotNil(metadata.primaryImage) + XCTAssertNotNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNotNil(metadata.website) + XCTAssertNil(delegate.error) + } + + // MARK: - Absence of attribute_sets options + + func testSuggestAndSelectWithEmptyOptions() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let updateExpectation = delegate.updateExpectation + + let searchOptions = SearchOptions( + limit: 100, + origin: CLLocationCoordinate2D(latitude: 38.902309, longitude: -77.029129), + filterTypes: [.poi] + ) + + searchEngine.search(query: "planet word", options: searchOptions) + wait(for: [updateExpectation], timeout: 200) + let suggestion = try XCTUnwrap(delegate.resolvedSuggestions?.first) + + let successExpectation = delegate.successExpectation + searchEngine.select(suggestion: suggestion) + wait(for: [successExpectation], timeout: 200) + + let result = try XCTUnwrap(delegate.resolvedResult) + let metadata = try XCTUnwrap(result.metadata) + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.otherImages) + XCTAssertNil(metadata.openHours) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.primaryImage) + XCTAssertNil(metadata.reviewCount, "Review count failed for \(String(describing: result.mapboxId))") + XCTAssertNil(metadata.website) + XCTAssertNil(delegate.error) + } + + /// Empty AttributeSets options is equivalent to using the attribute\_sets=basic + func testRetrieveDetailsByMapboxIDWithEmptyOptions() throws { + let searchEngine = SearchEngine(apiType: .searchBox) + let delegate = SearchEngineDelegateStub() + searchEngine.delegate = delegate + let successExpectation = delegate.successExpectation + + let planetWordMapboxID = "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk" + searchEngine.retrieve(mapboxID: planetWordMapboxID) + wait(for: [successExpectation], timeout: 200) + XCTAssertNil(delegate.error) + + let result = try XCTUnwrap(delegate.resolvedResult) + let metadata = try XCTUnwrap(result.metadata) + + XCTAssertNil(metadata.averageRating) + XCTAssertNil(metadata.primaryImage) + XCTAssertNil(metadata.reviewCount) + + // In this test no attribute sets were provided and these fields are nil + XCTAssertNil(metadata.otherImages) + XCTAssertNil(metadata.openHours) + XCTAssertNil(metadata.phone) + XCTAssertNil(metadata.website) + } +} diff --git a/Tests/MockData/search-box/search-box-retrieve-mapbox-id.json b/Tests/MockData/search-box/search-box-retrieve-mapbox-id.json new file mode 100644 index 000000000..4738ff340 --- /dev/null +++ b/Tests/MockData/search-box/search-box-retrieve-mapbox-id.json @@ -0,0 +1,246 @@ +{ + "geometry": { + "coordinates": [ + -77.029129, + 38.902309 + ], + "type": "Point" + }, + "properties": { + "address": "1300 I St NW", + "context": { + "address": { + "address_number": "1300", + "id": "", + "name": "1300 I St NW", + "street_name": "i street nw" + }, + "country": { + "country_code": "US", + "country_code_alpha_3": "USA", + "id": "", + "name": "United States" + }, + "neighborhood": { + "id": "dXJuOm1ieHBsYzpDaXpzN0E", + "name": "Downtown" + }, + "place": { + "id": "dXJuOm1ieHBsYzpGSmlvN0E", + "name": "Washington" + }, + "postcode": { + "id": "dXJuOm1ieHBsYzpBNTd1N0E", + "name": "20005" + }, + "region": { + "id": "", + "name": "District of Columbia", + "region_code": "DC", + "region_code_full": "US-DC" + }, + "street": { + "id": "", + "name": "i street nw" + } + }, + "coordinates": { + "latitude": 38.902309, + "longitude": -77.029129, + "routable_points": [ + { + "latitude": 38.90230737131555, + "longitude": -77.0296257487584, + "name": "default" + } + ] + }, + "external_ids": { + "foursquare": "5e4aa71ee6eec6000844fbfc" + }, + "feature_type": "poi", + "full_address": "1300 I St NW, Washington, District of Columbia 20005, United States", + "language": "en", + "maki": "marker", + "mapbox_id": "dXJuOm1ieHBvaTo0ZTg2ZWFkNS1jOWMwLTQ3OWEtOTA5Mi1kMDVlNDQ3NDdlODk", + "metadata": { + "facebook_id": "212540879151543", + "instagram": "planetworddc", + "open_hours": { + "periods": [ + { + "close": { + "day": 0, + "time": "1700" + }, + "open": { + "day": 0, + "time": "1000" + } + }, + { + "close": { + "day": 3, + "time": "1700" + }, + "open": { + "day": 3, + "time": "1000" + } + }, + { + "close": { + "day": 4, + "time": "1700" + }, + "open": { + "day": 4, + "time": "1000" + } + }, + { + "close": { + "day": 5, + "time": "1700" + }, + "open": { + "day": 5, + "time": "1000" + } + }, + { + "close": { + "day": 6, + "time": "1700" + }, + "open": { + "day": 6, + "time": "1000" + } + } + ], + "weekday_text": [ + "Monday: Closed", + "Tuesday: Closed", + "Wednesday: 10:00 AM - 5:00 PM", + "Thursday: 10:00 AM - 5:00 PM", + "Friday: 10:00 AM - 5:00 PM", + "Saturday: 10:00 AM - 5:00 PM", + "Sunday: 10:00 AM - 5:00 PM" + ] + }, + "phone": "+12029313139", + "photos": [ + { + "height": 768, + "url": "https://media-cdn.tripadvisor.com/media/photo-o/2a/08/ad/cd/caption.jpg", + "width": 1024 + }, + { + "height": 1024, + "url": "https://media-cdn.tripadvisor.com/media/photo-o/2a/08/ad/fe/caption.jpg", + "width": 768 + }, + { + "height": 975, + "url": "https://media-cdn.tripadvisor.com/media/photo-o/28/b6/2d/cc/caption.jpg", + "width": 2006 + }, + { + "height": 3888, + "url": "https://media-cdn.tripadvisor.com/media/photo-o/2a/45/ec/0a/caption.jpg", + "width": 5184 + }, + { + "height": 975, + "url": "https://media-cdn.tripadvisor.com/media/photo-o/28/b6/2d/ca/caption.jpg", + "width": 2006 + } + ], + "popular_hours": [ + { + "day": 0, + "intervals": [ + { + "end": { + "time": "1700" + }, + "start": { + "time": "1000" + } + } + ] + }, + { + "day": 3, + "intervals": [ + { + "end": { + "time": "1700" + }, + "start": { + "time": "1000" + } + } + ] + }, + { + "day": 4, + "intervals": [ + { + "end": { + "time": "1600" + }, + "start": { + "time": "1000" + } + } + ] + }, + { + "day": 5, + "intervals": [ + { + "end": { + "time": "1600" + }, + "start": { + "time": "1000" + } + } + ] + }, + { + "day": 6, + "intervals": [ + { + "end": { + "time": "1700" + }, + "start": { + "time": "1000" + } + } + ] + } + ], + "popularity": 0.9892701654583336, + "primary_photo": "https://ir.4sqi.net/img/general/original/38340_7KfSyEx1Dx1OZ1FlSH7sIt5t4t8ERFgMvtvRfwOEBLk.jpg", + "rating": 4.13, + "twitter": "planetworddc", + "website": "http://www.planetwordmuseum.org" + }, + "name": "Planet Word", + "operational_status": "active", + "place_formatted": "Washington, District of Columbia 20005, United States", + "poi_category": [ + "museum", + "tourist attraction" + ], + "poi_category_ids": [ + "museum", + "tourist_attraction" + ] + }, + "type": "Feature" +} diff --git a/Tests/MockWebServer/MockResponse.swift b/Tests/MockWebServer/MockResponse.swift index 309ff0505..7b6cfc183 100644 --- a/Tests/MockWebServer/MockResponse.swift +++ b/Tests/MockWebServer/MockResponse.swift @@ -208,6 +208,8 @@ enum SearchBoxMockResponse: MockResponse { case retrieveRecursion case multiRetrieve + case retrieveMapboxID + case recursion // Rename: suggestRecursion case categoryCafe case categoryHotelSearchAlongRoute_JP @@ -239,6 +241,8 @@ enum SearchBoxMockResponse: MockResponse { return bundle.path(forResource: "search-box-retrieve-categories", ofType: "json")! case .retrievePoi: return bundle.path(forResource: "retrieve-poi", ofType: "json")! + case .retrieveMapboxID: + return bundle.path(forResource: "search-box-retrieve-mapbox-id", ofType: "json")! case .recursion: return bundle.path(forResource: "search-box-recursion", ofType: "json")! case .multiRetrieve: @@ -277,6 +281,9 @@ enum SearchBoxMockResponse: MockResponse { .retrieveRecursion: path += "/retrieve/:identifier" + case .retrieveMapboxID: + path = "/search/details/v1/retrieve/:mapboxID" + case .multiRetrieve: path += "/retrieve/multi" @@ -303,7 +310,8 @@ enum SearchBoxMockResponse: MockResponse { .retrieveMinsk, .retrieveCategory, .retrieveSanFrancisco, - .retrieveRecursion: + .retrieveRecursion, + .retrieveMapboxID: return .get case .multiRetrieve,