From e1181db8c201c2528af315f68ed3fb508ddf14d9 Mon Sep 17 00:00:00 2001 From: Pedro Paulo de Amorim Date: Sun, 4 Oct 2020 19:55:57 +0100 Subject: [PATCH] Add support to [T: Codable] when adding documents --- Sources/MeiliSearch/Client.swift | 31 +++++++- Sources/MeiliSearch/Constants.swift | 7 ++ Sources/MeiliSearch/Documents.swift | 49 ++++++++++++ .../DocumentsTests.swift | 78 +++++++++++++++---- Tests/MeiliSearchUnitTests/ClientTests.swift | 5 +- .../MeiliSearchUnitTests/DocumentsTests.swift | 46 +++++++++++ 6 files changed, 199 insertions(+), 17 deletions(-) diff --git a/Sources/MeiliSearch/Client.swift b/Sources/MeiliSearch/Client.swift index 4e95dca8..44e15116 100755 --- a/Sources/MeiliSearch/Client.swift +++ b/Sources/MeiliSearch/Client.swift @@ -143,7 +143,36 @@ public struct MeiliSearch { For a partial update of the document see `updateDocument`. - parameter UID: The unique identifier for the Document's index to be found. - - parameter documents: The documents data (JSON) to be processed. + - parameter documents: The documents to be processed. + - parameter completion: The completion closure used to notify when the server + completes the update request, it returns a `Result` object that contains `Update` + value. If the request was sucessful or `Error` if a failure occured. + */ + public func addDocuments( + UID: String, + documents: [T], + encoder: JSONEncoder? = nil, + primaryKey: String?, + _ completion: @escaping (Result) -> Void) where T: Encodable { + self.documents.add( + UID, + documents, + encoder, + primaryKey, + completion) + } + + /** + Add a list of documents as data or replace them if they already exist. + + If you send an already existing document (same id) the whole existing document will + be overwritten by the new document. Fields previously in the document not present in + the new document are removed. + + For a partial update of the document see `updateDocument`. + + - parameter UID: The unique identifier for the Document's index to be found. + - parameter documents: The data to be processed. - parameter completion: The completion closure used to notify when the server completes the update request, it returns a `Result` object that contains `Update` value. If the request was sucessful or `Error` if a failure occured. diff --git a/Sources/MeiliSearch/Constants.swift b/Sources/MeiliSearch/Constants.swift index 481ff2f9..0739a50d 100644 --- a/Sources/MeiliSearch/Constants.swift +++ b/Sources/MeiliSearch/Constants.swift @@ -7,4 +7,11 @@ struct Constants { decoder.dateDecodingStrategy = .formatted(Formatter.iso8601) return decoder }() + + static let customJSONEecoder: JSONEncoder = { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .formatted(Formatter.iso8601) + return encoder + }() + } diff --git a/Sources/MeiliSearch/Documents.swift b/Sources/MeiliSearch/Documents.swift index b141b767..43c765bf 100755 --- a/Sources/MeiliSearch/Documents.swift +++ b/Sources/MeiliSearch/Documents.swift @@ -75,6 +75,7 @@ struct Documents { _ document: Data, _ primaryKey: String?, _ completion: @escaping (Result) -> Void) { + var query: String = "/indexes/\(UID)/documents" if let primaryKey: String = primaryKey { query += "?primaryKey=\(primaryKey)" @@ -94,6 +95,42 @@ struct Documents { } } + func add( + _ UID: String, + _ documents: [T], + _ encoder: JSONEncoder? = nil, + _ primaryKey: String?, + _ completion: @escaping (Result) -> Void) where T: Encodable { + + var query: String = "/indexes/\(UID)/documents" + if let primaryKey: String = primaryKey { + query += "?primaryKey=\(primaryKey)" + } + + let data: Data! + + switch encodeJSON(documents, encoder) { + case .success(let documentData): + data = documentData + case .failure(let error): + completion(.failure(error)) + return + } + + request.post(api: query, data) { result in + + switch result { + case .success(let data): + + Documents.decodeJSON(data, completion: completion) + + case .failure(let error): + completion(.failure(error)) + } + + } + } + func update( _ UID: String, _ document: Data, @@ -217,4 +254,16 @@ struct Documents { } } + private func encodeJSON( + _ documents: [T], + _ encoder: JSONEncoder?) -> Result { + do { + let data: Data = try (encoder ?? Constants.customJSONEecoder) + .encode(documents) + return .success(data) + } catch { + return .failure(error) + } + } + } diff --git a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift index 1a8ae2d9..5b903ace 100755 --- a/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift +++ b/Tests/MeiliSearchIntegrationTests/DocumentsTests.swift @@ -66,13 +66,12 @@ class DocumentsTests: XCTestCase { } func testAddAndGetDocuments() { - let documents: Data = try! JSONEncoder().encode(movies) let expectation = XCTestExpectation(description: "Add or replace Movies document") self.client.addDocuments( UID: self.uid, - documents: documents, + documents: movies, primaryKey: nil ) { result in @@ -112,6 +111,53 @@ class DocumentsTests: XCTestCase { } + func testAddDataAndGetDocuments() { + let documents: Data = try! JSONEncoder().encode(movies) + + let expectation = XCTestExpectation(description: "Add or replace Movies document") + + self.client.addDocuments( + UID: self.uid, + documents: documents, + primaryKey: nil + ) { result in + + switch result { + case .success(let update): + + XCTAssertEqual(Update(updateId: 0), update) + + Thread.sleep(forTimeInterval: 1.0) + + self.client.getDocuments( + UID: self.uid, + limit: 20 + ) { (result: Result<[Movie], Swift.Error>) in + + switch result { + case .success(let returnedMovies): + + movies.forEach { (movie: Movie) in + XCTAssertTrue(returnedMovies.contains(movie)) + } + + case .failure(let error): + print(error) + XCTFail() + } + + expectation.fulfill() + } + + case .failure(let error): + print(error) + XCTFail() + } + } + self.wait(for: [expectation], timeout: 5.0) + + } + func testGetOneDocumentAndFail() { let getExpectation = XCTestExpectation(description: "Get one document and fail") @@ -146,25 +192,26 @@ class DocumentsTests: XCTestCase { case .success(let update): + XCTAssertEqual(Update(updateId: 0), update) Thread.sleep(forTimeInterval: 1.0) - self.client.getDocument( - UID: self.uid, - identifier: "10" - ) { (result: Result) in + self.client.getDocument( + UID: self.uid, + identifier: "10" + ) { (result: Result) in - switch result { - case .success(let returnedMovie): - XCTAssertEqual(movie, returnedMovie) - case .failure(let error): - print(error) - XCTFail() - } - expectation.fulfill() + switch result { + case .success(let returnedMovie): + XCTAssertEqual(movie, returnedMovie) + case .failure(let error): + print(error) + XCTFail() + } + expectation.fulfill() - } + } case .failure(let error): print(error) @@ -391,6 +438,7 @@ class DocumentsTests: XCTestCase { static var allTests = [ ("testAddAndGetDocuments", testAddAndGetDocuments), + ("testAddDataAndGetDocuments", testAddDataAndGetDocuments), ("testGetOneDocumentAndFail", testGetOneDocumentAndFail), ("testAddAndGetOneDocuments", testAddAndGetOneDocuments), ("testUpdateAndGetDocuments", testUpdateAndGetDocuments), diff --git a/Tests/MeiliSearchUnitTests/ClientTests.swift b/Tests/MeiliSearchUnitTests/ClientTests.swift index 75e2f2ed..559eb5ce 100644 --- a/Tests/MeiliSearchUnitTests/ClientTests.swift +++ b/Tests/MeiliSearchUnitTests/ClientTests.swift @@ -3,8 +3,11 @@ import XCTest class ClientTests: XCTestCase { + private let session = MockURLSession() + func testValidHostURL() { - XCTAssertNotNil(try? MeiliSearch(Config(hostURL: "http://localhost:7700", apiKey: "masterKey"))) + session.pushEmpty(code: 200) + XCTAssertNotNil(try? MeiliSearch(Config.default(apiKey: "masterKey", session: session))) } func testEmptyHostURL() { diff --git a/Tests/MeiliSearchUnitTests/DocumentsTests.swift b/Tests/MeiliSearchUnitTests/DocumentsTests.swift index 66c00964..28002a6e 100755 --- a/Tests/MeiliSearchUnitTests/DocumentsTests.swift +++ b/Tests/MeiliSearchUnitTests/DocumentsTests.swift @@ -47,6 +47,51 @@ class DocumentsTests: XCTestCase { let uid: String = "Movies" + let movie = Movie( + id: 287947, + title: "Shazam", + overview: "A boy is given the ability to become an adult superhero in times of need with a single magic word.", + releaseDate: Date(timeIntervalSince1970: TimeInterval(1553299200))) + + let expectation = XCTestExpectation(description: "Add or replace Movies document") + + self.client.addDocuments( + UID: uid, + documents: [movie], + primaryKey: "") { result in + + switch result { + case .success(let update): + XCTAssertEqual(stubUpdate, update) + expectation.fulfill() + case .failure: + XCTFail("Failed to add or replace Movies document") + } + + } + + self.wait(for: [expectation], timeout: 1.0) + + } + + func testAddDataDocuments() { + + //Prepare the mock server + + let jsonString = """ + {"updateId":0} + """ + + let decoder: JSONDecoder = JSONDecoder() + let jsonData = jsonString.data(using: .utf8)! + let stubUpdate: Update = try! decoder.decode(Update.self, from: jsonData) + + session.pushData(jsonString, code: 202) + + // Start the test with the mocked server + + let uid: String = "Movies" + let documentJsonString = """ [{ "id": 287947, @@ -358,6 +403,7 @@ class DocumentsTests: XCTestCase { static var allTests = [ ("testAddDocuments", testAddDocuments), + ("testAddDataDocuments", testAddDataDocuments), ("testUpdateDocuments", testUpdateDocuments), ("testGetDocument", testGetDocument), ("testGetDocuments", testGetDocuments),