Skip to content

Commit c770bde

Browse files
authored
Try #54:
2 parents 47e6a57 + 7f3c1a4 commit c770bde

23 files changed

+1882
-131
lines changed

Sources/MeiliSearch/Client.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ public struct MeiliSearch {
6262
self.indexes.create(UID, completion)
6363
}
6464

65+
/**
66+
Get or create a new Index for the given `uid`.
67+
68+
- parameter UID: The unique identifier for the `Index` to be created.
69+
- parameter completion: The completion closure used to notify when the server
70+
completes the write request, it returns a `Result` object that contains `Index`
71+
value. If the request was sucessful or `Error` if a failure occured.
72+
*/
6573
public func getOrCreateIndex(
6674
UID: String,
6775
_ completion: @escaping (Result<Index, Swift.Error>) -> Void) {
@@ -526,7 +534,7 @@ public struct MeiliSearch {
526534
*/
527535
public func getDistinctAttribute(
528536
UID: String,
529-
_ completion: @escaping (Result<String, Swift.Error>) -> Void) {
537+
_ completion: @escaping (Result<String?, Swift.Error>) -> Void) {
530538
self.settings.getDistinctAttribute(UID, completion)
531539
}
532540

Sources/MeiliSearch/Constants.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ struct Constants {
55
static let customJSONDecoder: JSONDecoder = {
66
let decoder = JSONDecoder()
77
decoder.dateDecodingStrategy = .formatted(Formatter.iso8601)
8-
98
return decoder
109
}()
1110
}

Sources/MeiliSearch/Documents.swift

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,7 @@ struct Documents {
209209
_ customDecoder: JSONDecoder? = nil,
210210
completion: (Result<T, Swift.Error>) -> Void) {
211211
do {
212-
213-
let decoder: JSONDecoder
214-
if let customDecoder: JSONDecoder = customDecoder {
215-
decoder = customDecoder
216-
} else {
217-
decoder = Constants.customJSONDecoder
218-
}
219-
212+
let decoder: JSONDecoder = customDecoder ?? Constants.customJSONDecoder
220213
let value: T = try decoder.decode(T.self, from: data)
221214
completion(.success(value))
222215
} catch {

Sources/MeiliSearch/Indexes.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ struct Indexes {
1212
self.request = request
1313
}
1414

15+
// MARK: Functions
16+
1517
func get(
1618
_ UID: String,
1719
_ completion: @escaping (Result<Index, Swift.Error>) -> Void) {
@@ -162,6 +164,8 @@ struct Indexes {
162164

163165
}
164166

167+
// MARK: Codable
168+
165169
private static func decodeJSON(
166170
_ data: Data,
167171
_ completion: (Result<Index, Swift.Error>) -> Void) {
@@ -196,9 +200,18 @@ struct UpdateIndexPayload: Codable {
196200
let primaryKey: String
197201
}
198202

203+
/**
204+
Error type for all functions included in the `Indexes` struct.
205+
*/
199206
public enum CreateError: Swift.Error, Equatable {
207+
208+
// MARK: Values
209+
210+
/// Case the `Index` already exists in the Meilisearch instance, this error will return.
200211
case indexAlreadyExists
201212

213+
// MARK: Codable
214+
202215
static func decode(_ error: MSError) -> Swift.Error {
203216

204217
let underlyingError: NSError = error.underlying as NSError

Sources/MeiliSearch/Model/Key.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import Foundation
66
*/
77
public struct Key: Codable, Equatable {
88

9-
// MARK: Properties
9+
// MARK: Properties
1010

11-
///Private key used to access a determined set of API routes.
12-
public let `private`: String
11+
///Private key used to access a determined set of API routes.
12+
public let `private`: String
1313

14-
///Public key used to access a determined set of API routes.
15-
public let `public`: String
14+
///Public key used to access a determined set of API routes.
15+
public let `public`: String
1616

1717
}

Sources/MeiliSearch/Model/SearchParameters.swift

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ public struct SearchParameters: Codable, Equatable {
1212
/// Query string (mandatory).
1313
public let query: String
1414

15-
/// Number of documents to skip.
16-
public let offset: Int
17-
1815
/// Number of documents to take.
1916
public let limit: Int
2017

18+
/// Number of documents to skip.
19+
public let offset: Int
20+
2121
/// Document attributes to show.
2222
public let attributesToRetrieve: [String]?
2323

@@ -34,7 +34,6 @@ public struct SearchParameters: Codable, Equatable {
3434
public let filters: String?
3535

3636
/// Select which attribute has to be filtered, useful when you need to narrow down the results of the filter.
37-
//TODO: Migrate to FacetFilter object
3837
public let facetFilters: [[String]]?
3938

4039
/// Retrieve the count of matching terms for each facets.
@@ -47,11 +46,11 @@ public struct SearchParameters: Codable, Equatable {
4746

4847
init(
4948
query: String,
50-
offset: Int = 0,
51-
limit: Int = 20,
49+
offset: Int = Default.offset.rawValue,
50+
limit: Int = Default.limit.rawValue,
5251
attributesToRetrieve: [String]? = nil,
5352
attributesToCrop: [String] = [],
54-
cropLength: Int = 200,
53+
cropLength: Int = Default.cropLength.rawValue,
5554
attributesToHighlight: [String] = [],
5655
filters: String? = nil,
5756
facetFilters: [[String]]? = nil,
@@ -82,15 +81,70 @@ public struct SearchParameters: Codable, Equatable {
8281
SearchParameters(query: value)
8382
}
8483

85-
private func commaRepresentation(_ array: [String]) -> String {
86-
array.joined(separator: ",")
84+
// MARK: Codable Keys
85+
86+
enum CodingKeys: String, CodingKey {
87+
case query = "q"
88+
case offset
89+
case limit
90+
case attributesToRetrieve
91+
case attributesToCrop
92+
case cropLength
93+
case attributesToHighlight
94+
case filters
95+
case facetFilters
96+
case facetsDistribution
97+
case matches
8798
}
8899

89-
private func commaRepresentationEscaped(_ array: [String]) -> String {
90-
var value: String = "["
91-
value += array.map({ string in "\"\(string)\"" }).joined(separator: ",")
92-
value += "]"
93-
return value
100+
// MARK: Default value for keys
101+
102+
fileprivate enum Default: Int {
103+
case offset = 0
104+
case limit = 20
105+
case cropLength = 200
106+
}
107+
108+
}
109+
110+
extension SearchParameters {
111+
112+
// MARK: Codable
113+
114+
/// Encodes the `SearchParameters` to a JSON payload, removing any non necessary implicit parameter.
115+
public func encode(to encoder: Encoder) throws {
116+
var container = encoder.container(keyedBy: CodingKeys.self)
117+
try container.encode(query, forKey: .query)
118+
if limit != Default.limit.rawValue {
119+
try container.encode(limit, forKey: .limit)
120+
}
121+
if offset != Default.offset.rawValue {
122+
try container.encode(offset, forKey: .offset)
123+
}
124+
if let attributesToRetrieve: [String] = self.attributesToRetrieve, !attributesToRetrieve.isEmpty {
125+
try container.encode(attributesToRetrieve, forKey: .attributesToRetrieve)
126+
}
127+
if !attributesToCrop.isEmpty {
128+
try container.encode(attributesToCrop, forKey: .attributesToCrop)
129+
}
130+
if cropLength != Default.cropLength.rawValue {
131+
try container.encode(cropLength, forKey: .cropLength)
132+
}
133+
if !attributesToHighlight.isEmpty {
134+
try container.encode(attributesToHighlight, forKey: .attributesToHighlight)
135+
}
136+
if let filters: String = self.filters, !filters.isEmpty {
137+
try container.encode(filters, forKey: .filters)
138+
}
139+
if let facetFilters: [[String]] = self.facetFilters {
140+
try container.encode(facetFilters, forKey: .facetFilters)
141+
}
142+
if let facetsDistribution = self.facetsDistribution, !facetsDistribution.isEmpty {
143+
try container.encode(facetsDistribution, forKey: .facetsDistribution)
144+
}
145+
if matches {
146+
try container.encode(matches, forKey: .matches)
147+
}
94148
}
95149

96150
}

Sources/MeiliSearch/Model/SearchResult.swift

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import Foundation
22

33
/**
4-
`SearchResult` instances represent result of a search.
4+
`SearchResult` instances represent the result of a search.
5+
Requires that the value `T` conforms to the `Codable` and `Equatable` protocols.
56
*/
67
public struct SearchResult<T>: Codable, Equatable where T: Codable, T: Equatable {
78

@@ -16,10 +17,71 @@ public struct SearchResult<T>: Codable, Equatable where T: Codable, T: Equatable
1617
/// Number of documents taken.
1718
public let limit: Int
1819

20+
/// Total number of matches,
21+
public let nbHits: Int
22+
23+
/// Whether `nbHits` is exhaustive.
24+
public let exhaustiveNbHits: Bool?
25+
26+
/// Distribution of the given facets.
27+
public let facetsDistribution: [String: [String: Int]]?
28+
29+
/// Whether facetDistribution is exhaustive.
30+
public let exhaustiveFacetsCount: Bool?
31+
1932
/// Time, in milliseconds, to process the query.
2033
public let processingTimeMs: Int?
2134

2235
/// Query string from the search.
2336
public let query: String
2437

38+
// MARK: Dynamic Codable
39+
40+
/**
41+
`StringKey` internally used to decode the dynamic JSON used in the `facetsDistribution` value.
42+
*/
43+
fileprivate struct StringKey: CodingKey {
44+
var stringValue: String
45+
init?(stringValue: String) {
46+
self.stringValue = stringValue
47+
}
48+
var intValue: Int? { nil }
49+
init?(intValue: Int) { nil }
50+
}
51+
52+
}
53+
54+
extension SearchResult {
55+
56+
/// Decodes the JSON to a `SearchParameters` object, sets the default value if required.
57+
public init(from decoder: Decoder) throws {
58+
let values = try decoder.container(keyedBy: CodingKeys.self)
59+
hits = (try values.decodeIfPresent([T].self, forKey: .hits)) ?? []
60+
offset = try values.decode(Int.self, forKey: .offset)
61+
limit = try values.decode(Int.self, forKey: .limit)
62+
nbHits = try values.decode(Int.self, forKey: .nbHits)
63+
exhaustiveNbHits = try values.decodeIfPresent(Bool.self, forKey: .exhaustiveNbHits)
64+
exhaustiveFacetsCount = try values.decodeIfPresent(Bool.self, forKey: .exhaustiveFacetsCount)
65+
processingTimeMs = try values.decodeIfPresent(Int.self, forKey: .processingTimeMs)
66+
query = try values.decode(String.self, forKey: .query)
67+
68+
//Behemoth ancient code below needed to dynamically decode the JSON
69+
if values.contains(.facetsDistribution) {
70+
let nested: KeyedDecodingContainer<StringKey> = try values.nestedContainer(keyedBy: StringKey.self, forKey: .facetsDistribution)
71+
var dic: [String: [String: Int]] = Dictionary(minimumCapacity: nested.allKeys.count)
72+
try nested.allKeys.forEach { (key: KeyedDecodingContainer<StringKey>.Key) in
73+
let facet: KeyedDecodingContainer<StringKey> = try nested.nestedContainer(keyedBy: StringKey.self, forKey: key)
74+
var inner: [String: Int] = Dictionary(minimumCapacity: facet.allKeys.count)
75+
try facet.allKeys.forEach { (innerKey: KeyedDecodingContainer<StringKey>.Key) in
76+
inner[innerKey.stringValue] = try facet.decode(Int.self, forKey: innerKey)
77+
}
78+
dic[key.stringValue] = inner
79+
}
80+
facetsDistribution = dic
81+
} else {
82+
facetsDistribution = nil
83+
}
84+
85+
}
86+
2587
}

Sources/MeiliSearch/Model/Setting.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,26 @@ public struct Setting: Codable, Equatable {
2323
/// List of synonyms and its values for a given `Index`.
2424
public let synonyms: [String: [String]]
2525

26+
/// Optional distinct attribute set for a given `Index`.
27+
public let distinctAttribute: String?
28+
29+
/// List of attributes used for the faceting
30+
public let attributesForFaceting: [String]
31+
32+
}
33+
34+
extension Setting {
35+
2636
/// Tries to decode the JSON object to Setting object.
2737
public init(from decoder: Decoder) throws {
2838
let values = try? decoder.container(keyedBy: CodingKeys.self)
29-
3039
rankingRules = (try? values?.decodeIfPresent([String].self, forKey: .rankingRules)) ?? []
3140
searchableAttributes = (try? values?.decodeIfPresent([String].self, forKey: .searchableAttributes)) ?? ["*"]
3241
displayedAttributes = (try? values?.decodeIfPresent([String].self, forKey: .displayedAttributes)) ?? ["*"]
3342
stopWords = (try? values?.decodeIfPresent([String].self, forKey: .stopWords)) ?? []
3443
synonyms = (try? values?.decodeIfPresent([String: [String]].self, forKey: .synonyms)) ?? [:]
44+
distinctAttribute = try? values?.decodeIfPresent(String.self, forKey: .distinctAttribute)
45+
attributesForFaceting = (try? values?.decodeIfPresent([String].self, forKey: .attributesForFaceting)) ?? []
3546
}
47+
3648
}

Sources/MeiliSearch/Model/Update.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ public struct Update: Codable, Equatable {
2626
public let type: Type
2727

2828
///Duration of the update process.
29-
public let duration: TimeInterval
29+
public let duration: TimeInterval?
3030

3131
///Date when the update has been enqueued.
3232
public let enqueuedAt: Date
3333

3434
///Date when the update has been processed.
35-
public let processedAt: Date
35+
public let processedAt: Date?
3636

3737
///Typr of `Update`.
3838
public struct `Type`: Codable, Equatable {

0 commit comments

Comments
 (0)