From bdba2a35b70e2a80b57d16fd33e30ddc311645b2 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 13:20:23 +0100 Subject: [PATCH 01/10] Refactor Internal Reset Settings All 'reset' functions had the same exact implementation with the only chane being the API path. By reusing a single function we can have the same functionality with 100 less lines of code. --- Sources/MeiliSearch/Settings.swift | 158 ++++------------------------- 1 file changed, 21 insertions(+), 137 deletions(-) diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 6beef90c..64d7ff0c 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -74,23 +74,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.delete(api: "/indexes/\(uid)/settings") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + resetSetting(uid: uid, key: nil, completion: completion) } // MARK: Synonyms @@ -149,23 +133,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.delete(api: "/indexes/\(uid)/settings/synonyms") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + resetSetting(uid: uid, key: "synonyms", completion: completion) } // MARK: Stop Words @@ -223,23 +191,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.delete(api: "/indexes/\(uid)/settings/stop-words") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + resetSetting(uid: uid, key: "stop-words", completion: completion) } // MARK: Ranking @@ -298,23 +250,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.delete(api: "/indexes/\(uid)/settings/ranking-rules") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + resetSetting(uid: uid, key: "ranking-rules", completion: completion) } // MARK: Distinct attribute @@ -374,23 +310,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.delete(api: "/indexes/\(uid)/settings/distinct-attribute") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + resetSetting(uid: uid, key: "distinct-attribute", completion: completion) } // MARK: Searchable attributes @@ -449,23 +369,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.delete(api: "/indexes/\(uid)/settings/searchable-attributes") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + resetSetting(uid: uid, key: "searchable-attributes", completion: completion) } // MARK: Displayed attributes @@ -524,23 +428,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.delete(api: "/indexes/\(uid)/settings/displayed-attributes") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + resetSetting(uid: uid, key: "displayed-attributes", completion: completion) } // MARK: Filterable Attributes @@ -600,23 +488,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.delete(api: "/indexes/\(uid)/settings/filterable-attributes") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + resetSetting(uid: uid, key: "filterable-attributes", completion: completion) } // MARK: Sortable Attributes @@ -676,7 +548,19 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.delete(api: "/indexes/\(uid)/settings/sortable-attributes") { result in + resetSetting(uid: uid, key: "sortable-attributes", completion: completion) + } + + // MARK: Reusable Requests + + private func resetSetting( + uid: String, + key: String?, + completion: @escaping (Result) -> Void + ) { + // if a key is provided, path is equal to `/`, else it's an empty string + let path = key.map { "/" + $0 } ?? "" + self.request.delete(api: "/indexes/\(uid)/settings\(path)") { result in switch result { case .success(let data): guard let data: Data = data else { From 82b93600be7720e0509da01df33dd099442cd8a8 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 13:25:39 +0100 Subject: [PATCH 02/10] Refactor Internal Get Settings All 'get' functions had the same exact implementation with the only change being the API path. By reusing a single function we can have the same functionality with 100 less lines of code. --- Sources/MeiliSearch/Settings.swift | 191 ++++++----------------------- 1 file changed, 38 insertions(+), 153 deletions(-) diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 64d7ff0c..815b89cf 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -22,23 +22,8 @@ struct Settings { func get( _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.get(api: "/indexes/\(uid)/settings") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let settings: SettingResult = try Constants.customJSONDecoder.decode(SettingResult.self, from: data) - completion(.success(settings)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + + getSetting(uid: uid, key: nil, completion: completion) } func update( @@ -83,23 +68,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result<[String: [String]], Swift.Error>) -> Void) { - self.request.get(api: "/indexes/\(uid)/settings/synonyms") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let dictionary: [String: [String]] = try Constants.customJSONDecoder.decode([String: [String]].self, from: data) - completion(.success(dictionary)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + getSetting(uid: uid, key: "synonyms", completion: completion) } func updateSynonyms( @@ -141,23 +110,8 @@ struct Settings { func getStopWords( _ uid: String, _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { - self.request.get(api: "/indexes/\(uid)/settings/stop-words") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let array: [String] = try Constants.customJSONDecoder.decode([String].self, from: data) - completion(.success(array)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + + getSetting(uid: uid, key: "stop-words", completion: completion) } func updateStopWords( @@ -199,23 +153,8 @@ struct Settings { func getRankingRules( _ uid: String, _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { - self.request.get(api: "/indexes/\(uid)/settings/ranking-rules") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let array: [String] = try Constants.customJSONDecoder.decode([String].self, from: data) - completion(.success(array)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + + getSetting(uid: uid, key: "ranking-rules", completion: completion) } func updateRankingRules( @@ -259,23 +198,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result) -> Void) { - self.request.get(api: "/indexes/\(uid)/settings/distinct-attribute") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let value: String? = try Constants.customJSONDecoder.decode(String?.self, from: data) - completion(.success(value)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + getSetting(uid: uid, key: "distinct-attribute", completion: completion) } func updateDistinctAttribute( @@ -319,23 +242,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { - self.request.get(api: "/indexes/\(uid)/settings/searchable-attributes") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let array: [String] = try Constants.customJSONDecoder.decode([String].self, from: data) - completion(.success(array)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + getSetting(uid: uid, key: "searchable-attributes", completion: completion) } func updateSearchableAttributes( @@ -378,23 +285,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { - self.request.get(api: "/indexes/\(uid)/settings/displayed-attributes") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let array: [String] = try Constants.customJSONDecoder.decode([String].self, from: data) - completion(.success(array)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + getSetting(uid: uid, key: "displayed-attributes", completion: completion) } func updateDisplayedAttributes( @@ -437,23 +328,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { - self.request.get(api: "/indexes/\(uid)/settings/filterable-attributes") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let array: [String] = try Constants.customJSONDecoder.decode([String].self, from: data) - completion(.success(array)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + getSetting(uid: uid, key: "filterable-attributes", completion: completion) } func updateFilterableAttributes( @@ -497,23 +372,7 @@ struct Settings { _ uid: String, _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { - self.request.get(api: "/indexes/\(uid)/settings/sortable-attributes") { result in - switch result { - case .success(let data): - guard let data: Data = data else { - completion(.failure(MeiliSearch.Error.dataNotFound)) - return - } - do { - let array: [String] = try Constants.customJSONDecoder.decode([String].self, from: data) - completion(.success(array)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + getSetting(uid: uid, key: "sortable-attributes", completion: completion) } func updateSortableAttributes( @@ -553,6 +412,32 @@ struct Settings { // MARK: Reusable Requests + private func getSetting( + uid: String, + key: String?, + completion: @escaping (Result) -> Void + ) { + // if a key is provided, path is equal to `/`, else it's an empty string + let path = key.map { "/" + $0 } ?? "" + self.request.get(api: "/indexes/\(uid)/settings\(path)") { result in + switch result { + case .success(let data): + guard let data: Data = data else { + completion(.failure(MeiliSearch.Error.dataNotFound)) + return + } + do { + let array: ResponseType = try Constants.customJSONDecoder.decode(ResponseType.self, from: data) + completion(.success(array)) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } + private func resetSetting( uid: String, key: String?, From b53e431bb1f90e6817b0a5b83dbe3bee2e178907 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 13:35:39 +0100 Subject: [PATCH 03/10] Refactor Internal Update Settings All 'update' functions more or less had the same exact implementation with the only change being the API path and how the data was encoded. By reusing a single function we can have the same functionality with 150 less lines of code. --- Sources/MeiliSearch/Settings.swift | 229 ++++++----------------------- 1 file changed, 42 insertions(+), 187 deletions(-) diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 815b89cf..efc1156f 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -31,27 +31,7 @@ struct Settings { _ setting: Setting, _ completion: @escaping (Result) -> Void) { - let data: Data - do { - data = try JSONEncoder().encode(setting) - } catch { - completion(.failure(error)) - return - } - - self.request.patch(api: "/indexes/\(uid)/settings", data) { result in - switch result { - case .success(let data): - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + updateSetting(uid: uid, key: nil, data: setting, completion: completion) } // can this be refactor @@ -76,26 +56,7 @@ struct Settings { _ synonyms: [String: [String]]? = [:], _ completion: @escaping (Result) -> Void) { - let data: Data - do { - data = try JSONEncoder().encode(synonyms) - } catch { - completion(.failure(error)) - return - } - self.request.put(api: "/indexes/\(uid)/settings/synonyms", data) { result in - switch result { - case .success(let data): - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + updateSetting(uid: uid, key: "synonyms", data: synonyms, completion: completion) } func resetSynonyms( @@ -119,26 +80,7 @@ struct Settings { _ stopWords: [String]? = [], _ completion: @escaping (Result) -> Void) { - let data: Data - do { - data = try JSONEncoder().encode(stopWords) - } catch { - completion(.failure(error)) - return - } - self.request.put(api: "/indexes/\(uid)/settings/stop-words", data) { result in - switch result { - case .success(let data): - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + updateSetting(uid: uid, key: "stop-words", data: stopWords, completion: completion) } func resetStopWords( @@ -162,27 +104,7 @@ struct Settings { _ rankingRules: [String], _ completion: @escaping (Result) -> Void) { - let data: Data - do { - data = try JSONSerialization.data(withJSONObject: rankingRules, options: []) - } catch { - completion(.failure(error)) - return - } - - self.request.put(api: "/indexes/\(uid)/settings/ranking-rules", data) { result in - switch result { - case .success(let data): - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + updateSetting(uid: uid, key: "ranking-rules", data: rankingRules, completion: completion) } func resetRankingRules( @@ -206,27 +128,7 @@ struct Settings { _ distinctAttribute: String, _ completion: @escaping (Result) -> Void) { - let data: Data - do { - data = try JSONEncoder().encode(distinctAttribute) - } catch { - completion(.failure(error)) - return - } - - self.request.put(api: "/indexes/\(uid)/settings/distinct-attribute", data) { result in - switch result { - case .success(let data): - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + updateSetting(uid: uid, key: "distinct-attribute", data: distinctAttribute, completion: completion) } func resetDistinctAttribute( @@ -250,26 +152,7 @@ struct Settings { _ searchableAttributes: [String], _ completion: @escaping (Result) -> Void) { - let data: Data - do { - data = try JSONSerialization.data(withJSONObject: searchableAttributes, options: []) - } catch { - completion(.failure(error)) - return - } - self.request.put(api: "/indexes/\(uid)/settings/searchable-attributes", data) { result in - switch result { - case .success(let data): - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + updateSetting(uid: uid, key: "searchable-attributes", data: searchableAttributes, completion: completion) } func resetSearchableAttributes( @@ -293,26 +176,7 @@ struct Settings { _ displayedAttributes: [String], _ completion: @escaping (Result) -> Void) { - let data: Data - do { - data = try JSONSerialization.data(withJSONObject: displayedAttributes, options: []) - } catch { - completion(.failure(error)) - return - } - self.request.put(api: "/indexes/\(uid)/settings/displayed-attributes", data) { result in - switch result { - case .success(let data): - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + updateSetting(uid: uid, key: "displayed-attributes", data: displayedAttributes, completion: completion) } func resetDisplayedAttributes( @@ -336,27 +200,7 @@ struct Settings { _ attributes: [String], _ completion: @escaping (Result) -> Void) { - let data: Data - do { - data = try JSONSerialization.data(withJSONObject: attributes, options: []) - } catch { - completion(.failure(error)) - return - } - - self.request.put(api: "/indexes/\(uid)/settings/filterable-attributes", data) { result in - switch result { - case .success(let data): - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + updateSetting(uid: uid, key: "filterable-attributes", data: attributes, completion: completion) } func resetFilterableAttributes( @@ -380,27 +224,7 @@ struct Settings { _ attributes: [String], _ completion: @escaping (Result) -> Void) { - let data: Data - do { - data = try JSONSerialization.data(withJSONObject: attributes, options: []) - } catch { - completion(.failure(error)) - return - } - - self.request.put(api: "/indexes/\(uid)/settings/sortable-attributes", data) { result in - switch result { - case .success(let data): - do { - let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) - completion(.success(task)) - } catch { - completion(.failure(error)) - } - case .failure(let error): - completion(.failure(error)) - } - } + updateSetting(uid: uid, key: "sortable-attributes", data: attributes, completion: completion) } func resetSortableAttributes( @@ -411,7 +235,7 @@ struct Settings { } // MARK: Reusable Requests - + private func getSetting( uid: String, key: String?, @@ -437,7 +261,38 @@ struct Settings { } } } - + + private func updateSetting( + uid: String, + key: String?, + data: Encodable, + completion: @escaping (Result) -> Void + ) { + let body: Data + do { + body = try JSONEncoder().encode(data) + } catch { + completion(.failure(error)) + return + } + + // if a key is provided, path is equal to `/`, else it's an empty string + let path = key.map { "/" + $0 } ?? "" + self.request.put(api: "/indexes/\(uid)/settings\(path)", body) { result in + switch result { + case .success(let data): + do { + let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) + completion(.success(task)) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } + private func resetSetting( uid: String, key: String?, From d402481e2de2863c5ff824d3aa94907592d136e5 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 13:41:45 +0100 Subject: [PATCH 04/10] Undo Update Refactor for Patch Endpoint Running integration tests highlighted that I was a bit overzealous with my previous refactor. This one function uses a different HTTP method, in this instance it was cleaner to just have some duplication. I've added a comment for future reference. --- Sources/MeiliSearch/Settings.swift | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index efc1156f..be23f99d 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -31,7 +31,28 @@ struct Settings { _ setting: Setting, _ completion: @escaping (Result) -> Void) { - updateSetting(uid: uid, key: nil, data: setting, completion: completion) + let data: Data + do { + data = try JSONEncoder().encode(setting) + } catch { + completion(.failure(error)) + return + } + + // this uses patch instead of put for networking, so shouldn't use the reusable 'updateSetting' function + self.request.patch(api: "/indexes/\(uid)/settings", data) { result in + switch result { + case .success(let data): + do { + let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) + completion(.success(task)) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } } // can this be refactor From 3f82c0770f1034c274ed9b9e77b3ae4aea13ba24 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:01:11 +0100 Subject: [PATCH 05/10] Add support for pagination settings --- Sources/MeiliSearch/Indexes.swift | 41 +++++++ Sources/MeiliSearch/Model/Pagination.swift | 14 +++ Sources/MeiliSearch/Model/Setting.swift | 7 +- Sources/MeiliSearch/Model/SettingResult.swift | 3 + Sources/MeiliSearch/Model/Task.swift | 3 + Sources/MeiliSearch/Settings.swift | 45 +++++++ .../SettingsTests.swift | 111 ++++++++++++++++-- 7 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 Sources/MeiliSearch/Model/Pagination.swift diff --git a/Sources/MeiliSearch/Indexes.swift b/Sources/MeiliSearch/Indexes.swift index cc291ffc..cd44b6d4 100755 --- a/Sources/MeiliSearch/Indexes.swift +++ b/Sources/MeiliSearch/Indexes.swift @@ -867,6 +867,47 @@ public struct Indexes { _ completion: @escaping (Result) -> Void) { self.settings.resetSortableAttributes(self.uid, completion) } + + // MARK: Pagination + + /** + Get the pagination settings for the current index. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains an `Pagination` + value if the request was successful, or `Error` if a failure occurred. + */ + public func getPaginationSettings( + _ completion: @escaping (Result) -> Void) { + self.settings.getPaginationSettings(self.uid, completion) + } + + /** + Updates the pagination setting for the index. + + - parameter settings: The new preferences to use for pagination. + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func updatePaginationSettings( + _ settings: Pagination, + _ completion: @escaping (Result) -> Void) { + self.settings.updatePaginationSettings(self.uid, settings, completion) + } + + /** + Reset the pagination settings for the index. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func resetPaginationSettings( + _ completion: @escaping (Result) -> Void) { + self.settings.resetPaginationSettings(self.uid, completion) + } + // MARK: Stats /** diff --git a/Sources/MeiliSearch/Model/Pagination.swift b/Sources/MeiliSearch/Model/Pagination.swift new file mode 100644 index 00000000..654ccaf4 --- /dev/null +++ b/Sources/MeiliSearch/Model/Pagination.swift @@ -0,0 +1,14 @@ +import Foundation + +/** + `Pagination` is a wrapper used in the index pagination setting routes to handle the returned data. + */ +public struct Pagination: Codable, Equatable { + /// The maximum number of hits (document records) which can be returned by a single search request. + /// By default, Meilisearch returns 1000 results per search. This limit protects your database from malicious scraping. + public let maxTotalHits: Int + + public init(maxTotalHits: Int) { + self.maxTotalHits = maxTotalHits + } +} diff --git a/Sources/MeiliSearch/Model/Setting.swift b/Sources/MeiliSearch/Model/Setting.swift index 8f52c578..34d86671 100644 --- a/Sources/MeiliSearch/Model/Setting.swift +++ b/Sources/MeiliSearch/Model/Setting.swift @@ -32,6 +32,9 @@ public struct Setting: Codable, Equatable { /// List of attributes used for sorting public let sortableAttributes: [String]? + + /// Pagination settings for the current index + public let pagination: Pagination? // MARK: Initializers @@ -43,7 +46,8 @@ public struct Setting: Codable, Equatable { synonyms: [String: [String]]? = nil, distinctAttribute: String? = nil, filterableAttributes: [String]? = nil, - sortableAttributes: [String]? = nil + sortableAttributes: [String]? = nil, + pagination: Pagination? = nil ) { self.rankingRules = rankingRules self.searchableAttributes = searchableAttributes @@ -53,5 +57,6 @@ public struct Setting: Codable, Equatable { self.distinctAttribute = distinctAttribute self.filterableAttributes = filterableAttributes self.sortableAttributes = sortableAttributes + self.pagination = pagination } } diff --git a/Sources/MeiliSearch/Model/SettingResult.swift b/Sources/MeiliSearch/Model/SettingResult.swift index 168acc4c..3811e374 100644 --- a/Sources/MeiliSearch/Model/SettingResult.swift +++ b/Sources/MeiliSearch/Model/SettingResult.swift @@ -27,4 +27,7 @@ public struct SettingResult: Codable, Equatable { /// List of attributes used for sorting public let sortableAttributes: [String] + + /// Pagination settings for the current index + public let pagination: Pagination } diff --git a/Sources/MeiliSearch/Model/Task.swift b/Sources/MeiliSearch/Model/Task.swift index 405c9522..770a410f 100644 --- a/Sources/MeiliSearch/Model/Task.swift +++ b/Sources/MeiliSearch/Model/Task.swift @@ -75,6 +75,9 @@ public struct Task: Codable, Equatable { /// Distinct attribute on settings actions public let distinctAttribute: String? + + /// Settings for index level pagination rules + public let pagination: Pagination? } /// Error information in case of failed update. diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index be23f99d..35e9455f 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -254,6 +254,51 @@ struct Settings { resetSetting(uid: uid, key: "sortable-attributes", completion: completion) } + + // MARK: Pagination Preferences + + func getPaginationSettings( + _ uid: String, + _ completion: @escaping (Result) -> Void) { + + getSetting(uid: uid, key: "pagination", completion: completion) + } + + func updatePaginationSettings( + _ uid: String, + _ pagination: Pagination, + _ completion: @escaping (Result) -> Void) { + + let data: Data + do { + data = try JSONEncoder().encode(pagination) + } catch { + completion(.failure(error)) + return + } + + // this uses patch instead of put for networking, so shouldn't use the reusable 'updateSetting' function + self.request.patch(api: "/indexes/\(uid)/settings/pagination", data) { result in + switch result { + case .success(let data): + do { + let task: TaskInfo = try Constants.customJSONDecoder.decode(TaskInfo.self, from: data) + completion(.success(task)) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + } + } + } + + func resetPaginationSettings( + _ uid: String, + _ completion: @escaping (Result) -> Void) { + + resetSetting(uid: uid, key: "pagination", completion: completion) + } // MARK: Reusable Requests diff --git a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift index 836ff2ea..2f75b31c 100644 --- a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -27,6 +27,7 @@ class SettingsTests: XCTestCase { private let defaultSortableAttributes: [String] = [] private let defaultStopWords: [String] = [] private let defaultSynonyms: [String: [String]] = [:] + private let defaultPagination: Pagination = .init(maxTotalHits: 1000) private var defaultGlobalSettings: Setting? private var defaultGlobalReturnedSettings: SettingResult? @@ -71,7 +72,8 @@ class SettingsTests: XCTestCase { synonyms: self.defaultSynonyms, distinctAttribute: self.defaultDistinctAttribute, filterableAttributes: self.defaultFilterableAttributes, - sortableAttributes: self.defaultSortableAttributes + sortableAttributes: self.defaultSortableAttributes, + pagination: self.defaultPagination ) } @@ -513,6 +515,93 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } + // MARK: Pagination + + func testGetPagination() { + let expectation = XCTestExpectation(description: "Get current pagination") + + self.index.getPaginationSettings { result in + switch result { + case .success(let pagination): + XCTAssertEqual(self.defaultPagination, pagination) + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to get searchable attributes") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + func testUpdatePagination() { + let expectation = XCTestExpectation(description: "Update settings for pagination") + + let newPaginationSettings: Pagination = .init(maxTotalHits: 5) + + self.index.updatePaginationSettings(newPaginationSettings) { result in + switch result { + case .success(let task): + self.client.waitForTask(task: task) { result in + switch result { + case .success(let task): + XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual(Task.Status.succeeded, task.status) + if let details = task.details { + if let pagination = details.pagination { + XCTAssertEqual(newPaginationSettings, pagination) + } else { + XCTFail("pagination should not be nil") + } + } else { + XCTFail("details should exists in details field of task") + } + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to wait for task") + expectation.fulfill() + } + } + case .failure(let error): + dump(error) + XCTFail("Failed updating pagination") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + func testResetPagination() { + let expectation = XCTestExpectation(description: "Reset settings for pagination") + + self.index.resetPaginationSettings { result in + switch result { + case .success(let task): + self.client.waitForTask(task: task) { result in + switch result { + case .success(let task): + XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual(Task.Status.succeeded, task.status) + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to wait for task") + expectation.fulfill() + } + } + case .failure(let error): + dump(error) + XCTFail("Failed reseting pagination") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + // MARK: Stop words func testGetStopWords() { @@ -847,7 +936,7 @@ class SettingsTests: XCTestCase { // MARK: Global Settings - func testgetSettings() { + func testGetSettings() { let expectation = XCTestExpectation(description: "Get current settings") self.index.getSettings { result in @@ -865,7 +954,7 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testupdateSettings() { + func testUpdateSettings() { let newSettings = Setting( rankingRules: ["words", "typo", "proximity", "attribute", "sort", "exactness"], searchableAttributes: ["id", "title"], @@ -884,7 +973,8 @@ class SettingsTests: XCTestCase { synonyms: [:], distinctAttribute: nil, filterableAttributes: [], - sortableAttributes: ["title"] + sortableAttributes: ["title"], + pagination: .init(maxTotalHits: 1000) ) let expectation = XCTestExpectation(description: "Update settings") @@ -950,7 +1040,7 @@ class SettingsTests: XCTestCase { self.wait(for: [overrideSettingsExpectation], timeout: TESTS_TIME_OUT) } - func testupdateSettingssWithSynonymsAndStopWordsNil() { + func testUpdateSettingsWithSynonymsAndStopWordsNil() { let expectation = XCTestExpectation(description: "Update settings") let newSettings = Setting( @@ -961,7 +1051,9 @@ class SettingsTests: XCTestCase { synonyms: nil, distinctAttribute: nil, filterableAttributes: ["title"], - sortableAttributes: ["title"]) + sortableAttributes: ["title"], + pagination: .init(maxTotalHits: 500) + ) let expectedSettingResult = SettingResult( rankingRules: ["words", "typo", "proximity", "attribute", "sort", "exactness"], @@ -971,7 +1063,9 @@ class SettingsTests: XCTestCase { synonyms: [:], distinctAttribute: nil, filterableAttributes: ["title"], - sortableAttributes: ["title"]) + sortableAttributes: ["title"], + pagination: .init(maxTotalHits: 500) + ) self.index.updateSettings(newSettings) { result in switch result { @@ -988,6 +1082,7 @@ class SettingsTests: XCTestCase { XCTAssertEqual(expectedSettingResult.distinctAttribute, details.distinctAttribute) XCTAssertEqual(expectedSettingResult.filterableAttributes, details.filterableAttributes) XCTAssertEqual(expectedSettingResult.sortableAttributes, details.sortableAttributes) + XCTAssertEqual(expectedSettingResult.pagination.maxTotalHits, details.pagination?.maxTotalHits) } else { XCTFail("details should exists in details field of task") } @@ -1008,7 +1103,7 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testresetSettingss() { + func testResetSettings() { let expectation = XCTestExpectation(description: "Reset settings") self.index.resetSettings { result in From 9629500887085a7879b4cfaeab4fe2cfa61315d9 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:18:51 +0100 Subject: [PATCH 06/10] Add support for (non-)separator tokens and dictionary settings --- Sources/MeiliSearch/Indexes.swift | 120 ++++++++++++++++++ Sources/MeiliSearch/Model/Setting.swift | 17 ++- Sources/MeiliSearch/Model/SettingResult.swift | 11 +- Sources/MeiliSearch/Model/Task.swift | 11 +- Sources/MeiliSearch/Settings.swift | 72 +++++++++++ 5 files changed, 228 insertions(+), 3 deletions(-) diff --git a/Sources/MeiliSearch/Indexes.swift b/Sources/MeiliSearch/Indexes.swift index cd44b6d4..e5284e0f 100755 --- a/Sources/MeiliSearch/Indexes.swift +++ b/Sources/MeiliSearch/Indexes.swift @@ -867,6 +867,126 @@ public struct Indexes { _ completion: @escaping (Result) -> Void) { self.settings.resetSortableAttributes(self.uid, completion) } + + // MARK: Separator Tokens + + /** + Fetch the `separatorTokens` setting of a Meilisearch index. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains an `[String]` + value if the request was successful, or `Error` if a failure occurred. + */ + public func getSeparatorTokens( + _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { + self.settings.getSeparatorTokens(self.uid, completion) + } + + /** + Modify the `separatorTokens` setting of a Meilisearch index. + + - parameter attributes: List of tokens that will be considered as word separators + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func updateSeparatorTokens( + _ attributes: [String], + _ completion: @escaping (Result) -> Void) { + self.settings.updateSeparatorTokens(self.uid, attributes, completion) + } + + /** + Reset the `separatorTokens` setting of a Meilisearch index to the default value `[]`. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func resetSeparatorTokens( + _ completion: @escaping (Result) -> Void) { + self.settings.resetSeparatorTokens(self.uid, completion) + } + + // MARK: Non Separator Tokens + + /** + Fetch the `nonSeparatorTokens` setting of a Meilisearch index. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains an `[String]` + value if the request was successful, or `Error` if a failure occurred. + */ + public func getNonSeparatorTokens( + _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { + self.settings.getNonSeparatorTokens(self.uid, completion) + } + + /** + Modify the `nonSeparatorTokens` setting of a Meilisearch index. + + - parameter attributes: List of tokens that will not be considered as word separators + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func updateNonSeparatorTokens( + _ attributes: [String], + _ completion: @escaping (Result) -> Void) { + self.settings.updateNonSeparatorTokens(self.uid, attributes, completion) + } + + /** + Reset the `nonSeparatorTokens` setting of a Meilisearch index to the default value `[]`. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func resetNonSeparatorTokens( + _ completion: @escaping (Result) -> Void) { + self.settings.resetNonSeparatorTokens(self.uid, completion) + } + + // MARK: Dictionary + + /** + Fetch the `dictionary` setting of a Meilisearch index. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains an `[String]` + value if the request was successful, or `Error` if a failure occurred. + */ + public func getDictionary( + _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { + self.settings.getDictionary(self.uid, completion) + } + + /** + Modify the `dictionary` setting of a Meilisearch index. + + - parameter attributes: List of words on which the segmentation will be overridden + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func updateDictionary( + _ attributes: [String], + _ completion: @escaping (Result) -> Void) { + self.settings.updateDictionary(self.uid, attributes, completion) + } + + /** + Reset the `dictionary` setting of a Meilisearch index to the default value `[]`. + + - parameter completion: The completion closure is used to notify when the server + completes the query request, it returns a `Result` object that contains `TaskInfo` + value if the request was successful, or `Error` if a failure occurred. + */ + public func resetDictionary( + _ completion: @escaping (Result) -> Void) { + self.settings.resetDictionary(self.uid, completion) + } // MARK: Pagination diff --git a/Sources/MeiliSearch/Model/Setting.swift b/Sources/MeiliSearch/Model/Setting.swift index 34d86671..a5fe4b04 100644 --- a/Sources/MeiliSearch/Model/Setting.swift +++ b/Sources/MeiliSearch/Model/Setting.swift @@ -32,7 +32,16 @@ public struct Setting: Codable, Equatable { /// List of attributes used for sorting public let sortableAttributes: [String]? - + + /// List of tokens that will be considered as word separators by Meilisearch. + public let separatorTokens: [String]? + + /// List of tokens that will not be considered as word separators by Meilisearch. + public let nonSeparatorTokens: [String]? + + /// List of words on which the segmentation will be overridden. + public let dictionary: [String]? + /// Pagination settings for the current index public let pagination: Pagination? @@ -47,6 +56,9 @@ public struct Setting: Codable, Equatable { distinctAttribute: String? = nil, filterableAttributes: [String]? = nil, sortableAttributes: [String]? = nil, + separatorTokens: [String]? = nil, + nonSeparatorTokens: [String]? = nil, + dictionary: [String]? = nil, pagination: Pagination? = nil ) { self.rankingRules = rankingRules @@ -57,6 +69,9 @@ public struct Setting: Codable, Equatable { self.distinctAttribute = distinctAttribute self.filterableAttributes = filterableAttributes self.sortableAttributes = sortableAttributes + self.nonSeparatorTokens = nonSeparatorTokens + self.separatorTokens = separatorTokens + self.dictionary = dictionary self.pagination = pagination } } diff --git a/Sources/MeiliSearch/Model/SettingResult.swift b/Sources/MeiliSearch/Model/SettingResult.swift index 3811e374..2ce7fcc1 100644 --- a/Sources/MeiliSearch/Model/SettingResult.swift +++ b/Sources/MeiliSearch/Model/SettingResult.swift @@ -27,7 +27,16 @@ public struct SettingResult: Codable, Equatable { /// List of attributes used for sorting public let sortableAttributes: [String] - + + /// List of tokens that will be considered as word separators by Meilisearch. + public let separatorTokens: [String] + + /// List of tokens that will not be considered as word separators by Meilisearch. + public let nonSeparatorTokens: [String] + + /// List of words on which the segmentation will be overridden. + public let dictionary: [String] + /// Pagination settings for the current index public let pagination: Pagination } diff --git a/Sources/MeiliSearch/Model/Task.swift b/Sources/MeiliSearch/Model/Task.swift index 770a410f..476c87d3 100644 --- a/Sources/MeiliSearch/Model/Task.swift +++ b/Sources/MeiliSearch/Model/Task.swift @@ -75,7 +75,16 @@ public struct Task: Codable, Equatable { /// Distinct attribute on settings actions public let distinctAttribute: String? - + + /// List of tokens that will be considered as word separators by Meilisearch. + public let separatorTokens: [String]? + + /// List of tokens that will not be considered as word separators by Meilisearch. + public let nonSeparatorTokens: [String]? + + /// List of words on which the segmentation will be overridden. + public let dictionary: [String]? + /// Settings for index level pagination rules public let pagination: Pagination? diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 35e9455f..3a800702 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -255,6 +255,78 @@ struct Settings { resetSetting(uid: uid, key: "sortable-attributes", completion: completion) } + // MARK: Separator Tokens + + func getSeparatorTokens( + _ uid: String, + _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { + + getSetting(uid: uid, key: "separator-tokens", completion: completion) + } + + func updateSeparatorTokens( + _ uid: String, + _ attributes: [String], + _ completion: @escaping (Result) -> Void) { + + updateSetting(uid: uid, key: "non-separator-tokens", data: attributes, completion: completion) + } + + func resetSeparatorTokens( + _ uid: String, + _ completion: @escaping (Result) -> Void) { + + resetSetting(uid: uid, key: "non-separator-tokens", completion: completion) + } + + // MARK: Non-Separator Tokens + + func getNonSeparatorTokens( + _ uid: String, + _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { + + getSetting(uid: uid, key: "non-separator-tokens", completion: completion) + } + + func updateNonSeparatorTokens( + _ uid: String, + _ attributes: [String], + _ completion: @escaping (Result) -> Void) { + + updateSetting(uid: uid, key: "non-separator-tokens", data: attributes, completion: completion) + } + + func resetNonSeparatorTokens( + _ uid: String, + _ completion: @escaping (Result) -> Void) { + + resetSetting(uid: uid, key: "non-separator-tokens", completion: completion) + } + + // MARK: Dictionary + + func getDictionary( + _ uid: String, + _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { + + getSetting(uid: uid, key: "dictionary", completion: completion) + } + + func updateDictionary( + _ uid: String, + _ attributes: [String], + _ completion: @escaping (Result) -> Void) { + + updateSetting(uid: uid, key: "dictionary", data: attributes, completion: completion) + } + + func resetDictionary( + _ uid: String, + _ completion: @escaping (Result) -> Void) { + + resetSetting(uid: uid, key: "dictionary", completion: completion) + } + // MARK: Pagination Preferences func getPaginationSettings( From 6bc464475f3043157ca59fc3699f453d0a670290 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:30:30 +0100 Subject: [PATCH 07/10] Add Integration Tests for New APIs --- Sources/MeiliSearch/Settings.swift | 4 +- .../SettingsTests.swift | 297 +++++++++++++++++- 2 files changed, 298 insertions(+), 3 deletions(-) diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 3a800702..6114b883 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -269,14 +269,14 @@ struct Settings { _ attributes: [String], _ completion: @escaping (Result) -> Void) { - updateSetting(uid: uid, key: "non-separator-tokens", data: attributes, completion: completion) + updateSetting(uid: uid, key: "separator-tokens", data: attributes, completion: completion) } func resetSeparatorTokens( _ uid: String, _ completion: @escaping (Result) -> Void) { - resetSetting(uid: uid, key: "non-separator-tokens", completion: completion) + resetSetting(uid: uid, key: "separator-tokens", completion: completion) } // MARK: Non-Separator Tokens diff --git a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift index 2f75b31c..769ed023 100644 --- a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -25,6 +25,9 @@ class SettingsTests: XCTestCase { private let defaultSearchableAttributes: [String] = ["*"] private let defaultFilterableAttributes: [String] = [] private let defaultSortableAttributes: [String] = [] + private let defaultNonSeparatorTokens: [String] = [] + private let defaultSeparatorTokens: [String] = [] + private let defaultDictionary: [String] = [] private let defaultStopWords: [String] = [] private let defaultSynonyms: [String: [String]] = [:] private let defaultPagination: Pagination = .init(maxTotalHits: 1000) @@ -61,7 +64,11 @@ class SettingsTests: XCTestCase { synonyms: self.defaultSynonyms, distinctAttribute: self.defaultDistinctAttribute, filterableAttributes: self.defaultFilterableAttributes, - sortableAttributes: self.defaultFilterableAttributes + sortableAttributes: self.defaultFilterableAttributes, + separatorTokens: self.defaultSeparatorTokens, + nonSeparatorTokens: self.defaultNonSeparatorTokens, + dictionary: self.defaultDictionary, + pagination: self.defaultPagination ) self.defaultGlobalReturnedSettings = SettingResult( @@ -73,6 +80,9 @@ class SettingsTests: XCTestCase { distinctAttribute: self.defaultDistinctAttribute, filterableAttributes: self.defaultFilterableAttributes, sortableAttributes: self.defaultSortableAttributes, + separatorTokens: self.defaultSeparatorTokens, + nonSeparatorTokens: self.defaultNonSeparatorTokens, + dictionary: self.defaultDictionary, pagination: self.defaultPagination ) } @@ -514,6 +524,279 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } + + // MARK: Separator Tokens + + func testGetSeparatorTokens() { + let expectation = XCTestExpectation(description: "Get current Separator Tokens") + + self.index.getSeparatorTokens { result in + switch result { + case .success(let separatorTokens): + XCTAssertEqual(self.defaultSeparatorTokens, separatorTokens) + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to get Separator Tokens") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + func testUpdateSeparatorTokens() { + let expectation = XCTestExpectation(description: "Update settings for separator tokens") + + let newSeparatorTokens: [String] = [ + "&", + "
", + "@" + ] + + self.index.updateSeparatorTokens(newSeparatorTokens) { result in + switch result { + case .success(let task): + self.client.waitForTask(task: task) { result in + switch result { + case .success(let task): + XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual(Task.Status.succeeded, task.status) + if let details = task.details { + if let separatorTokens = details.separatorTokens { + XCTAssertEqual(newSeparatorTokens, separatorTokens) + } else { + XCTFail("separatorTokens should not be nil") + } + } else { + XCTFail("details should exists in details field of task") + } + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to wait for task") + expectation.fulfill() + } + } + case .failure(let error): + dump(error) + XCTFail("Failed updating separator tokens") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + func testResetSeparatorTokens() { + let expectation = XCTestExpectation(description: "Reset settings for separator tokens") + + self.index.resetSeparatorTokens { result in + switch result { + case .success(let task): + self.client.waitForTask(task: task) { result in + switch result { + case .success(let task): + XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual(Task.Status.succeeded, task.status) + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to wait for task") + expectation.fulfill() + } + } + case .failure(let error): + dump(error) + XCTFail("Failed reseting separator tokens") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + // MARK: Non Separator Tokens + + func testGetNonSeparatorTokens() { + let expectation = XCTestExpectation(description: "Get current non separator tokens") + + self.index.getNonSeparatorTokens { result in + switch result { + case .success(let nonSeparatorTokens): + XCTAssertEqual(self.defaultNonSeparatorTokens, nonSeparatorTokens) + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to get non separator tokens") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + func testUpdateNonSeparatorTokens() { + let expectation = XCTestExpectation(description: "Update settings for non separator tokens") + + let newNonSeparatorTokens: [String] = [ + "#", + "-", + "_" + ] + + self.index.updateNonSeparatorTokens(newNonSeparatorTokens) { result in + switch result { + case .success(let task): + self.client.waitForTask(task: task) { result in + switch result { + case .success(let task): + XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual(Task.Status.succeeded, task.status) + if let details = task.details { + if let nonSeparatorTokens = details.nonSeparatorTokens { + XCTAssertEqual(newNonSeparatorTokens, nonSeparatorTokens) + } else { + XCTFail("nonSeparatorTokens should not be nil") + } + } else { + XCTFail("details should exists in details field of task") + } + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to wait for task") + expectation.fulfill() + } + } + case .failure(let error): + dump(error) + XCTFail("Failed updating non separator tokens") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + func testResetNonSeparatorTokens() { + let expectation = XCTestExpectation(description: "Reset settings for searchable attributes") + + self.index.resetSearchableAttributes { result in + switch result { + case .success(let task): + self.client.waitForTask(task: task) { result in + switch result { + case .success(let task): + XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual(Task.Status.succeeded, task.status) + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to wait for task") + expectation.fulfill() + } + } + case .failure(let error): + dump(error) + XCTFail("Failed reseting searchable attributes") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + // MARK: Dictionary + + func testGetDictionary() { + let expectation = XCTestExpectation(description: "Get current dictionary") + + self.index.getDictionary { result in + switch result { + case .success(let dictionary): + XCTAssertEqual(self.defaultDictionary, dictionary) + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to get dictionary") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + func testUpdateDictionary() { + let expectation = XCTestExpectation(description: "Update settings for dictionary") + + let newDictionary: [String] = [ + "J.K", + "Dr.", + "G/Box" + ] + + self.index.updateDictionary(newDictionary) { result in + switch result { + case .success(let task): + self.client.waitForTask(task: task) { result in + switch result { + case .success(let task): + XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual(Task.Status.succeeded, task.status) + if let details = task.details { + if let dictionary = details.dictionary { + XCTAssertEqual(newDictionary.sorted(), dictionary.sorted()) + } else { + XCTFail("dictionary should not be nil") + } + } else { + XCTFail("details should exists in details field of task") + } + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to wait for task") + expectation.fulfill() + } + } + case .failure(let error): + dump(error) + XCTFail("Failed updating dictionary") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } + + func testResetDictionary() { + let expectation = XCTestExpectation(description: "Reset settings for dictionary") + + self.index.resetDictionary { result in + switch result { + case .success(let task): + self.client.waitForTask(task: task) { result in + switch result { + case .success(let task): + XCTAssertEqual("settingsUpdate", task.type) + XCTAssertEqual(Task.Status.succeeded, task.status) + expectation.fulfill() + case .failure(let error): + dump(error) + XCTFail("Failed to wait for task") + expectation.fulfill() + } + } + case .failure(let error): + dump(error) + XCTFail("Failed reseting dictionary") + expectation.fulfill() + } + } + + self.wait(for: [expectation], timeout: TESTS_TIME_OUT) + } // MARK: Pagination @@ -974,6 +1257,9 @@ class SettingsTests: XCTestCase { distinctAttribute: nil, filterableAttributes: [], sortableAttributes: ["title"], + separatorTokens: [], + nonSeparatorTokens: [], + dictionary: [], pagination: .init(maxTotalHits: 1000) ) @@ -1052,6 +1338,9 @@ class SettingsTests: XCTestCase { distinctAttribute: nil, filterableAttributes: ["title"], sortableAttributes: ["title"], + separatorTokens: ["&"], + nonSeparatorTokens: ["#"], + dictionary: ["J.K"], pagination: .init(maxTotalHits: 500) ) @@ -1064,6 +1353,9 @@ class SettingsTests: XCTestCase { distinctAttribute: nil, filterableAttributes: ["title"], sortableAttributes: ["title"], + separatorTokens: ["&"], + nonSeparatorTokens: ["#"], + dictionary: ["J.K"], pagination: .init(maxTotalHits: 500) ) @@ -1082,6 +1374,9 @@ class SettingsTests: XCTestCase { XCTAssertEqual(expectedSettingResult.distinctAttribute, details.distinctAttribute) XCTAssertEqual(expectedSettingResult.filterableAttributes, details.filterableAttributes) XCTAssertEqual(expectedSettingResult.sortableAttributes, details.sortableAttributes) + XCTAssertEqual(expectedSettingResult.separatorTokens, details.separatorTokens) + XCTAssertEqual(expectedSettingResult.nonSeparatorTokens, details.nonSeparatorTokens) + XCTAssertEqual(expectedSettingResult.dictionary, details.dictionary) XCTAssertEqual(expectedSettingResult.pagination.maxTotalHits, details.pagination?.maxTotalHits) } else { XCTFail("details should exists in details field of task") From 48cfde489b84bf1dd1ee53622a4b1107bdfdc310 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:43:10 +0100 Subject: [PATCH 08/10] Update Unit Tests with some Hygiene Changes Throwing tests are better than force unwrapping as it allows the test runner to complete and prepare a proper report. Removed many instances of force unwrapping by using a better data initialiser. --- .../MeiliSearchUnitTests/SettingsTests.swift | 202 +++++++++--------- 1 file changed, 102 insertions(+), 100 deletions(-) diff --git a/Tests/MeiliSearchUnitTests/SettingsTests.swift b/Tests/MeiliSearchUnitTests/SettingsTests.swift index dd7dbec1..5299818a 100644 --- a/Tests/MeiliSearchUnitTests/SettingsTests.swift +++ b/Tests/MeiliSearchUnitTests/SettingsTests.swift @@ -1,9 +1,7 @@ @testable import MeiliSearch import XCTest -// swiftlint:disable force_unwrapping // swiftlint:disable force_cast -// swiftlint:disable force_try class SettingsTests: XCTestCase { private var client: MeiliSearch! private var index: Indexes! @@ -32,6 +30,12 @@ class SettingsTests: XCTestCase { "filterableAttributes": [], "sortableAttributes": [], "stopWords": [], + "separatorTokens": [], + "nonSeparatorTokens": [], + "dictionary": [], + "pagination": { + "maxTotalHits": 1000 + }, "synonyms": { "wolverine": ["xmen", "logan"], "logan": ["wolverine", "xmen"] @@ -39,17 +43,17 @@ class SettingsTests: XCTestCase { } """ - override func setUp() { - super.setUp() - client = try! MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) + override func setUpWithError() throws { + try super.setUpWithError() + client = try MeiliSearch(host: "http://localhost:7700", apiKey: "masterKey", session: session) index = self.client.index(self.uid) } // MARK: Settings - func testgetSettings() { + func testGetSettings() throws { // Prepare the mock server - let stubSetting: SettingResult = buildStubSettingResult(from: json) + let stubSetting: SettingResult = try buildStubSettingResult(from: json) session.pushData(json) // Start the test with the mocked server @@ -67,18 +71,18 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testupdateSettings() { + func testUpdateSettings() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let jsonData = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: jsonData) + let jsonData = Data(jsonString.utf8) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: jsonData) session.pushData(jsonString) - let setting: Setting = buildStubSetting(from: json) + let setting: Setting = try buildStubSetting(from: json) // Start the test with the mocked server let expectation = XCTestExpectation(description: "Update settings") @@ -96,15 +100,15 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetSettings() { + func testResetSettings() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let data: Data = jsonString.data(using: .utf8)! - let stubTask: TaskInfo = try! decoder.decode(TaskInfo.self, from: data) + let data: Data = Data(jsonString.utf8) + let stubTask: TaskInfo = try decoder.decode(TaskInfo.self, from: data) session.pushData(jsonString) // Start the test with the mocked server @@ -125,7 +129,7 @@ class SettingsTests: XCTestCase { // MARK: Synonyms - func testGetSynonyms() { + func testGetSynonyms() throws { let jsonString = """ { "wolverine": ["xmen", "logan"], @@ -135,8 +139,8 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let jsonData = jsonString.data(using: .utf8)! - let stubSynonyms: [String: [String]] = try! JSONSerialization.jsonObject( + let jsonData = Data(jsonString.utf8) + let stubSynonyms: [String: [String]] = try JSONSerialization.jsonObject( with: jsonData, options: []) as! [String: [String]] session.pushData(jsonString) @@ -157,16 +161,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateSynonyms() { + func testUpdateSynonyms() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) let json = """ @@ -176,8 +180,8 @@ class SettingsTests: XCTestCase { "wow": ["world of warcraft"] } """ - let jsonData = json.data(using: .utf8)! - let synonyms: [String: [String]] = try! JSONSerialization.jsonObject( + let jsonData = Data(json.utf8) + let synonyms: [String: [String]] = try JSONSerialization.jsonObject( with: jsonData, options: []) as! [String: [String]] // Start the test with the mocked server @@ -195,16 +199,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetSynonyms() { + func testResetSynonyms() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) // Start the test with the mocked server @@ -225,14 +229,14 @@ class SettingsTests: XCTestCase { // MARK: Stop words - func testGetStopWords() { + func testGetStopWords() throws { let jsonString = """ ["of", "the", "to"] """ // Prepare the mock server - let jsonData = jsonString.data(using: .utf8)! - let stubStopWords: [String] = try! JSONSerialization.jsonObject( + let jsonData = Data(jsonString.utf8) + let stubStopWords: [String] = try JSONSerialization.jsonObject( with: jsonData, options: []) as! [String] session.pushData(jsonString) @@ -252,23 +256,23 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateStopWords() { + func testUpdateStopWords() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) let json = """ ["of", "the", "to"] """ - let stopWords: [String] = try! JSONSerialization.jsonObject( - with: json.data(using: .utf8)!, options: []) as! [String] + let stopWords: [String] = try JSONSerialization.jsonObject( + with: Data(json.utf8), options: []) as! [String] // Start the test with the mocked server let expectation = XCTestExpectation(description: "Update stop-words") @@ -285,16 +289,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetStopWords() { + func testResetStopWords() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) @@ -316,7 +320,7 @@ class SettingsTests: XCTestCase { // MARK: Ranking rules - func testGetRankingRules() { + func testGetRankingRules() throws { let jsonString = """ [ "words", @@ -330,8 +334,8 @@ class SettingsTests: XCTestCase { """ // Prepare the mock server - let jsonData = jsonString.data(using: .utf8)! - let stubRakingRules: [String] = try! JSONSerialization.jsonObject( + let jsonData = Data(jsonString.utf8) + let stubRankingRules: [String] = try JSONSerialization.jsonObject( with: jsonData, options: []) as! [String] session.pushData(jsonString) @@ -341,7 +345,7 @@ class SettingsTests: XCTestCase { self.index.getRankingRules { result in switch result { case .success(let rankingRules): - XCTAssertEqual(stubRakingRules, rankingRules) + XCTAssertEqual(stubRankingRules, rankingRules) case .failure: XCTFail("Failed to get ranking rules") } @@ -351,22 +355,22 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateRankingRules() { + func testUpdateRankingRules() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) let json = """ ["of", "the", "to"] """ - let stopWords: [String] = try! JSONSerialization.jsonObject( - with: json.data(using: .utf8)!, options: []) as! [String] + let stopWords: [String] = try JSONSerialization.jsonObject( + with: Data(json.utf8), options: []) as! [String] // Start the test with the mocked server let expectation = XCTestExpectation(description: "Update ranking rules") @@ -384,16 +388,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetRankingRules() { + func testResetRankingRules() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) @@ -415,7 +419,7 @@ class SettingsTests: XCTestCase { // MARK: Distinct Attribute - func testGetDistinctAttribute() { + func testGetDistinctAttribute() throws { let stubDistinctAttribute: String = """ "movie_id" """ @@ -429,7 +433,7 @@ class SettingsTests: XCTestCase { self.index.getDistinctAttribute { result in switch result { case .success(let distinctAttribute): - XCTAssertEqual("movie_id", distinctAttribute!) + XCTAssertEqual("movie_id", distinctAttribute) case .failure: XCTFail("Failed to get distinct attribute") } @@ -439,16 +443,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateDistinctAttribute() { + func testUpdateDistinctAttribute() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) let distinctAttribute = "movie_id" @@ -468,16 +472,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetDistinctAttribute() { + func testResetDistinctAttribute() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) // Start the test with the mocked server @@ -498,14 +502,14 @@ class SettingsTests: XCTestCase { // MARK: Searchable Attribute - func testGetSearchableAttributes() { + func testGetSearchableAttributes() throws { let jsonString = """ ["title", "description", "uid"] """ // Prepare the mock server - let jsonData = jsonString.data(using: .utf8)! - let stubSearchableAttribute: [String] = try! JSONSerialization.jsonObject( + let jsonData = Data(jsonString.utf8) + let stubSearchableAttribute: [String] = try JSONSerialization.jsonObject( with: jsonData, options: []) as! [String] session.pushData(jsonString) @@ -525,23 +529,23 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateSearchableAttributes() { + func testUpdateSearchableAttributes() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) let json = """ ["title", "description", "uid"] """ - let jsonData = json.data(using: .utf8)! - let searchableAttribute: [String] = try! JSONSerialization.jsonObject( + let jsonData = Data(json.utf8) + let searchableAttribute: [String] = try JSONSerialization.jsonObject( with: jsonData, options: []) as! [String] // Start the test with the mocked server @@ -559,16 +563,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetSearchableAttributes() { + func testResetSearchableAttributes() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) // Start the test with the mocked server @@ -588,14 +592,14 @@ class SettingsTests: XCTestCase { // MARK: Displayed Attributes - func testGetDisplayedAttributes() { + func testGetDisplayedAttributes() throws { let jsonString = """ ["title", "description", "release_date", "rank", "poster"] """ // Prepare the mock server - let jsonData = jsonString.data(using: .utf8)! - let stubDisplayedAttributes: [String] = try! JSONSerialization.jsonObject( + let jsonData = Data(jsonString.utf8) + let stubDisplayedAttributes: [String] = try JSONSerialization.jsonObject( with: jsonData, options: []) as! [String] session.pushData(jsonString) @@ -615,16 +619,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateDisplayedAttributes() { + func testUpdateDisplayedAttributes() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) @@ -632,8 +636,8 @@ class SettingsTests: XCTestCase { ["title", "description", "release_date", "rank", "poster"] """ - let jsonData = json.data(using: .utf8)! - let displayedAttributes: [String] = try! JSONSerialization.jsonObject( + let jsonData = Data(json.utf8) + let displayedAttributes: [String] = try JSONSerialization.jsonObject( with: jsonData, options: []) as! [String] // Start the test with the mocked server @@ -652,16 +656,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetDisplayedAttributes() { + func testResetDisplayedAttributes() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) // Start the test with the mocked server @@ -682,7 +686,7 @@ class SettingsTests: XCTestCase { // MARK: Filterable Attributes - func testGetFilterableAttributes() { + func testGetFilterableAttributes() throws { let jsonString = """ ["genre", "director"] """ @@ -706,16 +710,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateFilterableAttributes() { + func testUpdateFilterableAttributes() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) let attributes: [String] = ["genre", "director"] @@ -736,16 +740,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetFilterableAttributes() { + func testResetFilterableAttributes() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) @@ -767,7 +771,7 @@ class SettingsTests: XCTestCase { // MARK: Filterable Attributes - func testGetSortableAttributes() { + func testGetSortableAttributes() throws { let jsonString = """ ["genre", "director"] """ @@ -791,16 +795,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testUpdateSortableAttributes() { + func testUpdateSortableAttributes() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) let attributes: [String] = ["genre", "director"] @@ -821,16 +825,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - func testResetSortableAttributes() { + func testResetSortableAttributes() throws { let jsonString = """ {"taskUid":0,"indexUid":"movies_test","status":"enqueued","type":"settingsUpdate","enqueuedAt":"2022-07-27T19:03:50.494232841Z"} """ // Prepare the mock server let decoder = JSONDecoder() - let stubTask: TaskInfo = try! decoder.decode( + let stubTask: TaskInfo = try decoder.decode( TaskInfo.self, - from: jsonString.data(using: .utf8)!) + from: Data(jsonString.utf8)) session.pushData(jsonString) // Start the test with the mocked server @@ -849,18 +853,16 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - private func buildStubSetting(from json: String) -> Setting { - let data = json.data(using: .utf8)! + private func buildStubSetting(from json: String) throws -> Setting { + let data = Data(json.utf8) let decoder = JSONDecoder() - return try! decoder.decode(Setting.self, from: data) + return try decoder.decode(Setting.self, from: data) } - private func buildStubSettingResult(from json: String) -> SettingResult { - let data = json.data(using: .utf8)! + private func buildStubSettingResult(from json: String) throws -> SettingResult { + let data = Data(json.utf8) let decoder = JSONDecoder() - return try! decoder.decode(SettingResult.self, from: data) + return try decoder.decode(SettingResult.self, from: data) } } -// swiftlint:enable force_unwrapping // swiftlint:enable force_cast -// swiftlint:enable force_try From 9e45cb9d0351cd6c584c4f665b6675abaad20964 Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:43:36 +0100 Subject: [PATCH 09/10] Run SwiftLint to Fix Whitespace --- Sources/MeiliSearch/Indexes.swift | 4 ++-- Sources/MeiliSearch/Model/FacetStats.swift | 2 +- Sources/MeiliSearch/Model/SearchResult.swift | 2 +- Sources/MeiliSearch/Settings.swift | 14 +++++++------- .../MeiliSearchIntegrationTests/SearchTests.swift | 2 +- .../SettingsTests.swift | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/MeiliSearch/Indexes.swift b/Sources/MeiliSearch/Indexes.swift index e5284e0f..97e625dc 100755 --- a/Sources/MeiliSearch/Indexes.swift +++ b/Sources/MeiliSearch/Indexes.swift @@ -867,7 +867,7 @@ public struct Indexes { _ completion: @escaping (Result) -> Void) { self.settings.resetSortableAttributes(self.uid, completion) } - + // MARK: Separator Tokens /** @@ -947,7 +947,7 @@ public struct Indexes { _ completion: @escaping (Result) -> Void) { self.settings.resetNonSeparatorTokens(self.uid, completion) } - + // MARK: Dictionary /** diff --git a/Sources/MeiliSearch/Model/FacetStats.swift b/Sources/MeiliSearch/Model/FacetStats.swift index 08971668..997af80f 100644 --- a/Sources/MeiliSearch/Model/FacetStats.swift +++ b/Sources/MeiliSearch/Model/FacetStats.swift @@ -8,7 +8,7 @@ public struct FacetStats: Codable, Equatable { /// The minimum value found in the given facet public let min: Double - + /// The maximum value found in the given facet public let max: Double } diff --git a/Sources/MeiliSearch/Model/SearchResult.swift b/Sources/MeiliSearch/Model/SearchResult.swift index c1d92124..be576120 100644 --- a/Sources/MeiliSearch/Model/SearchResult.swift +++ b/Sources/MeiliSearch/Model/SearchResult.swift @@ -15,7 +15,7 @@ public class Searchable: Equatable, Codable where T: Codable, T: Equatable { /// Distribution of the given facets. public var facetDistribution: [String: [String: Int]]? - + /// Maximum & minimum stats of a numeric facet. public var facetStats: [String: FacetStats]? diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 6114b883..92c1937c 100644 --- a/Sources/MeiliSearch/Settings.swift +++ b/Sources/MeiliSearch/Settings.swift @@ -22,7 +22,7 @@ struct Settings { func get( _ uid: String, _ completion: @escaping (Result) -> Void) { - + getSetting(uid: uid, key: nil, completion: completion) } @@ -92,7 +92,7 @@ struct Settings { func getStopWords( _ uid: String, _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { - + getSetting(uid: uid, key: "stop-words", completion: completion) } @@ -116,7 +116,7 @@ struct Settings { func getRankingRules( _ uid: String, _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { - + getSetting(uid: uid, key: "ranking-rules", completion: completion) } @@ -254,7 +254,7 @@ struct Settings { resetSetting(uid: uid, key: "sortable-attributes", completion: completion) } - + // MARK: Separator Tokens func getSeparatorTokens( @@ -278,7 +278,7 @@ struct Settings { resetSetting(uid: uid, key: "separator-tokens", completion: completion) } - + // MARK: Non-Separator Tokens func getNonSeparatorTokens( @@ -302,7 +302,7 @@ struct Settings { resetSetting(uid: uid, key: "non-separator-tokens", completion: completion) } - + // MARK: Dictionary func getDictionary( @@ -326,7 +326,7 @@ struct Settings { resetSetting(uid: uid, key: "dictionary", completion: completion) } - + // MARK: Pagination Preferences func getPaginationSettings( diff --git a/Tests/MeiliSearchIntegrationTests/SearchTests.swift b/Tests/MeiliSearchIntegrationTests/SearchTests.swift index 7cbe26e4..60d84972 100644 --- a/Tests/MeiliSearchIntegrationTests/SearchTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SearchTests.swift @@ -1018,7 +1018,7 @@ class SearchTests: XCTestCase { XCTAssertEqual(documents.query, query) XCTAssertEqual(result.limit, limit) XCTAssertEqual(documents.hits.count, limit) - + XCTAssertEqual(documents.facetStats?["id"]?.min, 1) XCTAssertEqual(documents.facetStats?["id"]?.max, 1844) diff --git a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift index 769ed023..d28d51a0 100644 --- a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -524,7 +524,7 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - + // MARK: Separator Tokens func testGetSeparatorTokens() { @@ -615,7 +615,7 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - + // MARK: Non Separator Tokens func testGetNonSeparatorTokens() { @@ -706,7 +706,7 @@ class SettingsTests: XCTestCase { self.wait(for: [expectation], timeout: TESTS_TIME_OUT) } - + // MARK: Dictionary func testGetDictionary() { From d15cc4bb755e5227e4ad0380cbbe23b00d347fad Mon Sep 17 00:00:00 2001 From: James Sherlock <15193942+Sherlouk@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:50:49 +0100 Subject: [PATCH 10/10] Update .code-samples.meilisearch.yaml --- .code-samples.meilisearch.yaml | 46 ++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index b5f6d3de..cb697362 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -168,8 +168,44 @@ async_guide_canceled_by: |- print(error) } } +get_dictionary_1: |- + client.index("books").getDictionary { result in + // handle result + } +update_dictionary_1: |- + client.index("books").updateDictionary(["J. R. R.", "W. E. B."]) { result in + // handle result + } +reset_dictionary_1: |- + client.index("books").resetDictionary { result in + // handle result + } +get_separator_tokens_1: |- + client.index("books").getSeparatorTokens { result in + // handle result + } +update_separator_tokens_1: |- + client.index("books").updateSeparatorTokens(["|", "…"]) { result in + // handle result + } +reset_separator_tokens_1: |- + client.index("books").resetSeparatorTokens { result in + // handle result + } +get_non_separator_tokens_1: |- + client.index("books").getNonSeparatorTokens { result in + // handle result + } +update_non_separator_tokens_1: |- + client.index("books").updateNonSeparatorTokens(["@", "#"]) { result in + // handle result + } +reset_non_separator_tokens_1: |- + client.index("books").resetNonSeparatorTokens { result in + // handle result + } search_parameter_guide_hitsperpage_1: |- - let searchParameters = SearchParameters.query("", hitsPerPage: 15) + let searchParameters = SearchParameters(query: "", hitsPerPage: 15) client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in switch result { case .success(let searchResult): @@ -179,7 +215,7 @@ search_parameter_guide_hitsperpage_1: |- } } search_parameter_guide_page_1: |- - let searchParameters = SearchParameters.query("", page: 15) + let searchParameters = SearchParameters(query: "", page: 15) client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in switch result { case .success(let searchResult): @@ -362,7 +398,7 @@ delete_documents_1: |- } } search_post_1: |- - let searchParameters = SearchParameters.query("American ninja") + let searchParameters = SearchParameters(query: "American ninja") client.index("movies").search(searchParameters) { (result: Result, Swift.Error>) in switch result { case .success(let searchResult): @@ -458,7 +494,7 @@ delete_a_key_1: |- security_guide_search_key_1: |- client = try MeiliSearch(host: "http://localhost:7700", apiKey: "apiKey") client.index("patient_medical_records") - .search(SearchParameters.query("")) { (result: Result, Swift.Error>) in + .search(SearchParameters(query: "")) { (result: Result, Swift.Error>) in switch result { case .success(let searchResult): print(searchResult) @@ -1454,7 +1490,7 @@ geosearch_guide_filter_usage_2: |- } geosearch_guide_filter_usage_3: |- let searchParameters = SearchParameters( - filter: '_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])' + filter: "_geoBoundingBox([45.494181, 9.214024], [45.449484, 9.179175])" ) client.index("restaurants").search(searchParameters) { (result: Result, Swift.Error>) in switch result {