From e6116983f43e735bfaa715da5056a5d51ad75d9a Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 16 Sep 2023 23:31:20 +0100 Subject: [PATCH 1/3] Add async overload for search API This acts as an example for the conversation being held in issue #332. Async/await continues to grow in popularity and all modern codebases are moving towards its usage. This commit demonstrates how we can add support for Swift Concurrency without breaking existing compatibility with older operating systems. For a test I have taken an existing test, copied it, and converted it for async/await. In this instance, removing the XCTestExpectation and simply using an async test available since Xcode 13 back in September 2021. --- Sources/MeiliSearch/Indexes.swift | 22 ++++++++++- Tests/MeiliSearchUnitTests/SearchTests.swift | 40 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/Sources/MeiliSearch/Indexes.swift b/Sources/MeiliSearch/Indexes.swift index e42cdfa2..e70fbfe7 100755 --- a/Sources/MeiliSearch/Indexes.swift +++ b/Sources/MeiliSearch/Indexes.swift @@ -26,7 +26,7 @@ public struct Indexes { private let documents: Documents // Search methods - private let search: Search + fileprivate let search: Search // Settings methods private let settings: Settings @@ -1101,3 +1101,23 @@ public struct Indexes { } } } + +// MARK: Swift Concurrency (Async/Await) + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension Indexes { + /** + Search in the index. + + - Parameter searchParameters: Options on search. + - Throws: Error if a failure occurred. + - Returns: On completion if the request was successful a `Searchable` instance is returned containing the values. + */ + public func search(_ searchParameters: SearchParameters) async throws -> Searchable { + try await withCheckedThrowingContinuation { continuation in + self.search.search(self.uid, searchParameters) { result in + continuation.resume(with: result) + } + } + } +} diff --git a/Tests/MeiliSearchUnitTests/SearchTests.swift b/Tests/MeiliSearchUnitTests/SearchTests.swift index 506b7caa..a1a42f71 100644 --- a/Tests/MeiliSearchUnitTests/SearchTests.swift +++ b/Tests/MeiliSearchUnitTests/SearchTests.swift @@ -80,6 +80,46 @@ class SearchTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } + + @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) + func testSearchForBotmanMovieAsync() async throws { + let jsonString = """ + { + "hits": [ + { + "id": 29751, + "title": "Batman Unmasked: The Psychology of the Dark Knight", + "poster": "https://image.tmdb.org/t/p/w1280/jjHu128XLARc2k4cJrblAvZe0HE.jpg", + "overview": "Delve into the world of Batman and the vigilante justice tha", + "release_date": "2020-04-04T19:59:49.259572Z" + }, + { + "id": 471474, + "title": "Batman: Gotham by Gaslight", + "poster": "https://image.tmdb.org/t/p/w1280/7souLi5zqQCnpZVghaXv0Wowi0y.jpg", + "overview": "ve Victorian Age Gotham City, Batman begins his war on crime", + "release_date": "2020-04-04T19:59:49.259572Z" + } + ], + "offset": 0, + "limit": 20, + "processingTimeMs": 2, + "estimatedTotalHits": 2, + "query": "botman" + } + """ + + // Prepare the mock server + let data = jsonString.data(using: .utf8)! + let stubSearchResult: Searchable = try! Constants.customJSONDecoder.decode(Searchable.self, from: data) + session.pushData(jsonString) + + // Start the test with the mocked server + let searchParameters = SearchParameters.query("botman") + + let searchResult: Searchable = try await self.index.search(searchParameters) + XCTAssertEqual(stubSearchResult, searchResult) + } func testSearchForBotmanMovieFacets() { let jsonString = """ From 6f0291d214b4ab2f61a2b4ddf795027b79c00d97 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Thu, 28 Sep 2023 14:19:34 +0100 Subject: [PATCH 2/3] Separate Async Code I believe as we increase this implementation to support more APIs, it will become unwieldly stuck in one file. This commit pulls it out into a separate directory for ease of maintenance. --- Sources/MeiliSearch/Async/Indexes+async.swift | 19 ++++++++++++++++ Sources/MeiliSearch/Indexes.swift | 22 +------------------ 2 files changed, 20 insertions(+), 21 deletions(-) create mode 100644 Sources/MeiliSearch/Async/Indexes+async.swift diff --git a/Sources/MeiliSearch/Async/Indexes+async.swift b/Sources/MeiliSearch/Async/Indexes+async.swift new file mode 100644 index 00000000..69380154 --- /dev/null +++ b/Sources/MeiliSearch/Async/Indexes+async.swift @@ -0,0 +1,19 @@ +import Foundation + +@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) +extension Indexes { + /** + Search in the index. + + - Parameter searchParameters: Options on search. + - Throws: Error if a failure occurred. + - Returns: On completion if the request was successful a `Searchable` instance is returned containing the values. + */ + public func search(_ searchParameters: SearchParameters) async throws -> Searchable { + try await withCheckedThrowingContinuation { continuation in + self.search.search(self.uid, searchParameters) { result in + continuation.resume(with: result) + } + } + } +} diff --git a/Sources/MeiliSearch/Indexes.swift b/Sources/MeiliSearch/Indexes.swift index e70fbfe7..0f17f895 100755 --- a/Sources/MeiliSearch/Indexes.swift +++ b/Sources/MeiliSearch/Indexes.swift @@ -26,7 +26,7 @@ public struct Indexes { private let documents: Documents // Search methods - fileprivate let search: Search + internal let search: Search // Settings methods private let settings: Settings @@ -1101,23 +1101,3 @@ public struct Indexes { } } } - -// MARK: Swift Concurrency (Async/Await) - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension Indexes { - /** - Search in the index. - - - Parameter searchParameters: Options on search. - - Throws: Error if a failure occurred. - - Returns: On completion if the request was successful a `Searchable` instance is returned containing the values. - */ - public func search(_ searchParameters: SearchParameters) async throws -> Searchable { - try await withCheckedThrowingContinuation { continuation in - self.search.search(self.uid, searchParameters) { result in - continuation.resume(with: result) - } - } - } -} From 338faad9ef59579aa08fce6b1e470553ba919b2e Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Thu, 28 Sep 2023 14:23:56 +0100 Subject: [PATCH 3/3] Update README to use async example --- README.md | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 2c518cd2..c245ed87 100644 --- a/README.md +++ b/README.md @@ -149,24 +149,13 @@ With the `uid` of the task, you can check the status (`enqueued`, `canceled`, `p #### Basic Search ```swift - -let semaphore = DispatchSemaphore(value: 0) - -// Typealias that represents the result from Meilisearch. -typealias MeiliResult = Result, Swift.Error> - -// Call the function search and wait for the closure result. -client.index("movies").search(SearchParameters( query: "philoudelphia" )) { (result: MeiliResult) in - switch result { - case .success(let searchResult): - dump(searchResult) - case .failure(let error): - print(error.localizedDescription) - } - semaphore.signal() +do { + // Call the search function and wait for the result. + let result: SearchResult = try await client.index("movies").search(SearchParameters(query: "philoudelphia")) + dump(result) +} catch { + print(error.localizedDescription) } -semaphore.wait() - ``` Output: @@ -191,6 +180,8 @@ Output: Since Meilisearch is typo-tolerant, the movie `philadelphia` is a valid search response to `philoudelphia`. +> Note: All package APIs support closure-based results for backwards compatibility. Newer async/await variants are being added under [issue 332](https://github.com/meilisearch/meilisearch-swift/issues/332). + ## 🤖 Compatibility with Meilisearch This package guarantees compatibility with [version v1.x of Meilisearch](https://github.com/meilisearch/meilisearch/releases/latest), but some features may not be present. Please check the [issues](https://github.com/meilisearch/meilisearch-swift/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3Aenhancement) for more info.