Skip to content

Commit 5b931c5

Browse files
authored
Add support for the attributes set in category search (#352)
1 parent af0f026 commit 5b931c5

File tree

7 files changed

+65
-17
lines changed

7 files changed

+65
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Guide: https://keepachangelog.com/en/1.0.0/
77
## Unreleased
88

99
- [Details] Add `SearchEngine.retrieve(mapboxID: String, options: DetailsOptions)` function.
10+
- [SearchOptions] Add `SearchOptions.attributeSets` option. It allows to request of additional metadata attributes besides the basic ones in category search requests.
1011

1112
## 2.7.1
1213

Sources/Demo/Examples/SimpleCategorySearchViewController.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@ import MapboxSearch
22
import UIKit
33

44
class SimpleCategorySearchViewController: MapsViewController {
5-
let searchEngine = CategorySearchEngine()
5+
let searchEngine = CategorySearchEngine(apiType: .searchBox)
66
// let searchEngine = CategorySearchEngine(accessToken: "<#You can pass access token manually#>")
77

88
override func viewDidAppear(_ animated: Bool) {
99
super.viewDidAppear(animated)
1010

11-
searchEngine.search(categoryName: "cafe") { response in
11+
let searchOptions = SearchOptions(attributeSets: [.basic, .photos, .venue, .visit])
12+
searchEngine.search(categoryName: "cafe", options: searchOptions) { response in
1213
do {
1314
let results = try response.get()
1415
print("Number of category search results: \(results.count)")
1516

1617
for result in results {
18+
print("\tResult name: \(result.name)")
19+
print("\tResult metadata - phone: \(result.metadata?.phone ?? "null")")
1720
print("\tResult coordinate: \(result.coordinate)")
1821
}
1922

Sources/MapboxSearch/PublicAPI/Engine/SearchRequest/RetrieveOptions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public struct RetrieveOptions: Sendable {
1616
func toCore() -> CoreRetrieveOptions {
1717
CoreRetrieveOptions(
1818
attributeSets:
19-
attributeSets.map { $0.map { NSNumber(value: $0.coreValue.rawValue) } }
19+
attributeSets?.map { NSNumber(value: $0.coreValue.rawValue) }
2020
)
2121
}
2222
}

Sources/MapboxSearch/PublicAPI/SearchOptions.swift

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public struct SearchOptions {
3838
/// The bounding box cannot cross the 180th meridian.
3939
public var boundingBox: BoundingBox?
4040

41-
/// In case ``SearchOptions/boundingBox`` was applied, places search will look though all available tiles,
41+
/// In case ``SearchOptions/boundingBox`` was applied, places search will look through all available tiles,
4242
/// ignoring the bounding box. Other search types (Address, POI, Category) will no be affected by this setting.
4343
/// In case ``SearchOptions/boundingBox`` was not applied - this param will not be used.
4444
public var offlineSearchPlacesOutsideBbox: Bool
@@ -85,6 +85,10 @@ public struct SearchOptions {
8585
*/
8686
public var locale: Locale?
8787

88+
/// Configures additional metadata attributes besides the basic ones. This property is used only in category search.
89+
/// Supported in ``ApiType/searchBox`` only.
90+
public var attributeSets: [AttributeSet]?
91+
8892
/// Search request options constructor
8993
/// - Parameter countries: Limit results to one or more countries. Permitted values are ISO 3166 alpha 2 country
9094
/// codes (e.g. US, DE, GB)
@@ -94,6 +98,7 @@ public struct SearchOptions {
9498
/// - Parameter fuzzyMatch: Use non-strict (`true`) or strict (`false`) matching
9599
/// - Parameter proximity: Coordinate to search around
96100
/// - Parameter boundingBox: Limit search result to a region
101+
/// - Parameter offlineSearchPlacesOutsideBbox: Configures if places can be looked through all available files ignoring the bounding box
97102
/// - Parameter origin: Search origin point. This point is used for calculation of SearchResult ETA and distance
98103
/// fields
99104
/// - Parameter navigationOptions: Navigation options used for proper calculation of ETA and results ranking
@@ -102,6 +107,8 @@ public struct SearchOptions {
102107
/// - Parameter ignoreIndexableRecords: Do not search external records in `IndexableDataProvider`s
103108
/// - Parameter indexableRecordsDistanceThreshold: Radius of circle around `proximity` to filter indexable records
104109
/// - Parameter unsafeParameters: Non-verified query parameters to the server API
110+
/// - Parameter attributeSets: Configures additional metadata attributes besides the basic ones. If `attributeSets`
111+
/// is `nil` or empty, ``AttributeSet/basic`` will be requested.
105112
public init(
106113
countries: [String]? = nil,
107114
languages: [String]? = nil,
@@ -116,7 +123,8 @@ public struct SearchOptions {
116123
filterTypes: [SearchQueryType]? = nil,
117124
ignoreIndexableRecords: Bool = false,
118125
indexableRecordsDistanceThreshold: CLLocationDistance? = nil,
119-
unsafeParameters: [String: String]? = nil
126+
unsafeParameters: [String: String]? = nil,
127+
attributeSets: [AttributeSet]? = nil
120128
) {
121129
self.countries = countries
122130
self.languages = languages ?? Locale.defaultLanguages()
@@ -132,6 +140,7 @@ public struct SearchOptions {
132140
self.ignoreIndexableRecords = ignoreIndexableRecords
133141
self.indexableRecordsDistanceThreshold = indexableRecordsDistanceThreshold
134142
self.unsafeParameters = unsafeParameters
143+
self.attributeSets = attributeSets
135144
}
136145

137146
/// Search request options with custom proximity.
@@ -199,9 +208,11 @@ public struct SearchOptions {
199208
latitude: $0.value.latitude,
200209
longitude: $0.value.longitude
201210
) }
202-
let filterTypes: [SearchQueryType]? = options.types?
203-
.compactMap { CoreQueryType(rawValue: $0.intValue) }
204-
.compactMap { SearchQueryType.fromCoreValue($0) }
211+
212+
let filterTypes: [SearchQueryType]? = options.types?.compactMap {
213+
CoreQueryType(rawValue: $0.intValue)
214+
.flatMap { SearchQueryType.fromCoreValue($0) }
215+
}
205216

206217
var routeOptions: RouteOptions?
207218
let coordinates = options.route?.map(\.value)
@@ -218,6 +229,11 @@ public struct SearchOptions {
218229
etaType: etaType
219230
) }
220231

232+
let attributeSets: [AttributeSet]? = options.attributeSets?.compactMap {
233+
CoreAttributeSet(rawValue: $0.intValue)
234+
.flatMap { AttributeSet.fromCoreValue($0) }
235+
}
236+
221237
self.init(
222238
countries: options.countries,
223239
languages: options.language,
@@ -231,7 +247,8 @@ public struct SearchOptions {
231247
filterTypes: filterTypes,
232248
ignoreIndexableRecords: options.ignoreUR,
233249
indexableRecordsDistanceThreshold: options.urDistanceThreshold?.doubleValue,
234-
unsafeParameters: options.addonAPI
250+
unsafeParameters: options.addonAPI,
251+
attributeSets: attributeSets
235252
)
236253
}
237254

@@ -267,8 +284,8 @@ public struct SearchOptions {
267284
addonAPI: unsafeParameters,
268285
offlineSearchPlacesOutsideBbox: offlineSearchPlacesOutsideBbox,
269286
ensureResultsPerCategory: nil,
270-
// TODO: Support multiple categories search and ability to ensure results per category.
271-
attributeSets: nil
287+
// TODO: NAVIOS-2054 Support multiple categories search and ability to ensure results per category.
288+
attributeSets: attributeSets?.map { NSNumber(value: $0.coreValue.rawValue) }
272289
)
273290
}
274291

@@ -412,7 +429,8 @@ public struct SearchOptions {
412429
ignoreIndexableRecords: ignoreIndexableRecords,
413430
indexableRecordsDistanceThreshold: indexableRecordsDistanceThreshold ??
414431
with.indexableRecordsDistanceThreshold,
415-
unsafeParameters: unsafeParameters ?? with.unsafeParameters
432+
unsafeParameters: unsafeParameters ?? with.unsafeParameters,
433+
attributeSets: attributeSets ?? with.attributeSets
416434
)
417435
}
418436
}

Tests/MapboxSearchIntegrationTests/search-box/SearchBox_CategorySearchEngineIntegrationTests.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import XCTest
44

55
final class SearchBox_CategorySearchEngineIntegrationTests: MockServerIntegrationTestCase<SearchBoxMockResponse> {
66
private var searchEngine: CategorySearchEngine!
7+
private var searchOptions: SearchOptions!
78

89
override func setUpWithError() throws {
910
try super.setUpWithError()
@@ -15,18 +16,25 @@ final class SearchBox_CategorySearchEngineIntegrationTests: MockServerIntegratio
1516
apiType: apiType,
1617
baseURL: mockServerURL()
1718
)
19+
searchOptions = SearchOptions(attributeSets: [.basic, .photos, .venue, .visit])
1820
}
1921

2022
func testCategorySearch() throws {
2123
try server.setResponse(.categoryCafe)
2224

2325
let expectation = XCTestExpectation(description: "Expecting results")
24-
searchEngine.search(categoryName: "cafe") { result in
26+
searchEngine.search(categoryName: "cafe", options: searchOptions) { [weak self] result in
27+
guard let self else { return }
2528
switch result {
2629
case .success(let searchResults):
2730
XCTAssertFalse(searchResults.isEmpty)
2831
let matchingNames = searchResults.compactMap(\.matchingName)
2932
XCTAssertTrue(matchingNames.isEmpty)
33+
let queryParams = self.server.passedRequests.last!.queryParams
34+
let attributeSetsParam = queryParams.first {
35+
$0.0 == "attribute_sets"
36+
}?.1
37+
XCTAssertEqual(attributeSetsParam, "basic,photos,venue,visit")
3038
expectation.fulfill()
3139
case .failure:
3240
XCTFail("Error not expected")
@@ -40,7 +48,7 @@ final class SearchBox_CategorySearchEngineIntegrationTests: MockServerIntegratio
4048
try server.setResponse(.categoryCafe, statusCode: 500)
4149

4250
let expectation = XCTestExpectation(description: "Expecting failure")
43-
searchEngine.search(categoryName: "cafe") { result in
51+
searchEngine.search(categoryName: "cafe", options: searchOptions) { result in
4452
switch result {
4553
case .success:
4654
XCTFail("Not expected")

Tests/MapboxSearchTests/Legacy/SearchOptionsTests.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class SearchOptionsTests: XCTestCase {
2121
XCTAssertEqual(searchOptions.unsafeParameters, ["arg": "value"])
2222
XCTAssertFalse(searchOptions.ignoreIndexableRecords)
2323
XCTAssertEqual(searchOptions.indexableRecordsDistanceThreshold, 2_000)
24+
XCTAssertEqual(searchOptions.attributeSets, [.basic, .photos])
2425
}
2526

2627
func testSearchOptionBoundingBoxConstructor() {
@@ -40,6 +41,7 @@ class SearchOptionsTests: XCTestCase {
4041
XCTAssertNil(bbOptions.filterTypes)
4142
XCTAssertFalse(bbOptions.ignoreIndexableRecords)
4243
XCTAssertNil(bbOptions.indexableRecordsDistanceThreshold)
44+
XCTAssertNil(bbOptions.attributeSets)
4345
}
4446

4547
func testSearchOptionsProximityConstructors() {
@@ -58,6 +60,7 @@ class SearchOptionsTests: XCTestCase {
5860
XCTAssertNil(proximityOptions.filterTypes)
5961
XCTAssertFalse(proximityOptions.ignoreIndexableRecords)
6062
XCTAssertNil(proximityOptions.indexableRecordsDistanceThreshold)
63+
XCTAssertNil(proximityOptions.attributeSets)
6164
}
6265

6366
func testSearchOptionsNavigationConstructors() {
@@ -80,6 +83,7 @@ class SearchOptionsTests: XCTestCase {
8083
XCTAssertNil(navigationOptions.filterTypes)
8184
XCTAssertFalse(navigationOptions.ignoreIndexableRecords)
8285
XCTAssertNil(navigationOptions.indexableRecordsDistanceThreshold)
86+
XCTAssertNil(navigationOptions.attributeSets)
8387
}
8488

8589
func testSearchOptionsRouteConstructors() {
@@ -101,6 +105,7 @@ class SearchOptionsTests: XCTestCase {
101105
XCTAssertNil(routeOptions.filterTypes)
102106
XCTAssertFalse(routeOptions.ignoreIndexableRecords)
103107
XCTAssertNil(routeOptions.indexableRecordsDistanceThreshold)
108+
XCTAssertNil(routeOptions.attributeSets)
104109
}
105110

106111
func testSearchOptionsConversionForGeocodingAPI() {
@@ -125,6 +130,7 @@ class SearchOptionsTests: XCTestCase {
125130
fromCoreSearchOptions.indexableRecordsDistanceThreshold,
126131
searchOptions.indexableRecordsDistanceThreshold
127132
)
133+
XCTAssertEqual(fromCoreSearchOptions.attributeSets, searchOptions.attributeSets)
128134
}
129135

130136
func testSearchOptionsConversionForSBSAPI() {
@@ -155,6 +161,7 @@ class SearchOptionsTests: XCTestCase {
155161
fromCoreSearchOptions.indexableRecordsDistanceThreshold,
156162
searchOptions.indexableRecordsDistanceThreshold
157163
)
164+
XCTAssertEqual(fromCoreSearchOptions.attributeSets, searchOptions.attributeSets)
158165
}
159166

160167
func testSearchOptionsUsesLocale() {
@@ -184,6 +191,7 @@ class SearchOptionsTests: XCTestCase {
184191
XCTAssertNil(searchOptions.filterTypes)
185192
XCTAssertFalse(searchOptions.ignoreIndexableRecords)
186193
XCTAssertNil(searchOptions.indexableRecordsDistanceThreshold)
194+
XCTAssertNil(searchOptions.attributeSets)
187195
}
188196

189197
func testSearchOptionsConversion() throws {
@@ -206,6 +214,7 @@ class SearchOptionsTests: XCTestCase {
206214
XCTAssertEqual(mergedOptions.unsafeParameters, ["api": "v3"])
207215
XCTAssertEqual(mergedOptions.ignoreIndexableRecords, fullOptions.ignoreIndexableRecords)
208216
XCTAssertEqual(mergedOptions.indexableRecordsDistanceThreshold, fullOptions.indexableRecordsDistanceThreshold)
217+
XCTAssertEqual(mergedOptions.attributeSets, fullOptions.attributeSets)
209218
}
210219
}
211220

@@ -229,6 +238,7 @@ extension SearchOptions {
229238
filterTypes: [.poi, .address, .place],
230239
ignoreIndexableRecords: false,
231240
indexableRecordsDistanceThreshold: 2_000,
232-
unsafeParameters: ["arg": "value"]
241+
unsafeParameters: ["arg": "value"],
242+
attributeSets: [.basic, .photos]
233243
)
234244
}

Tests/MockWebServer/MockWebServer.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import Swifter
44
final class MockWebServer<Mock: MockResponse> {
55
let endpoint = "http://localhost:8080"
66

7+
var passedRequests: [Swifter.HttpRequest] = []
8+
79
private let server = HttpServer()
810

911
func setResponse(_ endpoint: Mock, query: String? = nil, statusCode: Int = 200) throws {
@@ -18,10 +20,16 @@ final class MockWebServer<Mock: MockResponse> {
1820

1921
switch method {
2022
case .get:
21-
server.get[route] = { _ in response }
23+
server.get[route] = { [weak self] request in
24+
self?.passedRequests.append(request)
25+
return response
26+
}
2227

2328
case .post:
24-
server.post[route] = { _ in response }
29+
server.post[route] = { [weak self] request in
30+
self?.passedRequests.append(request)
31+
return response
32+
}
2533
}
2634
}
2735

0 commit comments

Comments
 (0)