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 { diff --git a/Sources/MeiliSearch/Indexes.swift b/Sources/MeiliSearch/Indexes.swift index cc291ffc..97e625dc 100755 --- a/Sources/MeiliSearch/Indexes.swift +++ b/Sources/MeiliSearch/Indexes.swift @@ -867,6 +867,167 @@ 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 + + /** + 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/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/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/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/Model/Setting.swift b/Sources/MeiliSearch/Model/Setting.swift index 8f52c578..a5fe4b04 100644 --- a/Sources/MeiliSearch/Model/Setting.swift +++ b/Sources/MeiliSearch/Model/Setting.swift @@ -33,6 +33,18 @@ 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? + // MARK: Initializers public init( @@ -43,7 +55,11 @@ public struct Setting: Codable, Equatable { synonyms: [String: [String]]? = nil, distinctAttribute: String? = nil, filterableAttributes: [String]? = nil, - sortableAttributes: [String]? = nil + sortableAttributes: [String]? = nil, + separatorTokens: [String]? = nil, + nonSeparatorTokens: [String]? = nil, + dictionary: [String]? = nil, + pagination: Pagination? = nil ) { self.rankingRules = rankingRules self.searchableAttributes = searchableAttributes @@ -53,5 +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 168acc4c..2ce7fcc1 100644 --- a/Sources/MeiliSearch/Model/SettingResult.swift +++ b/Sources/MeiliSearch/Model/SettingResult.swift @@ -27,4 +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 405c9522..476c87d3 100644 --- a/Sources/MeiliSearch/Model/Task.swift +++ b/Sources/MeiliSearch/Model/Task.swift @@ -76,6 +76,18 @@ 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? + } /// Error information in case of failed update. public let error: MeiliSearch.MSErrorResponse? diff --git a/Sources/MeiliSearch/Settings.swift b/Sources/MeiliSearch/Settings.swift index 6beef90c..92c1937c 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( @@ -54,6 +39,7 @@ struct Settings { 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): @@ -74,23 +60,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 @@ -99,23 +69,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( @@ -123,49 +77,14 @@ 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( _ 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 @@ -173,23 +92,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( @@ -197,49 +101,14 @@ 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( _ 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 @@ -247,23 +116,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( @@ -271,50 +125,14 @@ 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( _ 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 @@ -323,23 +141,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( @@ -347,50 +149,14 @@ 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( _ 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 @@ -399,23 +165,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( @@ -423,49 +173,14 @@ 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( _ 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 @@ -474,23 +189,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( @@ -498,49 +197,14 @@ 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( _ 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 @@ -549,23 +213,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( @@ -573,15 +221,136 @@ struct Settings { _ attributes: [String], _ completion: @escaping (Result) -> Void) { + updateSetting(uid: uid, key: "filterable-attributes", data: attributes, completion: completion) + } + + func resetFilterableAttributes( + _ uid: String, + _ completion: @escaping (Result) -> Void) { + + resetSetting(uid: uid, key: "filterable-attributes", completion: completion) + } + + // MARK: Sortable Attributes + + func getSortableAttributes( + _ uid: String, + _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { + + getSetting(uid: uid, key: "sortable-attributes", completion: completion) + } + + func updateSortableAttributes( + _ uid: String, + _ attributes: [String], + _ completion: @escaping (Result) -> Void) { + + updateSetting(uid: uid, key: "sortable-attributes", data: attributes, completion: completion) + } + + func resetSortableAttributes( + _ uid: String, + _ completion: @escaping (Result) -> Void) { + + 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: "separator-tokens", data: attributes, completion: completion) + } + + func resetSeparatorTokens( + _ uid: String, + _ completion: @escaping (Result) -> Void) { + + resetSetting(uid: uid, key: "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( + _ 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 JSONSerialization.data(withJSONObject: attributes, options: []) + data = try JSONEncoder().encode(pagination) } catch { completion(.failure(error)) return } - self.request.put(api: "/indexes/\(uid)/settings/filterable-attributes", data) { result in + // 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 { @@ -596,36 +365,23 @@ struct Settings { } } - func resetFilterableAttributes( + func resetPaginationSettings( _ 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: "pagination", completion: completion) } - // MARK: Sortable Attributes - - func getSortableAttributes( - _ uid: String, - _ completion: @escaping (Result<[String], Swift.Error>) -> Void) { + // MARK: Reusable Requests - self.request.get(api: "/indexes/\(uid)/settings/sortable-attributes") { result in + 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 { @@ -633,7 +389,7 @@ struct Settings { return } do { - let array: [String] = try Constants.customJSONDecoder.decode([String].self, from: data) + let array: ResponseType = try Constants.customJSONDecoder.decode(ResponseType.self, from: data) completion(.success(array)) } catch { completion(.failure(error)) @@ -644,20 +400,23 @@ struct Settings { } } - func updateSortableAttributes( - _ uid: String, - _ attributes: [String], - _ completion: @escaping (Result) -> Void) { - - let data: Data + private func updateSetting( + uid: String, + key: String?, + data: Encodable, + completion: @escaping (Result) -> Void + ) { + let body: Data do { - data = try JSONSerialization.data(withJSONObject: attributes, options: []) + body = try JSONEncoder().encode(data) } catch { completion(.failure(error)) return } - self.request.put(api: "/indexes/\(uid)/settings/sortable-attributes", data) { result in + // 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 { @@ -672,11 +431,14 @@ struct Settings { } } - func resetSortableAttributes( - _ uid: String, - _ completion: @escaping (Result) -> Void) { - - self.request.delete(api: "/indexes/\(uid)/settings/sortable-attributes") { result in + 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 { 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 836ff2ea..d28d51a0 100644 --- a/Tests/MeiliSearchIntegrationTests/SettingsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/SettingsTests.swift @@ -25,8 +25,12 @@ 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) private var defaultGlobalSettings: Setting? private var defaultGlobalReturnedSettings: SettingResult? @@ -60,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( @@ -71,7 +79,11 @@ class SettingsTests: XCTestCase { synonyms: self.defaultSynonyms, distinctAttribute: self.defaultDistinctAttribute, filterableAttributes: self.defaultFilterableAttributes, - sortableAttributes: self.defaultSortableAttributes + sortableAttributes: self.defaultSortableAttributes, + separatorTokens: self.defaultSeparatorTokens, + nonSeparatorTokens: self.defaultNonSeparatorTokens, + dictionary: self.defaultDictionary, + pagination: self.defaultPagination ) } @@ -513,6 +525,366 @@ 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 + + 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 +1219,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 +1237,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 +1256,11 @@ class SettingsTests: XCTestCase { synonyms: [:], distinctAttribute: nil, filterableAttributes: [], - sortableAttributes: ["title"] + sortableAttributes: ["title"], + separatorTokens: [], + nonSeparatorTokens: [], + dictionary: [], + pagination: .init(maxTotalHits: 1000) ) let expectation = XCTestExpectation(description: "Update settings") @@ -950,7 +1326,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 +1337,12 @@ class SettingsTests: XCTestCase { synonyms: nil, distinctAttribute: nil, filterableAttributes: ["title"], - sortableAttributes: ["title"]) + sortableAttributes: ["title"], + separatorTokens: ["&"], + nonSeparatorTokens: ["#"], + dictionary: ["J.K"], + pagination: .init(maxTotalHits: 500) + ) let expectedSettingResult = SettingResult( rankingRules: ["words", "typo", "proximity", "attribute", "sort", "exactness"], @@ -971,7 +1352,12 @@ class SettingsTests: XCTestCase { synonyms: [:], distinctAttribute: nil, filterableAttributes: ["title"], - sortableAttributes: ["title"]) + sortableAttributes: ["title"], + separatorTokens: ["&"], + nonSeparatorTokens: ["#"], + dictionary: ["J.K"], + pagination: .init(maxTotalHits: 500) + ) self.index.updateSettings(newSettings) { result in switch result { @@ -988,6 +1374,10 @@ 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") } @@ -1008,7 +1398,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 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