From b63f1cde0f2e98275cd362f7c52dded20750b06d Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Wed, 23 Aug 2023 01:08:29 +0300 Subject: [PATCH 01/63] swift-docc-plugin 1.3.0 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index d2d05c2..016478d 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. From aa936537d3713bd26f85c7fc92abd3c3a0e7f416 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Wed, 23 Aug 2023 01:10:55 +0300 Subject: [PATCH 02/63] async-http-client 1.19.0 --- Package.resolved | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Package.resolved b/Package.resolved index bd26029..ad4dea0 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/async-http-client.git", "state" : { - "revision" : "78db67e5bf4a8543075787f228e8920097319281", - "version" : "1.18.0" + "revision" : "16f7e62c08c6969899ce6cc277041e868364e5cf", + "version" : "1.19.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "32e8d724467f8fe623624570367e3d50c5638e46", - "version" : "1.5.2" + "revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed", + "version" : "1.5.3" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "6213ba7a06febe8fef60563a4a7d26a4085783cf", - "version" : "2.54.0" + "revision" : "cf281631ff10ec6111f2761052aa81896a83a007", + "version" : "2.58.0" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "e866a626e105042a6a72a870c88b4c531ba05f83", - "version" : "2.24.0" + "revision" : "320bd978cceb8e88c125dcbb774943a92f6286e9", + "version" : "2.25.0" } }, { @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "41f4098903878418537020075a4d8a6e20a0b182", - "version" : "1.17.0" + "revision" : "e7403c35ca6bb539a7ca353b91cc2d8ec0362d58", + "version" : "1.19.0" } } ], From 4788d8ac8dd16220297ddaf013735fe6f3edc086 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Wed, 23 Aug 2023 01:22:59 +0300 Subject: [PATCH 03/63] replaced deprecated .createNew with the new .singleton --- Sources/CouchDBClient/CouchDBClient.swift | 190 +++++++++++----------- 1 file changed, 95 insertions(+), 95 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index be73f6d..0b00432 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -153,19 +153,19 @@ public class CouchDBClient { public func getAllDBs(eventLoopGroup: EventLoopGroup? = nil) async throws -> [String] { try await authIfNeed(eventLoopGroup: eventLoopGroup) - let httpClient: HTTPClient - if let eventLoopGroup = eventLoopGroup { - httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) - } else { - httpClient = HTTPClient(eventLoopGroupProvider: .createNew) - } + let httpClient: HTTPClient + if let eventLoopGroup = eventLoopGroup { + httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + } else { + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + } defer { DispatchQueue.main.async { try? httpClient.syncShutdown() } } - + let url = buildUrl(path: "/_all_dbs") let request = try buildRequest(fromUrl: url, withMethod: .GET) @@ -204,7 +204,7 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) } defer { @@ -244,7 +244,7 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) } defer { @@ -283,62 +283,62 @@ public class CouchDBClient { } } - /// Delete DB. - /// - /// Example: - /// ```swift - /// try await couchDBClient.deleteDB("myDBName") - /// ``` - /// - /// - Parameters: - /// - dbName: DB name. - /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Request response. - @discardableResult public func deleteDB(_ dbName: String, eventLoopGroup: EventLoopGroup? = nil) async throws -> UpdateDBResponse { - try await authIfNeed(eventLoopGroup: eventLoopGroup) - - let httpClient: HTTPClient - if let eventLoopGroup = eventLoopGroup { - httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) - } else { - httpClient = HTTPClient(eventLoopGroupProvider: .createNew) - } - - defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() - } - } - - let url = buildUrl(path: "/\(dbName)") - - let request = try self.buildRequest(fromUrl: url, withMethod: .DELETE) - - let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() - - if response.status == .unauthorized { - throw CouchDBClientError.unauthorized - } - - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse - } - - let data = Data(bytes) - let decoder = JSONDecoder() - - do { - let decodedResponse = try decoder.decode(UpdateDBResponse.self, from: data) - return decodedResponse - } catch let parsingError { - if let couchdbError = try? decoder.decode(CouchDBError.self, from: data) { - throw CouchDBClientError.insertError(error: couchdbError) - } - throw parsingError - } - } + /// Delete DB. + /// + /// Example: + /// ```swift + /// try await couchDBClient.deleteDB("myDBName") + /// ``` + /// + /// - Parameters: + /// - dbName: DB name. + /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. + /// - Returns: Request response. + @discardableResult public func deleteDB(_ dbName: String, eventLoopGroup: EventLoopGroup? = nil) async throws -> UpdateDBResponse { + try await authIfNeed(eventLoopGroup: eventLoopGroup) + + let httpClient: HTTPClient + if let eventLoopGroup = eventLoopGroup { + httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + } else { + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + } + + defer { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } + } + + let url = buildUrl(path: "/\(dbName)") + + let request = try self.buildRequest(fromUrl: url, withMethod: .DELETE) + + let response = try await httpClient + .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) + .get() + + if response.status == .unauthorized { + throw CouchDBClientError.unauthorized + } + + guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { + throw CouchDBClientError.unknownResponse + } + + let data = Data(bytes) + let decoder = JSONDecoder() + + do { + let decodedResponse = try decoder.decode(UpdateDBResponse.self, from: data) + return decodedResponse + } catch let parsingError { + if let couchdbError = try? decoder.decode(CouchDBError.self, from: data) { + throw CouchDBClientError.insertError(error: couchdbError) + } + throw parsingError + } + } /// Get data from DB. /// @@ -404,7 +404,7 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) } defer { @@ -453,7 +453,7 @@ public class CouchDBClient { /// - queryItems: Request query items. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: An object or a struct (of generic type) parsed from JSON. - public func get (dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { + public func get (dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { let response = try await get(dbName: dbName, uri: uri, queryItems: queryItems, eventLoopGroup: eventLoopGroup) if response.status == .unauthorized { @@ -506,7 +506,7 @@ public class CouchDBClient { /// /// // encode document into JSON string /// let data = try encoder.encode(updatedData) - /// + /// /// let response = try await couchDBClient.update( /// dbName: testsDB, /// uri: doc._id!, @@ -530,7 +530,7 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) } defer { @@ -550,7 +550,7 @@ public class CouchDBClient { if response.status == .unauthorized { throw CouchDBClientError.unauthorized } - + guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { throw CouchDBClientError.unknownResponse } @@ -604,7 +604,7 @@ public class CouchDBClient { /// - doc: Document object/struct. Should confirm to ``CouchDBRepresentable`` and Codable protocols. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Update response. - public func update (dbName: String, doc: inout T, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil ) async throws { + public func update (dbName: String, doc: inout T, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil ) async throws { guard let id = doc._id else { throw CouchDBClientError.idMissing } guard doc._rev?.isEmpty == false else { throw CouchDBClientError.revMissing } @@ -666,7 +666,7 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) } defer { @@ -687,7 +687,7 @@ public class CouchDBClient { if response.status == .unauthorized { throw CouchDBClientError.unauthorized } - + guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { throw CouchDBClientError.unknownResponse } @@ -736,10 +736,10 @@ public class CouchDBClient { /// - dbName: DB name. /// - doc: Document object/struct. Should confirm to ``CouchDBRepresentable`` protocol. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - public func insert (dbName: String, doc: inout T, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil ) async throws { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = dateEncodingStrategy - let insertEncodeData = try encoder.encode(doc) + public func insert (dbName: String, doc: inout T, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil ) async throws { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = dateEncodingStrategy + let insertEncodeData = try encoder.encode(doc) let insertResponse = try await insert( dbName: dbName, @@ -762,7 +762,7 @@ public class CouchDBClient { /// ```swift /// let response = try await couchDBClient.delete(fromDb: "databaseName", uri: doc._id, rev: doc._rev) /// ``` - /// + /// /// - Parameters: /// - dbName: DB name. /// - uri: document uri (usually _id). @@ -774,7 +774,7 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) } defer { @@ -839,9 +839,9 @@ internal extension CouchDBClient { components.host = couchHost components.port = couchPort components.path = path - + components.queryItems = query.isEmpty ? nil : query - + if components.url?.absoluteString == nil { assertionFailure("url should not be nil") } @@ -858,22 +858,22 @@ internal extension CouchDBClient { if let authData = authData, let sessionCookieExpires = sessionCookieExpires, sessionCookieExpires > Date() { return authData } - - let httpClient: HTTPClient - if let eventLoopGroup = eventLoopGroup { - httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) - } else { - httpClient = HTTPClient(eventLoopGroupProvider: .createNew) - } - + + let httpClient: HTTPClient + if let eventLoopGroup = eventLoopGroup { + httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + } else { + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + } + defer { DispatchQueue.main.async { try? httpClient.syncShutdown() } } - + let url = buildUrl(path: "/_session") - + var request = try HTTPClient.Request(url:url, method: .POST) request.headers.add(name: "Content-Type", value: "application/x-www-form-urlencoded") let dataString = "name=\(userName)&password=\(userPassword)" @@ -893,17 +893,17 @@ internal extension CouchDBClient { cookie = header.value } } - + if let httpCookie = HTTPClient.Cookie(header: cookie, defaultDomain: self.couchHost) { if httpCookie.expires == nil { let formatter = DateFormatter() formatter.dateFormat = "E, dd-MMM-yyy HH:mm:ss z" - + let expiresString = cookie.split(separator: ";") .map({ $0.trimmingCharacters(in: .whitespaces) }) .first(where: { $0.hasPrefix("Expires=") })? .split(separator: "=").last - + if let expiresString = expiresString { let expires = formatter.date(from: String(expiresString)) sessionCookieExpires = expires @@ -921,7 +921,7 @@ internal extension CouchDBClient { authData = try JSONDecoder().decode(CreateSessionResponse.self, from: data) return authData } - + /// Build HTTP request from url string. /// - Parameters: /// - url: URL string. From 37c4bf683063668d072cc8c2221e54a9c02ccaee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=C3=B3rio=20Gevartosky=20Torrezan?= Date: Tue, 16 Jan 2024 15:06:25 -0400 Subject: [PATCH 04/63] Adding _find capability. (#13) --- Sources/CouchDBClient/CouchDBClient.swift | 90 +++++++++++++++++++ .../Models/CouchDBFindResponse.swift | 13 +++ 2 files changed, 103 insertions(+) create mode 100644 Sources/CouchDBClient/Models/CouchDBFindResponse.swift diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 0b00432..e78e423 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -478,6 +478,96 @@ public class CouchDBClient { throw parsingError } } + + /// Find data on DB. + /// + /// Examples: + /// + /// Define your document model: + /// ```swift + /// // Example struct + /// struct ExpectedDoc: CouchDBRepresentable, Codable { + /// var name: String + /// var _id: String? + /// var _rev: String? + /// } + /// ``` + /// + /// Find documents by selector: + /// ```swift + /// // find documents from DB by selector + /// var response = try await couchDBClient.get(in: "databaseName", selector: ["selector": ["name": "Greg"]]) + /// + /// // parse JSON + /// let bytes = response.body!.readBytes(length: response.body!.readableBytes)! + /// let docs = try JSONDecoder().decode([ExpectedDoc].self, from: Data(bytes)) + /// ``` + /// + /// + /// - Parameters: + /// - in dbName: DB name. + /// - selector: Codable representation of json selector query. + /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. + /// - Returns: Array of documents [T]. + public func find(in dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { + let encoder = JSONEncoder() + let insertEncodeData = try encoder.encode(selector) + + let insertResponse = try await find( + in: dbName, + body: .data(insertEncodeData), + eventLoopGroup: eventLoopGroup + ) + + guard var body = insertResponse.body, let bytes = body.readBytes(length: body.readableBytes) else { + throw CouchDBClientError.unknownResponse + } + + let data = Data(bytes) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = dateDecodingStrategy + + do { + let doc = try decoder.decode(CouchDBFindResponse.self, from: data) + return doc.docs + } catch let parsingError { + if let couchdbError = try? decoder.decode(CouchDBError.self, from: data) { + throw CouchDBClientError.getError(error: couchdbError) + } + throw parsingError + } + + } + + public func find(in dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { + try await authIfNeed(eventLoopGroup: eventLoopGroup) + + let httpClient: HTTPClient + if let eventLoopGroup = eventLoopGroup { + httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + } else { + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + } + + defer { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } + } + + let url = buildUrl(path: "/" + dbName + "/_find", query: []) + var request = try buildRequest(fromUrl: url, withMethod: .POST) + request.body = body + let response = try await httpClient + .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) + .get() + + if response.status == .unauthorized { + throw CouchDBClientError.unauthorized + } + + return response + } /// Update data in DB. /// diff --git a/Sources/CouchDBClient/Models/CouchDBFindResponse.swift b/Sources/CouchDBClient/Models/CouchDBFindResponse.swift new file mode 100644 index 0000000..cd40015 --- /dev/null +++ b/Sources/CouchDBClient/Models/CouchDBFindResponse.swift @@ -0,0 +1,13 @@ +// +// CouchDBFindResponse.swift +// +// +// Created by Gregorio Gevartosky Torrezan on 2023-11-15. +// + +import Foundation + +public struct CouchDBFindResponse: Codable { + var docs: [T] + var bookmark: String? +} From 7db38dd0d3280d172a193899b755aab6cbc62f2b Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 22:16:28 +0300 Subject: [PATCH 05/63] docs --- Sources/CouchDBClient/CouchDBClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index e78e423..af6e1ec 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -496,7 +496,7 @@ public class CouchDBClient { /// Find documents by selector: /// ```swift /// // find documents from DB by selector - /// var response = try await couchDBClient.get(in: "databaseName", selector: ["selector": ["name": "Greg"]]) + /// var response = try await couchDBClient.find(in: "databaseName", selector: ["selector": ["name": "Greg"]]) /// /// // parse JSON /// let bytes = response.body!.readBytes(length: response.body!.readableBytes)! From 4e37384b7c36e7f49bf3bd403dcb2610b5f4895b Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 22:16:33 +0300 Subject: [PATCH 06/63] tabs --- Sources/CouchDBClient/CouchDBClient.swift | 117 +++++++++++----------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index af6e1ec..1091d87 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -509,65 +509,64 @@ public class CouchDBClient { /// - selector: Codable representation of json selector query. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Array of documents [T]. - public func find(in dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { - let encoder = JSONEncoder() - let insertEncodeData = try encoder.encode(selector) - - let insertResponse = try await find( - in: dbName, - body: .data(insertEncodeData), - eventLoopGroup: eventLoopGroup - ) - - guard var body = insertResponse.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse - } - - let data = Data(bytes) - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = dateDecodingStrategy - - do { - let doc = try decoder.decode(CouchDBFindResponse.self, from: data) - return doc.docs - } catch let parsingError { - if let couchdbError = try? decoder.decode(CouchDBError.self, from: data) { - throw CouchDBClientError.getError(error: couchdbError) - } - throw parsingError - } - - } - - public func find(in dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { - try await authIfNeed(eventLoopGroup: eventLoopGroup) - - let httpClient: HTTPClient - if let eventLoopGroup = eventLoopGroup { - httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) - } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) - } - - defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() - } - } - - let url = buildUrl(path: "/" + dbName + "/_find", query: []) - var request = try buildRequest(fromUrl: url, withMethod: .POST) - request.body = body - let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() - - if response.status == .unauthorized { - throw CouchDBClientError.unauthorized - } - - return response - } + public func find(in dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { + let encoder = JSONEncoder() + let insertEncodeData = try encoder.encode(selector) + + let insertResponse = try await find( + in: dbName, + body: .data(insertEncodeData), + eventLoopGroup: eventLoopGroup + ) + + guard var body = insertResponse.body, let bytes = body.readBytes(length: body.readableBytes) else { + throw CouchDBClientError.unknownResponse + } + + let data = Data(bytes) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = dateDecodingStrategy + + do { + let doc = try decoder.decode(CouchDBFindResponse.self, from: data) + return doc.docs + } catch let parsingError { + if let couchdbError = try? decoder.decode(CouchDBError.self, from: data) { + throw CouchDBClientError.getError(error: couchdbError) + } + throw parsingError + } + } + + public func find(in dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { + try await authIfNeed(eventLoopGroup: eventLoopGroup) + + let httpClient: HTTPClient + if let eventLoopGroup = eventLoopGroup { + httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + } else { + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + } + + defer { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } + } + + let url = buildUrl(path: "/" + dbName + "/_find", query: []) + var request = try buildRequest(fromUrl: url, withMethod: .POST) + request.body = body + let response = try await httpClient + .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) + .get() + + if response.status == .unauthorized { + throw CouchDBClientError.unauthorized + } + + return response + } /// Update data in DB. /// From a70250cda7ba57ca0f9f5d920fc84415158a220e Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 22:34:42 +0300 Subject: [PATCH 07/63] renames --- Sources/CouchDBClient/CouchDBClient.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 1091d87..3a9c601 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -479,7 +479,7 @@ public class CouchDBClient { } } - /// Find data on DB. + /// Find data in DB. /// /// Examples: /// @@ -511,15 +511,15 @@ public class CouchDBClient { /// - Returns: Array of documents [T]. public func find(in dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { let encoder = JSONEncoder() - let insertEncodeData = try encoder.encode(selector) + let selectorData = try encoder.encode(selector) - let insertResponse = try await find( + let findResponse = try await find( in: dbName, - body: .data(insertEncodeData), + body: .data(selectorData), eventLoopGroup: eventLoopGroup ) - guard var body = insertResponse.body, let bytes = body.readBytes(length: body.readableBytes) else { + guard var body = findResponse.body, let bytes = body.readBytes(length: body.readableBytes) else { throw CouchDBClientError.unknownResponse } From e2f2b508619a693216228da57b20dab1651634bf Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 22:35:01 +0300 Subject: [PATCH 08/63] added test for find method with body --- .../CouchDBClientTests.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index bf659d2..39d3159 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -243,6 +243,29 @@ final class CouchDBClientTests: XCTestCase { XCTAssertNotNil(couchDBClient.sessionCookieExpires) } + func test8_find_with_body() async throws { + do { + let testDoc = ExpectedDoc(name: "Greg") + let insertEncodedData = try JSONEncoder().encode(testDoc) + let insertResponse = try await couchDBClient.insert( + dbName: testsDB, + body: .data(insertEncodedData) + ) + + let selector = ["selector": ["name": "Greg"]] + let bodyData = try JSONEncoder().encode(selector) + var findResponse = try await couchDBClient.find(in: testsDB, body: .data(bodyData)) + + let bytes = findResponse.body!.readBytes(length: findResponse.body!.readableBytes)! + let decodedResponse = try JSONDecoder().decode(CouchDBFindResponse.self, from: Data(bytes)) + + XCTAssertTrue(decodedResponse.docs.count > 0) + XCTAssertEqual(decodedResponse.docs.first!._id, insertResponse.id) + } catch { + XCTFail(error.localizedDescription) + } + } + func test99_deleteDB() async throws { do { try await couchDBClient.deleteDB(testsDB) From f9aa3ab11d152456a8d4ea0a5072d626e6658321 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 22:36:42 +0300 Subject: [PATCH 09/63] docs added --- Sources/CouchDBClient/CouchDBClient.swift | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 3a9c601..04d0f64 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -537,7 +537,23 @@ public class CouchDBClient { throw parsingError } } - + + /// Find data in DB. + /// + /// Example: + /// ```swift + /// let selector = ["selector": ["name": "Greg"]] + /// let bodyData = try JSONEncoder().encode(selector) + /// var findResponse = try await couchDBClient.find(in: testsDB, body: .data(bodyData)) + /// + /// let bytes = findResponse.body!.readBytes(length: findResponse.body!.readableBytes)! + /// let docs = try JSONDecoder().decode(CouchDBFindResponse.self, from: Data(bytes)).docs + /// ``` + /// - Parameters: + /// - dbName: DB name. + /// - body: Request body data. + /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. + /// - Returns: Request response. public func find(in dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { try await authIfNeed(eventLoopGroup: eventLoopGroup) From e5602ae61636faee418beb21330ff66ab1606571 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 22:54:54 +0300 Subject: [PATCH 10/63] findError added --- Sources/CouchDBClient/CouchDBClient.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 04d0f64..cf906eb 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -22,6 +22,8 @@ public enum CouchDBClientError: Error { case insertError(error: CouchDBError) /// Update request wasn't successful. case updateError(error: CouchDBError) + /// Find request wasn't successful. + case findError(error: CouchDBError) /// Uknown response from CouchDB. case unknownResponse /// Wrong username or password. @@ -41,6 +43,8 @@ extension CouchDBClientError: LocalizedError { return "Insert request wasn't successful: \(error.localizedDescription)" case .updateError(let error): return "Update request wasn't successful: \(error.localizedDescription)" + case .findError(let error): + return "Find request wasn't successful: \(error.localizedDescription)" case .unknownResponse: return "Uknown response from CouchDB." case .unauthorized: @@ -532,7 +536,7 @@ public class CouchDBClient { return doc.docs } catch let parsingError { if let couchdbError = try? decoder.decode(CouchDBError.self, from: data) { - throw CouchDBClientError.getError(error: couchdbError) + throw CouchDBClientError.findError(error: couchdbError) } throw parsingError } From 0e06ef633732aaf886fd9d685e3baf941393c6da Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 22:55:41 +0300 Subject: [PATCH 11/63] tests renamed --- .../CouchDBClientTests/CouchDBClientTests.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index 39d3159..2bfcdcf 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -27,7 +27,7 @@ final class CouchDBClientTests: XCTestCase { try await super.setUp() } - func test0_CreateDB() async throws { + func test00_CreateDB() async throws { do { let exists = try await couchDBClient.dbExists(testsDB) if exists { @@ -40,7 +40,7 @@ final class CouchDBClientTests: XCTestCase { } } - func test1_DBExists() async throws { + func test01_DBExists() async throws { do { let exists = try await couchDBClient.dbExists(testsDB) XCTAssertTrue(exists) @@ -49,7 +49,7 @@ final class CouchDBClientTests: XCTestCase { } } - func test3_GetAllDbs() async throws { + func test03_GetAllDbs() async throws { do { let dbs = try await couchDBClient.getAllDBs() @@ -61,7 +61,7 @@ final class CouchDBClientTests: XCTestCase { } } - func test4_updateAndDeleteDocMethods() async throws { + func test04_updateAndDeleteDocMethods() async throws { var testDoc = ExpectedDoc(name: "test name") var expectedInsertId: String = "" var expectedInsertRev: String = "" @@ -141,7 +141,7 @@ final class CouchDBClientTests: XCTestCase { } } - func test5_InsertGetUpdateDelete() async throws { + func test05_InsertGetUpdateDelete() async throws { var testDoc = ExpectedDoc(name: "test name") var expectedInsertId: String = "" var expectedInsertRev: String = "" @@ -228,7 +228,7 @@ final class CouchDBClientTests: XCTestCase { } } - func test6_BuildUrl() { + func test06_BuildUrl() { let expectedUrl = "http://127.0.0.1:5984?key=testKey" let url = couchDBClient.buildUrl(path: "", query: [ URLQueryItem(name: "key", value: "testKey") @@ -236,14 +236,14 @@ final class CouchDBClientTests: XCTestCase { XCTAssertEqual(url, expectedUrl) } - func test7_Auth() async throws { + func test07_Auth() async throws { let session: CreateSessionResponse? = try await couchDBClient.authIfNeed() XCTAssertNotNil(session) XCTAssertEqual(true, session?.ok) XCTAssertNotNil(couchDBClient.sessionCookieExpires) } - func test8_find_with_body() async throws { + func test08_find_with_body() async throws { do { let testDoc = ExpectedDoc(name: "Greg") let insertEncodedData = try JSONEncoder().encode(testDoc) From c9416571d095866a73f2be07d361e9ba2742df66 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 22:56:46 +0300 Subject: [PATCH 12/63] added test for find method with generic type --- .../CouchDBClientTests.swift | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index 2bfcdcf..63b4c45 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -261,6 +261,37 @@ final class CouchDBClientTests: XCTestCase { XCTAssertTrue(decodedResponse.docs.count > 0) XCTAssertEqual(decodedResponse.docs.first!._id, insertResponse.id) + + _ = try await couchDBClient.delete( + fromDb: testsDB, + uri: decodedResponse.docs.first!._id!, + rev: decodedResponse.docs.first!._rev! + ) + } catch { + XCTFail(error.localizedDescription) + } + } + + func test09_find_with_generics() async throws { + do { + let testDoc = ExpectedDoc(name: "Sam") + let insertEncodedData = try JSONEncoder().encode(testDoc) + let insertResponse = try await couchDBClient.insert( + dbName: testsDB, + body: .data(insertEncodedData) + ) + + let selector = ["selector": ["name": "Sam"]] + let docs: [ExpectedDoc] = try await couchDBClient.find(in: testsDB, selector: selector) + + XCTAssertTrue(docs.count > 0) + XCTAssertEqual(docs.first!._id, insertResponse.id) + + _ = try await couchDBClient.delete( + fromDb: testsDB, + uri: docs.first!._id!, + rev: docs.first!._rev! + ) } catch { XCTFail(error.localizedDescription) } From e6b6566c9289bb5fd8424be0f7c47fe708ba7c4d Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 22:59:25 +0300 Subject: [PATCH 13/63] docs --- Sources/CouchDBClient/CouchDBClient.swift | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index cf906eb..7a36e7d 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -485,29 +485,14 @@ public class CouchDBClient { /// Find data in DB. /// - /// Examples: + /// Example: /// - /// Define your document model: /// ```swift - /// // Example struct - /// struct ExpectedDoc: CouchDBRepresentable, Codable { - /// var name: String - /// var _id: String? - /// var _rev: String? - /// } + /// // find documents in DB by selector + /// let selector = ["selector": ["name": "Sam"]] + /// let docs: [ExpectedDoc] = try await couchDBClient.find(in: testsDB, selector: selector) /// ``` /// - /// Find documents by selector: - /// ```swift - /// // find documents from DB by selector - /// var response = try await couchDBClient.find(in: "databaseName", selector: ["selector": ["name": "Greg"]]) - /// - /// // parse JSON - /// let bytes = response.body!.readBytes(length: response.body!.readableBytes)! - /// let docs = try JSONDecoder().decode([ExpectedDoc].self, from: Data(bytes)) - /// ``` - /// - /// /// - Parameters: /// - in dbName: DB name. /// - selector: Codable representation of json selector query. From d4f520f31ae7dc6468322ea60a6a92fdce6a1649 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 23:10:06 +0300 Subject: [PATCH 14/63] README updated --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index dc44aa3..70b826d 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,13 @@ print(dbs) // prints: ["_global_changes", "_replicator", "_users", "yourDBname"] ``` +Find documents in DB by selector: +```swift +let selector = ["selector": ["name": "Sam"]] +let docs: [ExpectedDoc] = try await couchDBClient.find(in: "databaseName", selector: selector) +print(docs) +``` + ### Using with Vapor Here's a simple [tutorial](https://spaceinbox.me/docs/couchdbclient/tutorials/couchdbclient/vaportutorial) for Vapor. From 78e7d1030b007b996b624f47f795986986b7ec8e Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 23:11:04 +0300 Subject: [PATCH 15/63] updated dependencies --- Package.resolved | 55 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/Package.resolved b/Package.resolved index ad4dea0..390b3d2 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/async-http-client.git", "state" : { - "revision" : "16f7e62c08c6969899ce6cc277041e868364e5cf", - "version" : "1.19.0" + "revision" : "291438696abdd48d2a83b52465c176efbd94512b", + "version" : "1.20.1" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms", + "state" : { + "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version" : "1.2.0" } }, { @@ -14,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-atomics.git", "state" : { - "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", - "version" : "1.1.0" + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" } }, { @@ -23,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", - "version" : "1.0.4" + "revision" : "d029d9d39c87bed85b1c50adee7c41795261a192", + "version" : "1.0.6" } }, { @@ -45,6 +54,15 @@ "version" : "1.0.0" } }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types", + "state" : { + "revision" : "1827dc94bdab2eb5f2fc804e9b0cb43574282566", + "version" : "1.0.2" + } + }, { "identity" : "swift-log", "kind" : "remoteSourceControl", @@ -59,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "cf281631ff10ec6111f2761052aa81896a83a007", - "version" : "2.58.0" + "revision" : "702cd7c56d5d44eeba73fdf83918339b26dc855c", + "version" : "2.62.0" } }, { @@ -68,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "0e0d0aab665ff1a0659ce75ac003081f2b1c8997", - "version" : "1.19.0" + "revision" : "798c962495593a23fdea0c0c63fd55571d8dff51", + "version" : "1.20.0" } }, { @@ -77,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "a8ccf13fa62775277a5d56844878c828bbb3be1a", - "version" : "1.27.0" + "revision" : "3bd9004b9d685ed6b629760fc84903e48efec806", + "version" : "1.29.0" } }, { @@ -95,8 +113,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "e7403c35ca6bb539a7ca353b91cc2d8ec0362d58", - "version" : "1.19.0" + "revision" : "ebf8b9c365a6ce043bf6e6326a04b15589bd285e", + "version" : "1.20.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" } } ], From 51966c35c78ad3cf59b544bbda8e1d17acfe9518 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 23:14:03 +0300 Subject: [PATCH 16/63] docs updated --- Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md | 1 + Sources/CouchDBClient/CouchDBClient.docc/Extensions/Client.md | 2 ++ Sources/CouchDBClient/CouchDBClient.swift | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md b/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md index 2e06c00..0672515 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md +++ b/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md @@ -17,6 +17,7 @@ Currently CouchDBClient supports: - Get databases list. - Get document by id or documents using view. - Insert/update documents. +- Find documents by selector. - Delete documents. - CouchDB authorization. diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Extensions/Client.md b/Sources/CouchDBClient/CouchDBClient.docc/Extensions/Client.md index 18ca725..65efbf7 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Extensions/Client.md +++ b/Sources/CouchDBClient/CouchDBClient.docc/Extensions/Client.md @@ -27,5 +27,7 @@ A CouchDB client class with methods using Swift Concurrency. - ``insert(dbName:doc:dateEncodingStrategy:eventLoopGroup:)`` - ``update(dbName:doc:dateEncodingStrategy:eventLoopGroup:)`` - ``update(dbName:uri:body:eventLoopGroup:)`` +- ``find(in:body:eventLoopGroup:)`` +- ``find(in:selector:dateDecodingStrategy:eventLoopGroup:)`` - ``delete(fromDb:doc:eventLoopGroup:)`` - ``delete(fromDb:uri:rev:eventLoopGroup:)`` diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 7a36e7d..f1bb924 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -483,7 +483,7 @@ public class CouchDBClient { } } - /// Find data in DB. + /// Find data in DB by selector. /// /// Example: /// @@ -527,7 +527,7 @@ public class CouchDBClient { } } - /// Find data in DB. + /// Find data in DB by selector. /// /// Example: /// ```swift From 1118bb3f1af1a62bca1b5b3aff7cad08081cadd4 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 23:24:11 +0300 Subject: [PATCH 17/63] try Swift 5.7.1 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 016478d..3c96474 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.6 +// swift-tools-version:5.7.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From dfdc610c8a987592deb09277d2811a0db3468135 Mon Sep 17 00:00:00 2001 From: Sergey Date: Tue, 16 Jan 2024 23:26:48 +0300 Subject: [PATCH 18/63] Update build-ubuntu.yml --- .github/workflows/build-ubuntu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index 322f04a..bb991a2 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -11,7 +11,7 @@ jobs: name: Build on Ubuntu with Swift ${{matrix.swift}} strategy: matrix: - swift: [5.8.1, 5.7.3, 5.6.3] + swift: [5.9, 5.8.1, 5.7.3] runs-on: ubuntu-latest container: image: swift:${{matrix.swift}} From ab8f67a89f5b83ed20fe30a9965a3f2f58f6d2c1 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 23:39:51 +0300 Subject: [PATCH 19/63] Swift 5.7.3 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 3c96474..a08f718 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7.1 +// swift-tools-version:5.7.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 896eef9ed6ad097efc14ed50c98c90175b1ace39 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Tue, 16 Jan 2024 23:42:10 +0300 Subject: [PATCH 20/63] Revert "Swift 5.7.3" This reverts commit ab8f67a89f5b83ed20fe30a9965a3f2f58f6d2c1. --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index a08f718..3c96474 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7.3 +// swift-tools-version:5.7.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 04f3bdd6c39c1581af943701597a3fce9e8e1c62 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Sat, 24 Feb 2024 18:38:32 +0500 Subject: [PATCH 21/63] updated dependencies --- Package.resolved | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/Package.resolved b/Package.resolved index 390b3d2..8e4161f 100644 --- a/Package.resolved +++ b/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "d029d9d39c87bed85b1c50adee7c41795261a192", - "version" : "1.0.6" + "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", + "version" : "1.1.0" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-types", "state" : { - "revision" : "1827dc94bdab2eb5f2fc804e9b0cb43574282566", - "version" : "1.0.2" + "revision" : "12358d55a3824bd5fed310b999ea8cf83a9a1a65", + "version" : "1.0.3" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-log.git", "state" : { - "revision" : "532d8b529501fb73a2455b179e0bbb6d49b652ed", - "version" : "1.5.3" + "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5", + "version" : "1.5.4" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "702cd7c56d5d44eeba73fdf83918339b26dc855c", - "version" : "2.62.0" + "revision" : "635b2589494c97e48c62514bc8b37ced762e0a62", + "version" : "2.63.0" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "798c962495593a23fdea0c0c63fd55571d8dff51", - "version" : "1.20.0" + "revision" : "363da63c1966405764f380c627409b2f9d9e710b", + "version" : "1.21.0" } }, { @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-http2.git", "state" : { - "revision" : "3bd9004b9d685ed6b629760fc84903e48efec806", - "version" : "1.29.0" + "revision" : "0904bf0feb5122b7e5c3f15db7df0eabe623dd87", + "version" : "1.30.0" } }, { @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-ssl.git", "state" : { - "revision" : "320bd978cceb8e88c125dcbb774943a92f6286e9", - "version" : "2.25.0" + "revision" : "7c381eb6083542b124a6c18fae742f55001dc2b5", + "version" : "2.26.0" } }, { @@ -113,8 +113,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-transport-services.git", "state" : { - "revision" : "ebf8b9c365a6ce043bf6e6326a04b15589bd285e", - "version" : "1.20.0" + "revision" : "6cbe0ed2b394f21ab0d46b9f0c50c6be964968ce", + "version" : "1.20.1" } }, { @@ -125,6 +125,15 @@ "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", "version" : "1.0.2" } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "025bcb1165deab2e20d4eaba79967ce73013f496", + "version" : "1.2.1" + } } ], "version" : 2 From bdeedda9ca5dd2f140d31181a64bafebfc2ef97b Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Sat, 24 Feb 2024 18:54:01 +0500 Subject: [PATCH 22/63] Docs updated --- README.md | 12 ++++++------ .../CouchDBClient.docc/CouchDBClient.md | 2 +- .../ErrorsHandling/ErrorsHandlingTutorial.tutorial | 8 ++++---- .../Tutorials/Tutorial Table of Contents.tutorial | 4 ++-- .../Tutorials/macOS/macOSTutorial.tutorial | 8 ++++---- .../Tutorials/vapor/VaporTutorial.tutorial | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 70b826d..4b867f9 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,14 @@ -This is simple lib to work with CouchDB in Swift. +This is a simple lib to work with CouchDB in Swift. - Latest version is based on async/await and requires Swift 5.6 and newer. Works with Vapor 4.50 and newer. - Version 1.0.0 can be used with Vapor 4 without async/await. Swift 5.3 is required -- You can use old version for Vapor 3 from vapor3 branch or using version < 1.0.0. +- You can use the old version for Vapor 3 from vapor3 branch or using version < 1.0.0. -The only depndency for this lib is async-http-client +The only dependency for this lib is async-http-client -## Documentaion +## Documentation You can find docs, examples and even tutorials [here](https://spaceinbox.me/docs/couchdbclient/documentation/couchdbclient). @@ -55,7 +55,7 @@ let couchDBClient = CouchDBClient( ) ``` -If you don’t want to have your password in the code you can pass COUCHDB_PASS param in you command line. For example you can run your Server Side Swift project: +If you don’t want to have your password in the code you can pass COUCHDB_PASS param in your command line. For example you can run your Server Side Swift project: ```bash COUCHDB_PASS=myPassword /path/.build/x86_64-unknown-linux-gnu/release/Run ``` @@ -113,7 +113,7 @@ try await couchDBClient.update( print(doc) // doc will have updated name and _rev values now ``` -Delete data example: +Delete data: ```swift let response = try await couchDBClient.delete(fromDb: "databaseName", doc: doc) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md b/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md index 0672515..0a95d58 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md +++ b/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md @@ -8,7 +8,7 @@ Source code is available on [GitHub](https://github.com/makoni/couchdb-vapor). CouchDBClient allows you to make simple requests to CouchDB. It's using Swift Concurrency (async/await) and supports Linux, iOS 13+ and macOS 10.15+. -It's using [AsyncHTTPClient](https://github.com/swift-server/async-http-client) which makes it easy to use CouchDBClient for server-side development with Vapor 4. +It's using [AsyncHTTPClient](https://github.com/swift-server/async-http-client) which makes it easy to use CouchDBClient for server-side development with Vapor 4. But it's easy to use it with any iOS or macOS app. Check the Essentials section for examples. Currently CouchDBClient supports: - Check if DB exists. diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/ErrorsHandling/ErrorsHandlingTutorial.tutorial b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/ErrorsHandling/ErrorsHandlingTutorial.tutorial index 75c1341..313adfa 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/ErrorsHandling/ErrorsHandlingTutorial.tutorial +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/ErrorsHandling/ErrorsHandlingTutorial.tutorial @@ -14,25 +14,25 @@ @Steps { @Step { - `CouchDBClient` has an Error enum `CouchDBClientError`. Some of enum values has nested error of `CouchDBError` type that represents error messages from CouchDB. + `CouchDBClient` has an Error enum `CouchDBClientError`. Some enum values have nested errors of `CouchDBError` type that represent error messages from CouchDB. @Code(name: "main.swift", file: ErrorsHandlingTutorial-1.swift) } @Step { - You can wrap your code in do-catch block to catch a CouchDB error during insert operation. + You can wrap your code in a do-catch block to catch a CouchDB error during insert operation. @Code(name: "main.swift", file: ErrorsHandlingTutorial-2.swift) } @Step { - Same for update to find out what's wrong. + Same for updates to find out what's wrong. @Code(name: "main.swift", file: ErrorsHandlingTutorial-3.swift) } @Step { - And same for get operation. CouchDB will return an error message if username or password is incorrect for example. + And same for a get operation. CouchDB will return an error message if the username or password is incorrect for example. @Code(name: "main.swift", file: ErrorsHandlingTutorial-4.swift) } diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/Tutorial Table of Contents.tutorial b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/Tutorial Table of Contents.tutorial index c6118a1..cec5b4a 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/Tutorial Table of Contents.tutorial +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/Tutorial Table of Contents.tutorial @@ -1,6 +1,6 @@ @Tutorials(name: "CouchDBClient") { @Intro(title: "Getting started") { - Check tutorials for using the lib in a macOS app or with Vapor for server-side developement. + Check tutorials for using the lib in a macOS app or with Vapor for server-side development. @Image(source: logo.png, alt: "CouchDBClient logo") } @@ -15,7 +15,7 @@ } @Chapter(name: "Errors handling") { - Examples handling CouchDBClient errors and errors from CouchDB. + Examples of handling CouchDBClient errors and errors from CouchDB. @Image(source: chapter1.png, alt: "CouchDBClient logo") diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial index 6ebdd8b..6cd9d2d 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial @@ -7,7 +7,7 @@ @Section(title: "Initializaton") { @ContentAndMedia { - Adding CouchDBClient in your project. + Adding CouchDBClient to your project. @Image(source: chapter1.png, alt: "Application icon") } @@ -38,7 +38,7 @@ } @Step { - Define a model for your CouchDB document. It should confirm to `Codable` and `CouchDBRepresentable` protocols. + Define a model for your CouchDB document. It should conform to `Codable` and `CouchDBRepresentable` protocols. @Code(name: "main.swift", file: macOSTutorial-4.swift) } @@ -50,13 +50,13 @@ } @Step { - Here's an example of updating the document. CouchDBClient will also update `_rev` value of your document with the value from CouchDB after saving. + Here's an example of updating the document. CouchDBClient will also update the `_rev` value of your document with the value from CouchDB after saving. @Code(name: "main.swift", file: macOSTutorial-6.swift) } @Step { - Getting document by it's `_id` from DB with that method will parse JSON into your model if you'll provide it as a generic type. + Getting a document by its `_id` from DB with that method will parse JSON into your model if you provide it as a generic type. @Code(name: "main.swift", file: macOSTutorial-7.swift) } diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial index 31a56ec..14ba158 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial @@ -32,13 +32,13 @@ } @Step { - Define you data model for CouchDB document. Nested `Row` and `RowsResponse` models will be used to parse CouchDB responses. + Define your data model for CouchDB documents. Nested `Row` and `RowsResponse` models will be used to parse CouchDB responses. @Code(name: "main.swift", file: VaporTutorial-3.swift) } @Step { - Get you document from DB. That example is using `CouchDB View` to find the document by url field. It's map function needs `key` param which is `appUrl` in our case. + Get your document from DB. That example is using `CouchDB View` to find the document by the url field. Its map function needs a `key` param which is `appUrl` in our case. @Code(name: "main.swift", file: VaporTutorial-4.swift) } From 2dd69f6824b6d49435987651331ebd56b544a8b9 Mon Sep 17 00:00:00 2001 From: Sergey Date: Sat, 24 Feb 2024 19:01:30 +0500 Subject: [PATCH 23/63] Delete .github/workflows/CodeQL.yml --- .github/workflows/CodeQL.yml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .github/workflows/CodeQL.yml diff --git a/.github/workflows/CodeQL.yml b/.github/workflows/CodeQL.yml deleted file mode 100644 index 8fef0e6..0000000 --- a/.github/workflows/CodeQL.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: [ "develop" ] - pull_request: - branches: [ "main" ] - -jobs: - run-codeql-linux: - name: Run CodeQL on Linux - runs-on: ubuntu-latest - permissions: - security-events: write - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: swift - - - name: Build - run: swift build - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 From 33cb1749063501c2f156bbf5b8e9c3f91ca4afd8 Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Wed, 27 Mar 2024 00:56:16 +0300 Subject: [PATCH 24/63] dependencies updated --- Package.resolved | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Package.resolved b/Package.resolved index 8e4161f..a2013f7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio.git", "state" : { - "revision" : "635b2589494c97e48c62514bc8b37ced762e0a62", - "version" : "2.63.0" + "revision" : "fc63f0cf4e55a4597407a9fc95b16a2bc44b4982", + "version" : "2.64.0" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-nio-extras.git", "state" : { - "revision" : "363da63c1966405764f380c627409b2f9d9e710b", - "version" : "1.21.0" + "revision" : "a3b640d7dc567225db7c94386a6e71aded1bfa63", + "version" : "1.22.0" } }, { From abce15ff8325e804dd183e5974bae8d333e8577a Mon Sep 17 00:00:00 2001 From: Sergey Armodin Date: Wed, 27 Mar 2024 00:57:27 +0300 Subject: [PATCH 25/63] =?UTF-8?q?fixed=20when=20update=20method=20didn?= =?UTF-8?q?=E2=80=99t=20use=20passed=20dateEncodingStrategy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/CouchDBClient/CouchDBClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index f1bb924..1db97d4 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -704,7 +704,7 @@ public class CouchDBClient { let encoder = JSONEncoder() encoder.dateEncodingStrategy = dateEncodingStrategy - let encodedData = try JSONEncoder().encode(doc) + let encodedData = try encoder.encode(doc) let updateResponse = try await update( dbName: dbName, From 1a522e05a025c080fa7888248ed588bc546f8a24 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 20:34:13 +0300 Subject: [PATCH 26/63] noData error added --- Sources/CouchDBClient/CouchDBClient.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 1db97d4..68254e1 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -28,6 +28,8 @@ public enum CouchDBClientError: Error { case unknownResponse /// Wrong username or password. case unauthorized + /// Missing data in response body. + case noData } extension CouchDBClientError: LocalizedError { @@ -49,6 +51,8 @@ extension CouchDBClientError: LocalizedError { return "Uknown response from CouchDB." case .unauthorized: return "Wrong username or password." + case .noData: + return "Missing data in response body." } } } From a1dcfaf413e11714336eee34b14393e26667e022 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 20:35:20 +0300 Subject: [PATCH 27/63] migrating to new HTTPClientRequest from HTTPClient.Request wip --- .../CouchDBClient/CouchDB+Deprecated.swift | 93 +++++++++++++++++++ Sources/CouchDBClient/CouchDBClient.swift | 54 +++++++---- 2 files changed, 130 insertions(+), 17 deletions(-) create mode 100644 Sources/CouchDBClient/CouchDB+Deprecated.swift diff --git a/Sources/CouchDBClient/CouchDB+Deprecated.swift b/Sources/CouchDBClient/CouchDB+Deprecated.swift new file mode 100644 index 0000000..f8742eb --- /dev/null +++ b/Sources/CouchDBClient/CouchDB+Deprecated.swift @@ -0,0 +1,93 @@ +// +// CouchDB+Deprecated.swift +// +// +// Created by Sergei Armodin on 02.04.2024. +// + +import Foundation +import AsyncHTTPClient +import NIO + +extension CouchDBClient { + /// Insert data in DB. Accepts HTTPClient.Body as body parameter. + /// + /// Examples: + /// + /// Define your document model: + /// ```swift + /// // Example struct + /// struct ExpectedDoc: CouchDBRepresentable, Codable { + /// var name: String + /// var _id: String? + /// var _rev: String? + /// } + /// ``` + /// + /// Create a new document and insert: + /// ```swift + /// let testDoc = ExpectedDoc(name: "My name") + /// let data = try JSONEncoder().encode(testData) + /// + /// let response = try await couchDBClient.insert( + /// dbName: "databaseName", + /// body: .data(data) + /// ) + /// + /// print(response) + /// ``` + /// + /// - Parameters: + /// - dbName: DB name. + /// - body: Request body data. + /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. + /// - Returns: Insert request response. + @available(*, deprecated, message: "Use the insert method that accepts HTTPClientRequest.Body type.") + public func insert(dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> CouchUpdateResponse { + try await authIfNeed(eventLoopGroup: eventLoopGroup) + + let httpClient: HTTPClient + if let eventLoopGroup = eventLoopGroup { + httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + } else { + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + } + + defer { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } + } + + let url = buildUrl(path: "/\(dbName)") + + var request = try self.buildRequest(fromUrl: url, withMethod: .POST) + request.body = body + + let response = try await httpClient + .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) + .get() + + if response.status == .unauthorized { + throw CouchDBClientError.unauthorized + } + + guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { + throw CouchDBClientError.unknownResponse + } + + let data = Data(bytes) + let decoder = JSONDecoder() + + do { + let decodedResponse = try decoder.decode(CouchUpdateResponse.self, from: data) + return decodedResponse + } catch let parsingError { + if let couchdbError = try? decoder.decode(CouchDBError.self, from: data) { + throw CouchDBClientError.insertError(error: couchdbError) + } + throw parsingError + } + } + +} diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 68254e1..d0a7461 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -176,20 +176,21 @@ public class CouchDBClient { let url = buildUrl(path: "/_all_dbs") - let request = try buildRequest(fromUrl: url, withMethod: .GET) + let request = try buildRequestNew(fromUrl: url, withMethod: .GET) let response = try await httpClient - .execute(request: request) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized } - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse - } + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) - let data = Data(bytes) + guard let data = bytes.readData(length: bytes.readableBytes) else { + throw CouchDBClientError.noData + } return try JSONDecoder().decode([String].self, from: data) } @@ -725,7 +726,7 @@ public class CouchDBClient { doc._id = updateResponse.id } - /// Insert data in DB. + /// Insert data in DB. Accepts HTTPClientRequest.Body as body parameter. /// /// Examples: /// @@ -744,9 +745,11 @@ public class CouchDBClient { /// let testDoc = ExpectedDoc(name: "My name") /// let data = try JSONEncoder().encode(testData) /// + /// let body: HTTPClientRequest.Body = .bytes(ByteBuffer(data: insertEncodeData)) + /// /// let response = try await couchDBClient.insert( /// dbName: "databaseName", - /// body: .data(data) + /// body: body /// ) /// /// print(response) @@ -757,7 +760,7 @@ public class CouchDBClient { /// - body: Request body data. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Insert request response. - public func insert(dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> CouchUpdateResponse { + public func insert(dbName: String, body: HTTPClientRequest.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> CouchUpdateResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) let httpClient: HTTPClient @@ -775,22 +778,24 @@ public class CouchDBClient { let url = buildUrl(path: "/\(dbName)") - var request = try self.buildRequest(fromUrl: url, withMethod: .POST) + var request = try self.buildRequestNew(fromUrl: url, withMethod: .POST) request.body = body let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized } - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) + + guard let data = bytes.readData(length: bytes.readableBytes) else { + throw CouchDBClientError.noData } - let data = Data(bytes) let decoder = JSONDecoder() do { @@ -839,9 +844,11 @@ public class CouchDBClient { encoder.dateEncodingStrategy = dateEncodingStrategy let insertEncodeData = try encoder.encode(doc) + let body: HTTPClientRequest.Body = .bytes(ByteBuffer(data: insertEncodeData)) + let insertResponse = try await insert( dbName: dbName, - body: .data(insertEncodeData), + body: body, eventLoopGroup: eventLoopGroup ) @@ -1038,4 +1045,17 @@ internal extension CouchDBClient { body: nil ) } + + func buildRequestNew(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClientRequest { + var headers = HTTPHeaders() + headers.add(name: "Content-Type", value: "application/json") + if let cookie = sessionCookie { + headers.add(name: "Cookie", value: cookie) + } + + var request = HTTPClientRequest(url: url) + request.method = method + request.headers = headers + return request + } } From 0bb698248e9c4ad87144a8ed5b4f9cbebfc08c05 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 20:37:54 +0300 Subject: [PATCH 28/63] migrating to new HTTPClientRequest from HTTPClient.Request wip --- Sources/CouchDBClient/CouchDBClient.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index d0a7461..38b6b8c 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -223,10 +223,9 @@ public class CouchDBClient { } let url = buildUrl(path: "/" + dbName) - let request = try buildRequest(fromUrl: url, withMethod: .HEAD) + let request = try buildRequestNew(fromUrl: url, withMethod: .HEAD) let response = try await httpClient - .execute(request: request) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized From ad36014e42feed91e91d536f414281cb758fe772 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 20:39:22 +0300 Subject: [PATCH 29/63] migrating to new HTTPClientRequest from HTTPClient.Request wip --- Sources/CouchDBClient/CouchDBClient.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 38b6b8c..ede8569 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -263,21 +263,23 @@ public class CouchDBClient { let url = buildUrl(path: "/\(dbName)") - let request = try self.buildRequest(fromUrl: url, withMethod: .PUT) + let request = try self.buildRequestNew(fromUrl: url, withMethod: .PUT) let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized } - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) + + guard let data = bytes.readData(length: bytes.readableBytes) else { + throw CouchDBClientError.noData } - let data = Data(bytes) let decoder = JSONDecoder() do { From 73ffa10b93b0a3b0cb36fa3e2664e8207c38d47c Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 20:40:29 +0300 Subject: [PATCH 30/63] migrating to new HTTPClientRequest from HTTPClient.Request wip --- Sources/CouchDBClient/CouchDBClient.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index ede8569..c9bc062 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -322,21 +322,23 @@ public class CouchDBClient { let url = buildUrl(path: "/\(dbName)") - let request = try self.buildRequest(fromUrl: url, withMethod: .DELETE) + let request = try self.buildRequestNew(fromUrl: url, withMethod: .DELETE) let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized } - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) + + guard let data = bytes.readData(length: bytes.readableBytes) else { + throw CouchDBClientError.noData } - let data = Data(bytes) let decoder = JSONDecoder() do { From c60feade15cd011b682946ee999c133f075b83e7 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 21:09:14 +0300 Subject: [PATCH 31/63] migrating to new HTTPClientRequest from HTTPClient.Request wip --- .../CouchDBClient/CouchDB+Deprecated.swift | 81 +------------------ Sources/CouchDBClient/CouchDBClient.swift | 49 ++++++++--- .../CouchDBClientTests.swift | 42 +++++++--- 3 files changed, 67 insertions(+), 105 deletions(-) diff --git a/Sources/CouchDBClient/CouchDB+Deprecated.swift b/Sources/CouchDBClient/CouchDB+Deprecated.swift index f8742eb..5d1473a 100644 --- a/Sources/CouchDBClient/CouchDB+Deprecated.swift +++ b/Sources/CouchDBClient/CouchDB+Deprecated.swift @@ -10,84 +10,5 @@ import AsyncHTTPClient import NIO extension CouchDBClient { - /// Insert data in DB. Accepts HTTPClient.Body as body parameter. - /// - /// Examples: - /// - /// Define your document model: - /// ```swift - /// // Example struct - /// struct ExpectedDoc: CouchDBRepresentable, Codable { - /// var name: String - /// var _id: String? - /// var _rev: String? - /// } - /// ``` - /// - /// Create a new document and insert: - /// ```swift - /// let testDoc = ExpectedDoc(name: "My name") - /// let data = try JSONEncoder().encode(testData) - /// - /// let response = try await couchDBClient.insert( - /// dbName: "databaseName", - /// body: .data(data) - /// ) - /// - /// print(response) - /// ``` - /// - /// - Parameters: - /// - dbName: DB name. - /// - body: Request body data. - /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Insert request response. - @available(*, deprecated, message: "Use the insert method that accepts HTTPClientRequest.Body type.") - public func insert(dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> CouchUpdateResponse { - try await authIfNeed(eventLoopGroup: eventLoopGroup) - - let httpClient: HTTPClient - if let eventLoopGroup = eventLoopGroup { - httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) - } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) - } - - defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() - } - } - - let url = buildUrl(path: "/\(dbName)") - - var request = try self.buildRequest(fromUrl: url, withMethod: .POST) - request.body = body - - let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() - - if response.status == .unauthorized { - throw CouchDBClientError.unauthorized - } - - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse - } - - let data = Data(bytes) - let decoder = JSONDecoder() - - do { - let decodedResponse = try decoder.decode(CouchUpdateResponse.self, from: data) - return decodedResponse - } catch let parsingError { - if let couchdbError = try? decoder.decode(CouchDBError.self, from: data) { - throw CouchDBClientError.insertError(error: couchdbError) - } - throw parsingError - } - } - + } diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index c9bc062..68c9d08 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -369,11 +369,22 @@ public class CouchDBClient { /// Get document by ID: /// ```swift /// // get data from DB by document ID - /// var response = try await couchDBClient.get(dbName: "databaseName", uri: "documentId") + /// var response = try await couchDBClient.get( + /// dbName: "databaseName", + /// uri: "documentId" + /// ) /// /// // parse JSON - /// let bytes = response.body!.readBytes(length: response.body!.readableBytes)! - /// let doc = try JSONDecoder().decode(ExpectedDoc.self, from: Data(bytes)) + /// let expectedBytes = response.headers + /// .first(name: "content-length") + /// .flatMap(Int.init) ?? 1024 * 1024 * 10 + /// var bytes = try await response.body.collect(upTo: expectedBytes) + /// let data = bytes.readData(length: bytes.readableBytes) + /// + /// let doc = try JSONDecoder().decode( + /// ExpectedDoc.self, + /// from: data! + /// ) /// ``` /// /// You can also provide CouchDB view document as uri and key in query. @@ -397,8 +408,18 @@ public class CouchDBClient { /// uri: "_design/all/_view/by_url", /// query: ["key": "\"\(url)\""] /// ) - /// let bytes = response.body!.readBytes(length: response.body!.readableBytes)! - /// let decodedResponse = try JSONDecoder().decode(RowsResponse.self, from: data) + /// + /// let expectedBytes = response.headers + /// .first(name: "content-length") + /// .flatMap(Int.init) ?? 1024 * 1024 * 10 + /// var bytes = try await response.body.collect(upTo: expectedBytes) + /// let data = bytes.readData(length: bytes.readableBytes) + /// + /// let decodedResponse = try JSONDecoder().decode( + /// RowsResponse.self, + /// from: data! + /// ) + /// /// print(decodedResponse.rows) /// print(decodedResponse.rows.first?.value) /// ``` @@ -409,7 +430,7 @@ public class CouchDBClient { /// - query: Request query items. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Request response. - public func get(dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { + public func get(dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClientResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) let httpClient: HTTPClient @@ -426,10 +447,9 @@ public class CouchDBClient { } let url = buildUrl(path: "/" + dbName + "/" + uri, query: queryItems ?? []) - let request = try buildRequest(fromUrl: url, withMethod: .GET) + let request = try buildRequestNew(fromUrl: url, withMethod: .GET) let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized @@ -466,17 +486,20 @@ public class CouchDBClient { /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: An object or a struct (of generic type) parsed from JSON. public func get (dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { - let response = try await get(dbName: dbName, uri: uri, queryItems: queryItems, eventLoopGroup: eventLoopGroup) + let response: HTTPClientResponse = try await get(dbName: dbName, uri: uri, queryItems: queryItems, eventLoopGroup: eventLoopGroup) if response.status == .unauthorized { throw CouchDBClientError.unauthorized } - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) + + guard let data = bytes.readData(length: bytes.readableBytes) else { + throw CouchDBClientError.noData } - let data = Data(bytes) let decoder = JSONDecoder() decoder.dateDecodingStrategy = dateDecodingStrategy diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index 63b4c45..6e72bd8 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -115,14 +115,20 @@ final class CouchDBClientTests: XCTestCase { XCTAssertEqual(testDoc._id, expectedInsertId) // get updated doc - var getResponse2 = try await couchDBClient.get( + let getResponse2 = try await couchDBClient.get( dbName: testsDB, uri: expectedInsertId ) XCTAssertNotNil(getResponse2.body) - let bytes2 = getResponse2.body!.readBytes(length: getResponse2.body!.readableBytes)! - testDoc = try JSONDecoder().decode(ExpectedDoc.self, from: Data(bytes2)) + let expectedBytes2 = getResponse2.headers.first(name: "content-length").flatMap(Int.init) ?? 1024 * 1024 * 10 + var bytes2 = try await getResponse2.body.collect(upTo: expectedBytes2) + let data2 = bytes2.readData(length: bytes2.readableBytes) + + testDoc = try JSONDecoder().decode( + ExpectedDoc.self, + from: data2! + ) XCTAssertEqual(expectedName, testDoc.name) @@ -151,7 +157,7 @@ final class CouchDBClientTests: XCTestCase { let insertEncodeData = try JSONEncoder().encode(testDoc) let response = try await couchDBClient.insert( dbName: testsDB, - body: .data(insertEncodeData) + body: .bytes(ByteBuffer(data: insertEncodeData)) ) XCTAssertEqual(response.ok, true) @@ -167,11 +173,17 @@ final class CouchDBClientTests: XCTestCase { // Test Get var expectedName = testDoc.name do { - var response = try await couchDBClient.get(dbName: testsDB, uri: expectedInsertId) + let response = try await couchDBClient.get(dbName: testsDB, uri: expectedInsertId) XCTAssertNotNil(response.body) - let bytes = response.body!.readBytes(length: response.body!.readableBytes)! - testDoc = try JSONDecoder().decode(ExpectedDoc.self, from: Data(bytes)) + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) ?? 1024 * 1024 * 10 + var bytes = try await response.body.collect(upTo: expectedBytes) + let data = bytes.readData(length: bytes.readableBytes) + + testDoc = try JSONDecoder().decode( + ExpectedDoc.self, + from: data! + ) XCTAssertEqual(expectedName, testDoc.name) XCTAssertEqual(testDoc._rev, expectedInsertRev) @@ -197,14 +209,20 @@ final class CouchDBClientTests: XCTestCase { XCTAssertNotEqual(updateResponse.rev, expectedInsertRev) XCTAssertEqual(updateResponse.id, expectedInsertId) - var getResponse = try await couchDBClient.get( + let getResponse = try await couchDBClient.get( dbName: testsDB, uri: expectedInsertId ) XCTAssertNotNil(getResponse.body) - let bytes = getResponse.body!.readBytes(length: getResponse.body!.readableBytes)! - testDoc = try JSONDecoder().decode(ExpectedDoc.self, from: Data(bytes)) + let expectedBytes = getResponse.headers.first(name: "content-length").flatMap(Int.init) ?? 1024 * 1024 * 10 + var bytes = try await getResponse.body.collect(upTo: expectedBytes) + let data = bytes.readData(length: bytes.readableBytes) + + testDoc = try JSONDecoder().decode( + ExpectedDoc.self, + from: data! + ) XCTAssertEqual(expectedName, testDoc.name) } catch let error { @@ -249,7 +267,7 @@ final class CouchDBClientTests: XCTestCase { let insertEncodedData = try JSONEncoder().encode(testDoc) let insertResponse = try await couchDBClient.insert( dbName: testsDB, - body: .data(insertEncodedData) + body: .bytes(ByteBuffer(data: insertEncodedData)) ) let selector = ["selector": ["name": "Greg"]] @@ -278,7 +296,7 @@ final class CouchDBClientTests: XCTestCase { let insertEncodedData = try JSONEncoder().encode(testDoc) let insertResponse = try await couchDBClient.insert( dbName: testsDB, - body: .data(insertEncodedData) + body: .bytes(ByteBuffer(data: insertEncodedData)) ) let selector = ["selector": ["name": "Sam"]] From dd00301d52db835f3f1aea5d7a1778b11c46bac2 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 21:14:30 +0300 Subject: [PATCH 32/63] migrating to new HTTPClientRequest from HTTPClient.Request wip --- Sources/CouchDBClient/CouchDBClient.swift | 25 ++++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 68c9d08..56d188f 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -631,11 +631,12 @@ public class CouchDBClient { /// /// // encode document into JSON string /// let data = try encoder.encode(updatedData) + /// let body: HTTPClientRequest.Body = .bytes(ByteBuffer(data: data)) /// /// let response = try await couchDBClient.update( /// dbName: testsDB, /// uri: doc._id!, - /// body: .data(data) + /// body: body /// ) /// /// print(response) @@ -645,10 +646,10 @@ public class CouchDBClient { /// - Parameters: /// - dbName: DB name. /// - uri: URI (view or document id). - /// - body: Request body data. New will be created if nil value provided. + /// - body: Request body data. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Update response. - public func update(dbName: String, uri: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> CouchUpdateResponse { + public func update(dbName: String, uri: String, body: HTTPClientRequest.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> CouchUpdateResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) let httpClient: HTTPClient @@ -665,22 +666,24 @@ public class CouchDBClient { } let url = buildUrl(path: "/" + dbName + "/" + uri) - var request = try buildRequest(fromUrl: url, withMethod: .PUT) + var request = try buildRequestNew(fromUrl: url, withMethod: .PUT) request.body = body let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized } - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) + + guard let data = bytes.readData(length: bytes.readableBytes) else { + throw CouchDBClientError.noData } - let data = Data(bytes) let decoder = JSONDecoder() do { @@ -737,10 +740,12 @@ public class CouchDBClient { encoder.dateEncodingStrategy = dateEncodingStrategy let encodedData = try encoder.encode(doc) + let body: HTTPClientRequest.Body = .bytes(ByteBuffer(data: encodedData)) + let updateResponse = try await update( dbName: dbName, uri: id, - body: .data(encodedData), + body: body, eventLoopGroup: eventLoopGroup ) From febe7b3071c0022f0143c942cec69dd0c9c70283 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 21:15:02 +0300 Subject: [PATCH 33/63] =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tests/CouchDBClientTests/CouchDBClientTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index 6e72bd8..c6f08dd 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -198,10 +198,12 @@ final class CouchDBClientTests: XCTestCase { do { let updateEncodedData = try JSONEncoder().encode(testDoc) + let body: HTTPClientRequest.Body = .bytes(ByteBuffer(data: updateEncodedData)) + let updateResponse = try await couchDBClient.update( dbName: testsDB, uri: expectedInsertId, - body: .data(updateEncodedData) + body: body ) XCTAssertFalse(updateResponse.rev.isEmpty) From 5260938b8e0e67dfbd7a72cf327a11dba7bd5ef0 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 21:30:44 +0300 Subject: [PATCH 34/63] migrating to new HTTPClientRequest from HTTPClient.Request wip --- Sources/CouchDBClient/CouchDBClient.swift | 50 +++++++------------ .../CouchDBClientTests.swift | 26 ++++++++-- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 56d188f..add3761 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -532,18 +532,22 @@ public class CouchDBClient { public func find(in dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { let encoder = JSONEncoder() let selectorData = try encoder.encode(selector) + let requestBody: HTTPClientRequest.Body = .bytes(ByteBuffer(data: selectorData)) let findResponse = try await find( in: dbName, - body: .data(selectorData), + body: requestBody, eventLoopGroup: eventLoopGroup ) - guard var body = findResponse.body, let bytes = body.readBytes(length: body.readableBytes) else { - throw CouchDBClientError.unknownResponse + let body = findResponse.body + let expectedBytes = findResponse.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) + + guard let data = bytes.readData(length: bytes.readableBytes) else { + throw CouchDBClientError.noData } - let data = Data(bytes) let decoder = JSONDecoder() decoder.dateDecodingStrategy = dateDecodingStrategy @@ -574,7 +578,7 @@ public class CouchDBClient { /// - body: Request body data. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Request response. - public func find(in dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { + public func find(in dbName: String, body: HTTPClientRequest.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClientResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) let httpClient: HTTPClient @@ -591,11 +595,10 @@ public class CouchDBClient { } let url = buildUrl(path: "/" + dbName + "/_find", query: []) - var request = try buildRequest(fromUrl: url, withMethod: .POST) + var request = try buildRequestNew(fromUrl: url, withMethod: .POST) request.body = body let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized @@ -922,21 +925,23 @@ public class CouchDBClient { let url = buildUrl(path: "/" + dbName + "/" + uri, query: [ URLQueryItem(name: "rev", value: rev) ]) - let request = try self.buildRequest(fromUrl: url, withMethod: .DELETE) + let request = try self.buildRequestNew(fromUrl: url, withMethod: .DELETE) let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized } - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) + + guard let data = bytes.readData(length: bytes.readableBytes) else { return CouchUpdateResponse(ok: false, id: "", rev: "") } - let data = Data(bytes) return try JSONDecoder().decode(CouchUpdateResponse.self, from: data) } @@ -1058,25 +1063,6 @@ internal extension CouchDBClient { return authData } - /// Build HTTP request from url string. - /// - Parameters: - /// - url: URL string. - /// - method: HTTP method. - /// - Returns: HTTP Request. - func buildRequest(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClient.Request { - var headers = HTTPHeaders() - headers.add(name: "Content-Type", value: "application/json") - if let cookie = sessionCookie { - headers.add(name: "Cookie", value: cookie) - } - return try HTTPClient.Request( - url: url, - method: method, - headers: headers, - body: nil - ) - } - func buildRequestNew(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClientRequest { var headers = HTTPHeaders() headers.add(name: "Content-Type", value: "application/json") diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index c6f08dd..8fc3136 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -272,16 +272,32 @@ final class CouchDBClientTests: XCTestCase { body: .bytes(ByteBuffer(data: insertEncodedData)) ) + try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2) + let selector = ["selector": ["name": "Greg"]] let bodyData = try JSONEncoder().encode(selector) - var findResponse = try await couchDBClient.find(in: testsDB, body: .data(bodyData)) + let requestBody: HTTPClientRequest.Body = .bytes(ByteBuffer(data: bodyData)) + + let findResponse = try await couchDBClient.find( + in: testsDB, + body: requestBody + ) + + let body = findResponse.body + let expectedBytes = findResponse.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) - let bytes = findResponse.body!.readBytes(length: findResponse.body!.readableBytes)! - let decodedResponse = try JSONDecoder().decode(CouchDBFindResponse.self, from: Data(bytes)) + guard let data = bytes.readData(length: bytes.readableBytes) else { + throw CouchDBClientError.noData + } + + let decodedResponse = try JSONDecoder().decode(CouchDBFindResponse.self, from: data) XCTAssertTrue(decodedResponse.docs.count > 0) XCTAssertEqual(decodedResponse.docs.first!._id, insertResponse.id) + try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2) + _ = try await couchDBClient.delete( fromDb: testsDB, uri: decodedResponse.docs.first!._id!, @@ -301,12 +317,16 @@ final class CouchDBClientTests: XCTestCase { body: .bytes(ByteBuffer(data: insertEncodedData)) ) + try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2) + let selector = ["selector": ["name": "Sam"]] let docs: [ExpectedDoc] = try await couchDBClient.find(in: testsDB, selector: selector) XCTAssertTrue(docs.count > 0) XCTAssertEqual(docs.first!._id, insertResponse.id) + try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2) + _ = try await couchDBClient.delete( fromDb: testsDB, uri: docs.first!._id!, From c1cd49a6160f0a047151a6bce4fe33a3ebe34c32 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Tue, 2 Apr 2024 21:31:21 +0300 Subject: [PATCH 35/63] rename --- Sources/CouchDBClient/CouchDBClient.swift | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index add3761..7d4327a 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -176,7 +176,7 @@ public class CouchDBClient { let url = buildUrl(path: "/_all_dbs") - let request = try buildRequestNew(fromUrl: url, withMethod: .GET) + let request = try buildRequest(fromUrl: url, withMethod: .GET) let response = try await httpClient .execute(request, timeout: .seconds(requestsTimeout)) @@ -223,7 +223,7 @@ public class CouchDBClient { } let url = buildUrl(path: "/" + dbName) - let request = try buildRequestNew(fromUrl: url, withMethod: .HEAD) + let request = try buildRequest(fromUrl: url, withMethod: .HEAD) let response = try await httpClient .execute(request, timeout: .seconds(requestsTimeout)) @@ -263,7 +263,7 @@ public class CouchDBClient { let url = buildUrl(path: "/\(dbName)") - let request = try self.buildRequestNew(fromUrl: url, withMethod: .PUT) + let request = try self.buildRequest(fromUrl: url, withMethod: .PUT) let response = try await httpClient .execute(request, timeout: .seconds(requestsTimeout)) @@ -322,7 +322,7 @@ public class CouchDBClient { let url = buildUrl(path: "/\(dbName)") - let request = try self.buildRequestNew(fromUrl: url, withMethod: .DELETE) + let request = try self.buildRequest(fromUrl: url, withMethod: .DELETE) let response = try await httpClient .execute(request, timeout: .seconds(requestsTimeout)) @@ -447,7 +447,7 @@ public class CouchDBClient { } let url = buildUrl(path: "/" + dbName + "/" + uri, query: queryItems ?? []) - let request = try buildRequestNew(fromUrl: url, withMethod: .GET) + let request = try buildRequest(fromUrl: url, withMethod: .GET) let response = try await httpClient .execute(request, timeout: .seconds(requestsTimeout)) @@ -595,7 +595,7 @@ public class CouchDBClient { } let url = buildUrl(path: "/" + dbName + "/_find", query: []) - var request = try buildRequestNew(fromUrl: url, withMethod: .POST) + var request = try buildRequest(fromUrl: url, withMethod: .POST) request.body = body let response = try await httpClient .execute(request, timeout: .seconds(requestsTimeout)) @@ -669,7 +669,7 @@ public class CouchDBClient { } let url = buildUrl(path: "/" + dbName + "/" + uri) - var request = try buildRequestNew(fromUrl: url, withMethod: .PUT) + var request = try buildRequest(fromUrl: url, withMethod: .PUT) request.body = body let response = try await httpClient @@ -812,7 +812,7 @@ public class CouchDBClient { let url = buildUrl(path: "/\(dbName)") - var request = try self.buildRequestNew(fromUrl: url, withMethod: .POST) + var request = try self.buildRequest(fromUrl: url, withMethod: .POST) request.body = body let response = try await httpClient @@ -925,7 +925,7 @@ public class CouchDBClient { let url = buildUrl(path: "/" + dbName + "/" + uri, query: [ URLQueryItem(name: "rev", value: rev) ]) - let request = try self.buildRequestNew(fromUrl: url, withMethod: .DELETE) + let request = try self.buildRequest(fromUrl: url, withMethod: .DELETE) let response = try await httpClient .execute(request, timeout: .seconds(requestsTimeout)) @@ -1063,7 +1063,7 @@ internal extension CouchDBClient { return authData } - func buildRequestNew(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClientRequest { + func buildRequest(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClientRequest { var headers = HTTPHeaders() headers.add(name: "Content-Type", value: "application/json") if let cookie = sessionCookie { From d9e4ec872ff4497c9566f43887df5a5eaa4a6b2f Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 19:42:16 +0300 Subject: [PATCH 36/63] tests updated --- Tests/CouchDBClientTests/CouchDBClientTests.swift | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index 8fc3136..8cfa1f9 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -272,7 +272,6 @@ final class CouchDBClientTests: XCTestCase { body: .bytes(ByteBuffer(data: insertEncodedData)) ) - try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2) let selector = ["selector": ["name": "Greg"]] let bodyData = try JSONEncoder().encode(selector) @@ -294,14 +293,12 @@ final class CouchDBClientTests: XCTestCase { let decodedResponse = try JSONDecoder().decode(CouchDBFindResponse.self, from: data) XCTAssertTrue(decodedResponse.docs.count > 0) - XCTAssertEqual(decodedResponse.docs.first!._id, insertResponse.id) - - try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2) + XCTAssertTrue(decodedResponse.docs.contains(where: { $0._id == insertResponse.id })) _ = try await couchDBClient.delete( fromDb: testsDB, - uri: decodedResponse.docs.first!._id!, - rev: decodedResponse.docs.first!._rev! + uri: insertResponse.id, + rev: insertResponse.rev ) } catch { XCTFail(error.localizedDescription) @@ -317,16 +314,12 @@ final class CouchDBClientTests: XCTestCase { body: .bytes(ByteBuffer(data: insertEncodedData)) ) - try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2) - let selector = ["selector": ["name": "Sam"]] let docs: [ExpectedDoc] = try await couchDBClient.find(in: testsDB, selector: selector) XCTAssertTrue(docs.count > 0) XCTAssertEqual(docs.first!._id, insertResponse.id) - try await Task.sleep(nanoseconds: NSEC_PER_SEC * 2) - _ = try await couchDBClient.delete( fromDb: testsDB, uri: docs.first!._id!, From 20497ebd68a42a2ad95c867f792dd602a3153638 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 19:42:37 +0300 Subject: [PATCH 37/63] auth fixed --- Sources/CouchDBClient/CouchDBClient.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 7d4327a..6351525 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -1056,9 +1056,14 @@ internal extension CouchDBClient { sessionCookie = cookie - guard var body = response.body, let bytes = body.readBytes(length: body.readableBytes) else { return nil } + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) + var bytes = try await body.collect(upTo: expectedBytes ?? 1024 * 1024 * 10) + + guard let data = bytes.readData(length: bytes.readableBytes) else { + throw CouchDBClientError.noData + } - let data = Data(bytes) authData = try JSONDecoder().decode(CreateSessionResponse.self, from: data) return authData } From d8eab77fc320c7f8d194dc47272459e94421e4a1 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 19:42:55 +0300 Subject: [PATCH 38/63] collect body bytes before returning response --- Sources/CouchDBClient/CouchDBClient.swift | 26 +++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 6351525..fa4fe87 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -448,13 +448,20 @@ public class CouchDBClient { let url = buildUrl(path: "/" + dbName + "/" + uri, query: queryItems ?? []) let request = try buildRequest(fromUrl: url, withMethod: .GET) - let response = try await httpClient + var response = try await httpClient .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized } + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) ?? 1024 * 1024 * 10 + + response.body = .bytes( + try await body.collect(upTo: expectedBytes) + ) + return response } @@ -597,13 +604,20 @@ public class CouchDBClient { let url = buildUrl(path: "/" + dbName + "/_find", query: []) var request = try buildRequest(fromUrl: url, withMethod: .POST) request.body = body - let response = try await httpClient + var response = try await httpClient .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized } + let body = response.body + let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) ?? 1024 * 1024 * 10 + + response.body = .bytes( + try await body.collect(upTo: expectedBytes) + ) + return response } @@ -1015,14 +1029,14 @@ internal extension CouchDBClient { let url = buildUrl(path: "/_session") - var request = try HTTPClient.Request(url:url, method: .POST) + var request = HTTPClientRequest(url: url) + request.method = .POST request.headers.add(name: "Content-Type", value: "application/x-www-form-urlencoded") let dataString = "name=\(userName)&password=\(userPassword)" - request.body = HTTPClient.Body.string(dataString) + request.body = .bytes(ByteBuffer(string: dataString)) let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() + .execute(request, timeout: .seconds(requestsTimeout)) if response.status == .unauthorized { throw CouchDBClientError.unauthorized From 85208260a5e74b5ef3c5706b0bfc0758882bf9e3 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 19:54:39 +0300 Subject: [PATCH 39/63] param renamed to make keep backward compatibility with old methods --- .../CouchDBClient/CouchDB+Deprecated.swift | 153 +++++++++++++++++- Sources/CouchDBClient/CouchDBClient.swift | 10 +- .../CouchDBClientTests.swift | 8 +- 3 files changed, 161 insertions(+), 10 deletions(-) diff --git a/Sources/CouchDBClient/CouchDB+Deprecated.swift b/Sources/CouchDBClient/CouchDB+Deprecated.swift index 5d1473a..ca227bb 100644 --- a/Sources/CouchDBClient/CouchDB+Deprecated.swift +++ b/Sources/CouchDBClient/CouchDB+Deprecated.swift @@ -8,7 +8,158 @@ import Foundation import AsyncHTTPClient import NIO +import NIOHTTP1 extension CouchDBClient { - + /// Get data from DB. + /// + /// Examples: + /// + /// Define your document model: + /// ```swift + /// // Example struct + /// struct ExpectedDoc: CouchDBRepresentable, Codable { + /// var name: String + /// var _id: String? + /// var _rev: String? + /// } + /// ``` + /// + /// Get document by ID: + /// ```swift + /// // get data from DB by document ID + /// var response = try await couchDBClient.get(dbName: "databaseName", uri: "documentId") + /// + /// // parse JSON + /// let bytes = response.body!.readBytes(length: response.body!.readableBytes)! + /// let doc = try JSONDecoder().decode(ExpectedDoc.self, from: Data(bytes)) + /// ``` + /// + /// You can also provide CouchDB view document as uri and key in query. + /// Define Row and RowsResponse models: + /// ```swift + /// struct Row: Codable { + /// let value: ExpectedDoc + /// } + /// + /// struct RowsResponse: Codable { + /// let total_rows: Int + /// let offset: Int + /// let rows: [Row] + /// } + /// ``` + /// + /// Get data and parse RowsResponse: + /// ```swift + /// let response = try await couchDBClient.get( + /// dbName: "databaseName", + /// uri: "_design/all/_view/by_url", + /// query: ["key": "\"\(url)\""] + /// ) + /// let bytes = response.body!.readBytes(length: response.body!.readableBytes)! + /// let decodedResponse = try JSONDecoder().decode(RowsResponse.self, from: data) + /// print(decodedResponse.rows) + /// print(decodedResponse.rows.first?.value) + /// ``` + /// + /// - Parameters: + /// - dbName: DB name. + /// - uri: URI (view or document id). + /// - query: Request query items. + /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. + /// - Returns: Request response. + @available(*, deprecated, message: "Use the new get(fromDB:uri:queryItems) method.") + public func get(dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { + try await authIfNeed(eventLoopGroup: eventLoopGroup) + + let httpClient: HTTPClient + if let eventLoopGroup = eventLoopGroup { + httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + } else { + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + } + + defer { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } + } + + let url = buildUrl(path: "/" + dbName + "/" + uri, query: queryItems ?? []) + let request = try buildRequestOld(fromUrl: url, withMethod: .GET) + let response = try await httpClient + .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) + .get() + + if response.status == .unauthorized { + throw CouchDBClientError.unauthorized + } + + return response + } + + /// Find data in DB by selector. + /// + /// Example: + /// ```swift + /// let selector = ["selector": ["name": "Greg"]] + /// let bodyData = try JSONEncoder().encode(selector) + /// var findResponse = try await couchDBClient.find(in: testsDB, body: .data(bodyData)) + /// + /// let bytes = findResponse.body!.readBytes(length: findResponse.body!.readableBytes)! + /// let docs = try JSONDecoder().decode(CouchDBFindResponse.self, from: Data(bytes)).docs + /// ``` + /// - Parameters: + /// - dbName: DB name. + /// - body: Request body data. + /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. + /// - Returns: Request response. + public func find(in dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { + try await authIfNeed(eventLoopGroup: eventLoopGroup) + + let httpClient: HTTPClient + if let eventLoopGroup = eventLoopGroup { + httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + } else { + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + } + + defer { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } + } + + let url = buildUrl(path: "/" + dbName + "/_find", query: []) + var request = try buildRequestOld(fromUrl: url, withMethod: .POST) + request.body = body + let response = try await httpClient + .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) + .get() + + if response.status == .unauthorized { + throw CouchDBClientError.unauthorized + } + + return response + } + + /// Build HTTP request from url string. + /// - Parameters: + /// - url: URL string. + /// - method: HTTP method. + /// - Returns: HTTP Request. + func buildRequestOld(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClient.Request { + var headers = HTTPHeaders() + headers.add(name: "Content-Type", value: "application/json") + if let cookie = sessionCookie { + headers.add(name: "Cookie", value: cookie) + } + return try HTTPClient.Request( + url: url, + method: method, + headers: headers, + body: nil + ) + } } diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index fa4fe87..7c97181 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -85,7 +85,7 @@ public class CouchDBClient { /// Base URL. private var couchBaseURL: String = "" /// Session cookie for requests that needs authorization. - private var sessionCookie: String? + internal var sessionCookie: String? /// Session cookie as Cookie struct internal var sessionCookieExpires: Date? /// CouchDB user name. @@ -430,7 +430,7 @@ public class CouchDBClient { /// - query: Request query items. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Request response. - public func get(dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClientResponse { + public func get(fromDB dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClientResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) let httpClient: HTTPClient @@ -493,7 +493,7 @@ public class CouchDBClient { /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: An object or a struct (of generic type) parsed from JSON. public func get (dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { - let response: HTTPClientResponse = try await get(dbName: dbName, uri: uri, queryItems: queryItems, eventLoopGroup: eventLoopGroup) + let response: HTTPClientResponse = try await get(fromDB: dbName, uri: uri, queryItems: queryItems, eventLoopGroup: eventLoopGroup) if response.status == .unauthorized { throw CouchDBClientError.unauthorized @@ -542,7 +542,7 @@ public class CouchDBClient { let requestBody: HTTPClientRequest.Body = .bytes(ByteBuffer(data: selectorData)) let findResponse = try await find( - in: dbName, + inDB: dbName, body: requestBody, eventLoopGroup: eventLoopGroup ) @@ -585,7 +585,7 @@ public class CouchDBClient { /// - body: Request body data. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Request response. - public func find(in dbName: String, body: HTTPClientRequest.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClientResponse { + public func find(inDB dbName: String, body: HTTPClientRequest.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClientResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) let httpClient: HTTPClient diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index 8cfa1f9..563fc97 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -116,7 +116,7 @@ final class CouchDBClientTests: XCTestCase { // get updated doc let getResponse2 = try await couchDBClient.get( - dbName: testsDB, + fromDB: testsDB, uri: expectedInsertId ) XCTAssertNotNil(getResponse2.body) @@ -173,7 +173,7 @@ final class CouchDBClientTests: XCTestCase { // Test Get var expectedName = testDoc.name do { - let response = try await couchDBClient.get(dbName: testsDB, uri: expectedInsertId) + let response = try await couchDBClient.get(fromDB: testsDB, uri: expectedInsertId) XCTAssertNotNil(response.body) let expectedBytes = response.headers.first(name: "content-length").flatMap(Int.init) ?? 1024 * 1024 * 10 @@ -212,7 +212,7 @@ final class CouchDBClientTests: XCTestCase { XCTAssertEqual(updateResponse.id, expectedInsertId) let getResponse = try await couchDBClient.get( - dbName: testsDB, + fromDB: testsDB, uri: expectedInsertId ) XCTAssertNotNil(getResponse.body) @@ -278,7 +278,7 @@ final class CouchDBClientTests: XCTestCase { let requestBody: HTTPClientRequest.Body = .bytes(ByteBuffer(data: bodyData)) let findResponse = try await couchDBClient.find( - in: testsDB, + inDB: testsDB, body: requestBody ) From 7af5c33c5d11f7015c7c075ca2281515fa222ea1 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 19:55:17 +0300 Subject: [PATCH 40/63] marking old find as deprecated --- .../CouchDBClient/CouchDB+Deprecated.swift | 109 +++++++++--------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/Sources/CouchDBClient/CouchDB+Deprecated.swift b/Sources/CouchDBClient/CouchDB+Deprecated.swift index ca227bb..63ca4d5 100644 --- a/Sources/CouchDBClient/CouchDB+Deprecated.swift +++ b/Sources/CouchDBClient/CouchDB+Deprecated.swift @@ -99,67 +99,68 @@ extension CouchDBClient { } /// Find data in DB by selector. - /// - /// Example: - /// ```swift - /// let selector = ["selector": ["name": "Greg"]] - /// let bodyData = try JSONEncoder().encode(selector) - /// var findResponse = try await couchDBClient.find(in: testsDB, body: .data(bodyData)) - /// - /// let bytes = findResponse.body!.readBytes(length: findResponse.body!.readableBytes)! - /// let docs = try JSONDecoder().decode(CouchDBFindResponse.self, from: Data(bytes)).docs - /// ``` - /// - Parameters: - /// - dbName: DB name. - /// - body: Request body data. - /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Request response. - public func find(in dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { - try await authIfNeed(eventLoopGroup: eventLoopGroup) + /// + /// Example: + /// ```swift + /// let selector = ["selector": ["name": "Greg"]] + /// let bodyData = try JSONEncoder().encode(selector) + /// var findResponse = try await couchDBClient.find(in: testsDB, body: .data(bodyData)) + /// + /// let bytes = findResponse.body!.readBytes(length: findResponse.body!.readableBytes)! + /// let docs = try JSONDecoder().decode(CouchDBFindResponse.self, from: Data(bytes)).docs + /// ``` + /// - Parameters: + /// - dbName: DB name. + /// - body: Request body data. + /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. + /// - Returns: Request response. + @available(*, deprecated, message: "Use the new get(fromDB:uri:queryItems) method.") + public func find(in dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { + try await authIfNeed(eventLoopGroup: eventLoopGroup) - let httpClient: HTTPClient - if let eventLoopGroup = eventLoopGroup { - httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) - } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) - } + let httpClient: HTTPClient + if let eventLoopGroup = eventLoopGroup { + httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) + } else { + httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + } - defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() - } + defer { + DispatchQueue.main.async { + try? httpClient.syncShutdown() } + } - let url = buildUrl(path: "/" + dbName + "/_find", query: []) - var request = try buildRequestOld(fromUrl: url, withMethod: .POST) - request.body = body - let response = try await httpClient - .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) - .get() - - if response.status == .unauthorized { - throw CouchDBClientError.unauthorized - } + let url = buildUrl(path: "/" + dbName + "/_find", query: []) + var request = try buildRequestOld(fromUrl: url, withMethod: .POST) + request.body = body + let response = try await httpClient + .execute(request: request, deadline: .now() + .seconds(requestsTimeout)) + .get() - return response + if response.status == .unauthorized { + throw CouchDBClientError.unauthorized } + return response + } + /// Build HTTP request from url string. - /// - Parameters: - /// - url: URL string. - /// - method: HTTP method. - /// - Returns: HTTP Request. - func buildRequestOld(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClient.Request { - var headers = HTTPHeaders() - headers.add(name: "Content-Type", value: "application/json") - if let cookie = sessionCookie { - headers.add(name: "Cookie", value: cookie) - } - return try HTTPClient.Request( - url: url, - method: method, - headers: headers, - body: nil - ) + /// - Parameters: + /// - url: URL string. + /// - method: HTTP method. + /// - Returns: HTTP Request. + func buildRequestOld(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClient.Request { + var headers = HTTPHeaders() + headers.add(name: "Content-Type", value: "application/json") + if let cookie = sessionCookie { + headers.add(name: "Cookie", value: cookie) } + return try HTTPClient.Request( + url: url, + method: method, + headers: headers, + body: nil + ) + } } From ca0caa2cee38ea026f2983b9f367b39f1148f63f Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 19:55:32 +0300 Subject: [PATCH 41/63] private --- Sources/CouchDBClient/CouchDB+Deprecated.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CouchDBClient/CouchDB+Deprecated.swift b/Sources/CouchDBClient/CouchDB+Deprecated.swift index 63ca4d5..07b3680 100644 --- a/Sources/CouchDBClient/CouchDB+Deprecated.swift +++ b/Sources/CouchDBClient/CouchDB+Deprecated.swift @@ -150,7 +150,7 @@ extension CouchDBClient { /// - url: URL string. /// - method: HTTP method. /// - Returns: HTTP Request. - func buildRequestOld(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClient.Request { + private func buildRequestOld(fromUrl url: String, withMethod method: HTTPMethod) throws -> HTTPClient.Request { var headers = HTTPHeaders() headers.add(name: "Content-Type", value: "application/json") if let cookie = sessionCookie { From 78f56666e7bf2dc0f651bbaba9fd832f124b541c Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 19:58:21 +0300 Subject: [PATCH 42/63] deprecated message --- Sources/CouchDBClient/CouchDB+Deprecated.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CouchDBClient/CouchDB+Deprecated.swift b/Sources/CouchDBClient/CouchDB+Deprecated.swift index 07b3680..dc3b247 100644 --- a/Sources/CouchDBClient/CouchDB+Deprecated.swift +++ b/Sources/CouchDBClient/CouchDB+Deprecated.swift @@ -68,7 +68,7 @@ extension CouchDBClient { /// - query: Request query items. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Request response. - @available(*, deprecated, message: "Use the new get(fromDB:uri:queryItems) method.") + @available(*, deprecated, message: "Use the new `get(fromDB:uri:queryItems:eventLoopGroup)` method.") public func get(dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { try await authIfNeed(eventLoopGroup: eventLoopGroup) @@ -114,7 +114,7 @@ extension CouchDBClient { /// - body: Request body data. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Request response. - @available(*, deprecated, message: "Use the new get(fromDB:uri:queryItems) method.") + @available(*, deprecated, message: "Use the new 'find(inDB:body:eventLoopGroup)' method.") public func find(in dbName: String, body: HTTPClient.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClient.Response { try await authIfNeed(eventLoopGroup: eventLoopGroup) From a54dfcfd99b4e336e342b5eb987735410452a644 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 20:05:57 +0300 Subject: [PATCH 43/63] renames --- Sources/CouchDBClient/CouchDBClient.swift | 16 +++++++++++++--- .../CouchDBClientTests/CouchDBClientTests.swift | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 7c97181..5d02dca 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -465,6 +465,11 @@ public class CouchDBClient { return response } + @available(*, deprecated, renamed: "get", message: "Renamed to: get(fromDB:uri:queryItems:dateDecodingStrategy:eventLoopGroup)") + public func get (dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { + return try await get(fromDB: dbName, uri: uri, queryItems: queryItems, dateDecodingStrategy: dateDecodingStrategy, eventLoopGroup: eventLoopGroup) + } + /// Get a document from DB. It will parse JSON using provided generic type. Check an example in Discussion. /// @@ -492,7 +497,7 @@ public class CouchDBClient { /// - queryItems: Request query items. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: An object or a struct (of generic type) parsed from JSON. - public func get (dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { + public func get (fromDB dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { let response: HTTPClientResponse = try await get(fromDB: dbName, uri: uri, queryItems: queryItems, eventLoopGroup: eventLoopGroup) if response.status == .unauthorized { @@ -520,7 +525,12 @@ public class CouchDBClient { throw parsingError } } - + + @available(*, deprecated, renamed: "find", message: "Renamed to: find(inDB:selector:dateDecodingStrategy:eventLoopGroup)") + public func find(in dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { + return try await find(inDB: dbName, selector: selector, dateDecodingStrategy: dateDecodingStrategy, eventLoopGroup: eventLoopGroup) + } + /// Find data in DB by selector. /// /// Example: @@ -536,7 +546,7 @@ public class CouchDBClient { /// - selector: Codable representation of json selector query. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Array of documents [T]. - public func find(in dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { + public func find(inDB dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { let encoder = JSONEncoder() let selectorData = try encoder.encode(selector) let requestBody: HTTPClientRequest.Body = .bytes(ByteBuffer(data: selectorData)) diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index 563fc97..6da3631 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -85,7 +85,7 @@ final class CouchDBClientTests: XCTestCase { // get inserted doc do { - testDoc = try await couchDBClient.get(dbName: testsDB, uri: expectedInsertId) + testDoc = try await couchDBClient.get(fromDB: testsDB, uri: expectedInsertId) } catch CouchDBClientError.getError(let error) { XCTFail(error.reason) return @@ -315,7 +315,7 @@ final class CouchDBClientTests: XCTestCase { ) let selector = ["selector": ["name": "Sam"]] - let docs: [ExpectedDoc] = try await couchDBClient.find(in: testsDB, selector: selector) + let docs: [ExpectedDoc] = try await couchDBClient.find(inDB: testsDB, selector: selector) XCTAssertTrue(docs.count > 0) XCTAssertEqual(docs.first!._id, insertResponse.id) From d6c9c9c9a8cb8c06d9a97184948918f542c6c78b Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 20:08:25 +0300 Subject: [PATCH 44/63] moved deprecations --- Sources/CouchDBClient/CouchDB+Deprecated.swift | 10 ++++++++++ Sources/CouchDBClient/CouchDBClient.swift | 11 ----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Sources/CouchDBClient/CouchDB+Deprecated.swift b/Sources/CouchDBClient/CouchDB+Deprecated.swift index dc3b247..e095653 100644 --- a/Sources/CouchDBClient/CouchDB+Deprecated.swift +++ b/Sources/CouchDBClient/CouchDB+Deprecated.swift @@ -163,4 +163,14 @@ extension CouchDBClient { body: nil ) } + + @available(*, deprecated, renamed: "get", message: "Renamed to: get(fromDB:uri:queryItems:dateDecodingStrategy:eventLoopGroup)") + public func get (dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { + return try await get(fromDB: dbName, uri: uri, queryItems: queryItems, dateDecodingStrategy: dateDecodingStrategy, eventLoopGroup: eventLoopGroup) + } + + @available(*, deprecated, renamed: "find", message: "Renamed to: find(inDB:selector:dateDecodingStrategy:eventLoopGroup)") + public func find(in dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { + return try await find(inDB: dbName, selector: selector, dateDecodingStrategy: dateDecodingStrategy, eventLoopGroup: eventLoopGroup) + } } diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 5d02dca..d9fac50 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -465,12 +465,6 @@ public class CouchDBClient { return response } - @available(*, deprecated, renamed: "get", message: "Renamed to: get(fromDB:uri:queryItems:dateDecodingStrategy:eventLoopGroup)") - public func get (dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { - return try await get(fromDB: dbName, uri: uri, queryItems: queryItems, dateDecodingStrategy: dateDecodingStrategy, eventLoopGroup: eventLoopGroup) - } - - /// Get a document from DB. It will parse JSON using provided generic type. Check an example in Discussion. /// /// Example: @@ -526,11 +520,6 @@ public class CouchDBClient { } } - @available(*, deprecated, renamed: "find", message: "Renamed to: find(inDB:selector:dateDecodingStrategy:eventLoopGroup)") - public func find(in dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { - return try await find(inDB: dbName, selector: selector, dateDecodingStrategy: dateDecodingStrategy, eventLoopGroup: eventLoopGroup) - } - /// Find data in DB by selector. /// /// Example: From eecb20f1d2e453bb393cdabf46e6874c511496d8 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Fri, 5 Apr 2024 20:10:22 +0300 Subject: [PATCH 45/63] docs --- Sources/CouchDBClient/CouchDBClient.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index d9fac50..5aa119f 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -404,7 +404,7 @@ public class CouchDBClient { /// Get data and parse RowsResponse: /// ```swift /// let response = try await couchDBClient.get( - /// dbName: "databaseName", + /// fromDB: "databaseName", /// uri: "_design/all/_view/by_url", /// query: ["key": "\"\(url)\""] /// ) @@ -482,7 +482,7 @@ public class CouchDBClient { /// Get document by ID: /// ```swift /// // get data from DB by document ID - /// let doc: ExpectedDoc = try await couchDBClient.get(dbName: "databaseName", uri: "documentId") + /// let doc: ExpectedDoc = try await couchDBClient.get(fromDB: "databaseName", uri: "documentId") /// ``` /// /// - Parameters: @@ -527,7 +527,7 @@ public class CouchDBClient { /// ```swift /// // find documents in DB by selector /// let selector = ["selector": ["name": "Sam"]] - /// let docs: [ExpectedDoc] = try await couchDBClient.find(in: testsDB, selector: selector) + /// let docs: [ExpectedDoc] = try await couchDBClient.find(inDB: testsDB, selector: selector) /// ``` /// /// - Parameters: @@ -574,7 +574,7 @@ public class CouchDBClient { /// ```swift /// let selector = ["selector": ["name": "Greg"]] /// let bodyData = try JSONEncoder().encode(selector) - /// var findResponse = try await couchDBClient.find(in: testsDB, body: .data(bodyData)) + /// var findResponse = try await couchDBClient.find(inDB: testsDB, body: .data(bodyData)) /// /// let bytes = findResponse.body!.readBytes(length: findResponse.body!.readableBytes)! /// let docs = try JSONDecoder().decode(CouchDBFindResponse.self, from: Data(bytes)).docs From 469247f9b1c1c5ca6fff12c92aead5433a73a2b6 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sat, 6 Apr 2024 22:05:49 +0300 Subject: [PATCH 46/63] docs --- Sources/CouchDBClient/Models/CouchDBRepresentable.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CouchDBClient/Models/CouchDBRepresentable.swift b/Sources/CouchDBClient/Models/CouchDBRepresentable.swift index 637e8a7..55b8c2e 100644 --- a/Sources/CouchDBClient/Models/CouchDBRepresentable.swift +++ b/Sources/CouchDBClient/Models/CouchDBRepresentable.swift @@ -7,7 +7,7 @@ import Foundation -/// Every CouchDB document should have **\_id** and **\_rev** properties. Both should be optional **String?** type. Unfortunatelly DocC ignores properties starting with `_` symbol so check the example in Overview section. +/// Every CouchDB document should have **\_id** and **\_rev** properties. Both should be optional **String?** type. Unfortunately DocC ignores properties starting with `_` symbol so check the example in the Overview section. /// /// Example: /// ```swift From 31187b51d1dd5697ad7885362aceb2ebf52b8363 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 21:50:39 +0300 Subject: [PATCH 47/63] added Codable to CouchDBRepresentable protocol --- Sources/CouchDBClient/Models/CouchDBRepresentable.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/CouchDBClient/Models/CouchDBRepresentable.swift b/Sources/CouchDBClient/Models/CouchDBRepresentable.swift index 55b8c2e..74409ee 100644 --- a/Sources/CouchDBClient/Models/CouchDBRepresentable.swift +++ b/Sources/CouchDBClient/Models/CouchDBRepresentable.swift @@ -12,13 +12,13 @@ import Foundation /// Example: /// ```swift /// // Example struct -/// struct ExpectedDoc: CouchDBRepresentable, Codable { +/// struct ExpectedDoc: CouchDBRepresentable { /// var name: String /// var _id: String? /// var _rev: String? /// } /// ``` -public protocol CouchDBRepresentable { +public protocol CouchDBRepresentable: Codable { /// Document ID. var _id: String? { get set } /// Revision MVCC token. From f33724089accb263acf2c9e32cc8858a527fd852 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 21:58:34 +0300 Subject: [PATCH 48/63] added RowsResponse model --- .../CouchDBClient/Models/RowsResponse.swift | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Sources/CouchDBClient/Models/RowsResponse.swift diff --git a/Sources/CouchDBClient/Models/RowsResponse.swift b/Sources/CouchDBClient/Models/RowsResponse.swift new file mode 100644 index 0000000..ef9a6a3 --- /dev/null +++ b/Sources/CouchDBClient/Models/RowsResponse.swift @@ -0,0 +1,18 @@ +// +// RowsResponse.swift +// +// +// Created by Sergei Armodin on 07.04.2024. +// + +import Foundation + +public struct RowsResponse: Codable { + public struct Row: Codable { + public let value: T + } + + public let total_rows: Int + public let offset: Int + public let rows: [Row] +} From 6cd2d096e02d5dea45e61c0130e8efdc672eb4ce Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 22:12:26 +0300 Subject: [PATCH 49/63] docs --- Sources/CouchDBClient/CouchDB+Deprecated.swift | 16 ++-------------- Sources/CouchDBClient/CouchDBClient.swift | 14 +------------- Sources/CouchDBClient/Models/RowsResponse.swift | 7 ++++++- 3 files changed, 9 insertions(+), 28 deletions(-) diff --git a/Sources/CouchDBClient/CouchDB+Deprecated.swift b/Sources/CouchDBClient/CouchDB+Deprecated.swift index e095653..3ef03e5 100644 --- a/Sources/CouchDBClient/CouchDB+Deprecated.swift +++ b/Sources/CouchDBClient/CouchDB+Deprecated.swift @@ -36,20 +36,8 @@ extension CouchDBClient { /// ``` /// /// You can also provide CouchDB view document as uri and key in query. - /// Define Row and RowsResponse models: - /// ```swift - /// struct Row: Codable { - /// let value: ExpectedDoc - /// } - /// - /// struct RowsResponse: Codable { - /// let total_rows: Int - /// let offset: Int - /// let rows: [Row] - /// } - /// ``` /// - /// Get data and parse RowsResponse: + /// Get data and parse `RowsResponse`: /// ```swift /// let response = try await couchDBClient.get( /// dbName: "databaseName", @@ -57,7 +45,7 @@ extension CouchDBClient { /// query: ["key": "\"\(url)\""] /// ) /// let bytes = response.body!.readBytes(length: response.body!.readableBytes)! - /// let decodedResponse = try JSONDecoder().decode(RowsResponse.self, from: data) + /// let decodedResponse = try JSONDecoder().decode(RowsResponse.self, from: data) /// print(decodedResponse.rows) /// print(decodedResponse.rows.first?.value) /// ``` diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 5aa119f..c26f764 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -388,18 +388,6 @@ public class CouchDBClient { /// ``` /// /// You can also provide CouchDB view document as uri and key in query. - /// Define Row and RowsResponse models: - /// ```swift - /// struct Row: Codable { - /// let value: ExpectedDoc - /// } - /// - /// struct RowsResponse: Codable { - /// let total_rows: Int - /// let offset: Int - /// let rows: [Row] - /// } - /// ``` /// /// Get data and parse RowsResponse: /// ```swift @@ -416,7 +404,7 @@ public class CouchDBClient { /// let data = bytes.readData(length: bytes.readableBytes) /// /// let decodedResponse = try JSONDecoder().decode( - /// RowsResponse.self, + /// RowsResponse.self, /// from: data! /// ) /// diff --git a/Sources/CouchDBClient/Models/RowsResponse.swift b/Sources/CouchDBClient/Models/RowsResponse.swift index ef9a6a3..328609b 100644 --- a/Sources/CouchDBClient/Models/RowsResponse.swift +++ b/Sources/CouchDBClient/Models/RowsResponse.swift @@ -7,12 +7,17 @@ import Foundation +/// Rows response model. public struct RowsResponse: Codable { public struct Row: Codable { + /// A CouchDB document. public let value: T } - + + /// Total documents in a response. public let total_rows: Int + /// Results offset. public let offset: Int + /// CouchDB documents. public let rows: [Row] } From 8d47cd21da39ed822c490260b31570265bbabebe Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 22:12:37 +0300 Subject: [PATCH 50/63] Vapor tutorial updated --- .../Tutorials/vapor/VaporTutorial-0.swift | 14 +------------- .../Tutorials/vapor/VaporTutorial-3.swift | 12 ------------ .../Tutorials/vapor/VaporTutorial-4.swift | 12 ------------ .../Tutorials/vapor/VaporTutorial-5.swift | 14 +------------- .../Tutorials/vapor/VaporTutorial-6.swift | 14 +------------- .../Tutorials/vapor/VaporTutorial.tutorial | 2 +- 6 files changed, 4 insertions(+), 64 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-0.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-0.swift index 6b3aaaa..9c467de 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-0.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-0.swift @@ -8,18 +8,6 @@ struct MyApp: Content, CouchDBRepresentable { let url: String let _id: String var _rev: String - - /// Row model for CouchDB - struct Row: Content { - let value: AppOnSiteModel - } - - /// Rows response - struct RowsResponse: Content { - let total_rows: Int - let offset: Int - let rows: [Row] - } } func routes(_ app: Application) throws { @@ -38,7 +26,7 @@ func routes(_ app: Application) throws { guard let bytes = response.body else { throw Abort(.notFound) } let data = Data(buffer: bytes) - let decodeResponse = try JSONDecoder().decode(RowsResponse.self, from: data) + let decodeResponse = try JSONDecoder().decode(RowsResponse.self, from: data) guard let myApp = decodeResponse.rows.first?.value else { throw Abort(.notFound) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-3.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-3.swift index c8983d0..bd1b9a1 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-3.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-3.swift @@ -8,18 +8,6 @@ struct MyApp: Content, CouchDBRepresentable { let url: String let _id: String var _rev: String - - /// Row model for CouchDB - struct Row: Content { - let value: MyApp - } - - /// Rows response - struct RowsResponse: Content { - let total_rows: Int - let offset: Int - let rows: [Row] - } } func routes(_ app: Application) throws { diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-4.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-4.swift index 62853e2..fe6b2cf 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-4.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-4.swift @@ -8,18 +8,6 @@ struct MyApp: Content, CouchDBRepresentable { let url: String let _id: String var _rev: String - - /// Row model for CouchDB - struct Row: Content { - let value: MyApp - } - - /// Rows response - struct RowsResponse: Content { - let total_rows: Int - let offset: Int - let rows: [Row] - } } func routes(_ app: Application) throws { diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-5.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-5.swift index 182c1f9..43ffbb9 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-5.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-5.swift @@ -8,18 +8,6 @@ struct MyApp: Content, CouchDBRepresentable { let url: String let _id: String var _rev: String - - /// Row model for CouchDB - struct Row: Content { - let value: MyApp - } - - /// Rows response - struct RowsResponse: Content { - let total_rows: Int - let offset: Int - let rows: [Row] - } } func routes(_ app: Application) throws { @@ -37,7 +25,7 @@ func routes(_ app: Application) throws { guard let bytes = response.body else { throw Abort(.notFound) } let data = Data(buffer: bytes) - let decodeResponse = try JSONDecoder().decode(RowsResponse.self, from: data) + let decodeResponse = try JSONDecoder().decode(RowsResponse.self, from: data) guard let myApp = decodeResponse.rows.first?.value else { throw Abort(.notFound) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-6.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-6.swift index 61917c2..8c3741d 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-6.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial-6.swift @@ -8,18 +8,6 @@ struct MyApp: Content, CouchDBRepresentable { let url: String let _id: String var _rev: String - - /// Row model for CouchDB - struct Row: Content { - let value: MyApp - } - - /// Rows response - struct RowsResponse: Content { - let total_rows: Int - let offset: Int - let rows: [Row] - } } func routes(_ app: Application) throws { @@ -37,7 +25,7 @@ func routes(_ app: Application) throws { guard let bytes = response.body else { throw Abort(.notFound) } let data = Data(buffer: bytes) - let decodeResponse = try JSONDecoder().decode(RowsResponse.self, from: data) + let decodeResponse = try JSONDecoder().decode(RowsResponse.self, from: data) guard let myApp = decodeResponse.rows.first?.value else { throw Abort(.notFound) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial index 14ba158..386d729 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial @@ -32,7 +32,7 @@ } @Step { - Define your data model for CouchDB documents. Nested `Row` and `RowsResponse` models will be used to parse CouchDB responses. + Define your data model for CouchDB documents. `RowsResponse` model from the `CouchDBClient` library will be used to parse CouchDB responses. @Code(name: "main.swift", file: VaporTutorial-3.swift) } From dba25fe618f91fce4d9e84cb341da90c389a6e94 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 22:17:55 +0300 Subject: [PATCH 51/63] docs --- .../CouchDBClient.docc/Extensions/Client.md | 14 ++++++++++---- Sources/CouchDBClient/CouchDBClient.swift | 8 ++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Extensions/Client.md b/Sources/CouchDBClient/CouchDBClient.docc/Extensions/Client.md index 65efbf7..35cd017 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Extensions/Client.md +++ b/Sources/CouchDBClient/CouchDBClient.docc/Extensions/Client.md @@ -21,13 +21,19 @@ A CouchDB client class with methods using Swift Concurrency. - ``createDB(_:eventLoopGroup:)`` - ``deleteDB(_:eventLoopGroup:)`` - ``dbExists(_:eventLoopGroup:)`` -- ``get(dbName:uri:queryItems:eventLoopGroup:)`` -- ``get(dbName:uri:queryItems:dateDecodingStrategy:eventLoopGroup:)`` +- ``get(fromDB:uri:queryItems:eventLoopGroup:)`` +- ``get(fromDB:uri:queryItems:dateDecodingStrategy:eventLoopGroup:)`` - ``insert(dbName:body:eventLoopGroup:)`` - ``insert(dbName:doc:dateEncodingStrategy:eventLoopGroup:)`` - ``update(dbName:doc:dateEncodingStrategy:eventLoopGroup:)`` - ``update(dbName:uri:body:eventLoopGroup:)`` -- ``find(in:body:eventLoopGroup:)`` -- ``find(in:selector:dateDecodingStrategy:eventLoopGroup:)`` +- ``find(inDB:body:eventLoopGroup:)`` +- ``find(inDB:selector:dateDecodingStrategy:eventLoopGroup:)`` - ``delete(fromDb:doc:eventLoopGroup:)`` - ``delete(fromDb:uri:rev:eventLoopGroup:)`` + +### Deprecated methods +- ``get(dbName:uri:queryItems:eventLoopGroup:)`` +- ``get(dbName:uri:queryItems:dateDecodingStrategy:eventLoopGroup:)`` +- ``find(in:body:eventLoopGroup:)`` +- ``find(in:selector:dateDecodingStrategy:eventLoopGroup:)`` diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index c26f764..4102795 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -114,7 +114,7 @@ public class CouchDBClient { /// userPassword: "myPassword" /// ) /// ``` - /// If you don't want to have your password in the code you can pass `COUCHDB_PASS` param in you command line. + /// If you don't want to have your password in the code you can pass `COUCHDB_PASS` param in your command line. /// For example you can run your Server Side Swift project: /// ```bash /// COUCHDB_PASS=myPassword /path/.build/x86_64-unknown-linux-gnu/release/Run @@ -130,9 +130,9 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - couchProtocol: Protocol for requests (check ``CouchDBProtocol`` enum for avaiable values). - /// - couchHost: Host of CouchDB instance. - /// - couchPort: Port CouchDB works on. + /// - couchProtocol: Protocol for requests (check the ``CouchDBProtocol`` enum for available values). + /// - couchHost: Host of the CouchDB instance. + /// - couchPort: Port that CouchDB works on. /// - userName: Username. /// - userPassword: User password. public init(couchProtocol: CouchDBProtocol = .http, couchHost: String = "127.0.0.1", couchPort: Int = 5984, userName: String = "", userPassword: String = "") { From ebab497b73716a902ac89e5774aaea474fc83ad7 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 22:50:35 +0300 Subject: [PATCH 52/63] docs and refactoring --- Sources/CouchDBClient/CouchDBClient.swift | 160 +++++++++++----------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 4102795..2e9d625 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -12,23 +12,23 @@ import AsyncHTTPClient /// CouchDB client errors. public enum CouchDBClientError: Error { - /// **id** property is empty or missing in provided document. + /// **id** property is empty or missing in the provided document. case idMissing - /// **\_rev** property is empty or missing in provided document. + /// **\_rev** property is empty or missing in the provided document. case revMissing - /// Get request wasn't successful. + /// The Get request wasn't successful. case getError(error: CouchDBError) - /// Insert request wasn't successful. + /// The Insert request wasn't successful. case insertError(error: CouchDBError) - /// Update request wasn't successful. + /// The Update request wasn't successful. case updateError(error: CouchDBError) - /// Find request wasn't successful. + /// The Find request wasn't successful. case findError(error: CouchDBError) - /// Uknown response from CouchDB. + /// Unknown response from CouchDB. case unknownResponse /// Wrong username or password. case unauthorized - /// Missing data in response body. + /// Missing data in the response body. case noData } @@ -36,23 +36,23 @@ extension CouchDBClientError: LocalizedError { public var errorDescription: String? { switch self { case .idMissing: - return "id property is empty or missing in provided document." + return "id property is empty or missing in the provided document." case .revMissing: - return "_rev property is empty or missing in provided document." + return "_rev property is empty or missing in the provided document." case .getError(let error): - return "Get request wasn't successful: \(error.localizedDescription)" + return "The Get request wasn't successful: \(error.localizedDescription)" case .insertError(let error): - return "Insert request wasn't successful: \(error.localizedDescription)" + return "The Insert request wasn't successful: \(error.localizedDescription)" case .updateError(let error): - return "Update request wasn't successful: \(error.localizedDescription)" + return "The Update request wasn't successful: \(error.localizedDescription)" case .findError(let error): - return "Find request wasn't successful: \(error.localizedDescription)" + return "The Find request wasn't successful: \(error.localizedDescription)" case .unknownResponse: - return "Uknown response from CouchDB." + return "Unknown response from CouchDB." case .unauthorized: return "Wrong username or password." case .noData: - return "Missing data in response body." + return "Missing data in the response body." } } } @@ -61,18 +61,18 @@ extension CouchDBClientError: LocalizedError { public class CouchDBClient { /// Protocol (URL scheme) that should be used to perform requests to CouchDB. public enum CouchDBProtocol: String { - /// Use HTTP protocol. + /// HTTP protocol. case http - /// Use HTTPS protocol. + /// HTTPS protocol. case https } // MARK: - Public properties - /// Flag if did authorize in CouchDB. + /// Flag if authorized in CouchDB. public var isAuthorized: Bool { authData?.ok ?? false } - /// You can set timeout for requests in seconds. Default value is 30. + /// You can set a timeout for requests in seconds. Default value is 30. public var requestsTimeout: Int64 = 30 // MARK: - Private properties @@ -84,9 +84,9 @@ public class CouchDBClient { private var couchPort: Int = 5984 /// Base URL. private var couchBaseURL: String = "" - /// Session cookie for requests that needs authorization. + /// Session cookie for requests that need authorization. internal var sessionCookie: String? - /// Session cookie as Cookie struct + /// Session cookie as Cookie struct. internal var sessionCookieExpires: Date? /// CouchDB user name. private var userName: String = "" @@ -149,7 +149,7 @@ public class CouchDBClient { // MARK: - Public methods - /// Get DBs list. + /// Get a database list. /// /// Example: /// ```swift @@ -157,7 +157,7 @@ public class CouchDBClient { /// ``` /// /// - Parameter eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Array of strings containing DBs names. + /// - Returns: Array of strings containing database names. public func getAllDBs(eventLoopGroup: EventLoopGroup? = nil) async throws -> [String] { try await authIfNeed(eventLoopGroup: eventLoopGroup) @@ -194,7 +194,7 @@ public class CouchDBClient { return try JSONDecoder().decode([String].self, from: data) } - /// Check if DB exists + /// Check if database exists. /// /// Example: /// @@ -203,7 +203,7 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - dbName: DB name. + /// - dbName: Database name. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: True or false. public func dbExists(_ dbName: String, eventLoopGroup: EventLoopGroup? = nil) async throws -> Bool { @@ -234,7 +234,7 @@ public class CouchDBClient { return response.status == .ok } - /// Create DB. + /// Create a database. /// /// Example: /// ```swift @@ -242,9 +242,9 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - dbName: DB name. + /// - dbName: Database name. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Request response. + /// - Returns: Creation response. @discardableResult public func createDB(_ dbName: String, eventLoopGroup: EventLoopGroup? = nil) async throws -> UpdateDBResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) @@ -293,7 +293,7 @@ public class CouchDBClient { } } - /// Delete DB. + /// Delete a database. /// /// Example: /// ```swift @@ -301,9 +301,9 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - dbName: DB name. + /// - dbName: Database name. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Request response. + /// - Returns: Deletion response. @discardableResult public func deleteDB(_ dbName: String, eventLoopGroup: EventLoopGroup? = nil) async throws -> UpdateDBResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) @@ -352,14 +352,14 @@ public class CouchDBClient { } } - /// Get data from DB. + /// Get data from a database. /// /// Examples: /// - /// Define your document model: + /// Define your document data model: /// ```swift /// // Example struct - /// struct ExpectedDoc: CouchDBRepresentable, Codable { + /// struct ExpectedDoc: CouchDBRepresentable { /// var name: String /// var _id: String? /// var _rev: String? @@ -387,9 +387,9 @@ public class CouchDBClient { /// ) /// ``` /// - /// You can also provide CouchDB view document as uri and key in query. + /// You can also provide a CouchDB view document as uri and key in the query. /// - /// Get data and parse RowsResponse: + /// Get data and parse `RowsResponse`: /// ```swift /// let response = try await couchDBClient.get( /// fromDB: "databaseName", @@ -413,11 +413,11 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - dbName: DB name. + /// - dbName: Database name. /// - uri: URI (view or document id). /// - query: Request query items. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Request response. + /// - Returns: Response. public func get(fromDB dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClientResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) @@ -453,14 +453,14 @@ public class CouchDBClient { return response } - /// Get a document from DB. It will parse JSON using provided generic type. Check an example in Discussion. + /// Get a document from a database. It will parse JSON using the provided generic type. Check an example in Discussion. /// /// Example: /// /// Define your document model: /// ```swift /// // Example struct - /// struct ExpectedDoc: CouchDBRepresentable, Codable { + /// struct ExpectedDoc: CouchDBRepresentable { /// var name: String /// var _id: String? /// var _rev: String? @@ -469,17 +469,17 @@ public class CouchDBClient { /// /// Get document by ID: /// ```swift - /// // get data from DB by document ID + /// // get data from the database by document ID /// let doc: ExpectedDoc = try await couchDBClient.get(fromDB: "databaseName", uri: "documentId") /// ``` /// /// - Parameters: - /// - dbName: DB name. + /// - dbName: Database name. /// - uri: URI (view or document id). /// - queryItems: Request query items. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: An object or a struct (of generic type) parsed from JSON. - public func get (fromDB dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { + public func get (fromDB dbName: String, uri: String, queryItems: [URLQueryItem]? = nil, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> T { let response: HTTPClientResponse = try await get(fromDB: dbName, uri: uri, queryItems: queryItems, eventLoopGroup: eventLoopGroup) if response.status == .unauthorized { @@ -508,22 +508,22 @@ public class CouchDBClient { } } - /// Find data in DB by selector. + /// Find data in a database by selector. /// /// Example: /// /// ```swift - /// // find documents in DB by selector + /// // find documents in the database by selector /// let selector = ["selector": ["name": "Sam"]] /// let docs: [ExpectedDoc] = try await couchDBClient.find(inDB: testsDB, selector: selector) /// ``` /// /// - Parameters: - /// - in dbName: DB name. - /// - selector: Codable representation of json selector query. + /// - in dbName: Database name. + /// - selector: Codable representation of the JSON selector query. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Array of documents [T]. - public func find(inDB dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { + /// - Returns: Array of `[T]` documents. + public func find(inDB dbName: String, selector: Codable, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil) async throws -> [T] { let encoder = JSONEncoder() let selectorData = try encoder.encode(selector) let requestBody: HTTPClientRequest.Body = .bytes(ByteBuffer(data: selectorData)) @@ -556,7 +556,7 @@ public class CouchDBClient { } } - /// Find data in DB by selector. + /// Find data in a database by selector. /// /// Example: /// ```swift @@ -568,10 +568,10 @@ public class CouchDBClient { /// let docs = try JSONDecoder().decode(CouchDBFindResponse.self, from: Data(bytes)).docs /// ``` /// - Parameters: - /// - dbName: DB name. + /// - dbName: Database name. /// - body: Request body data. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Request response. + /// - Returns: Response. public func find(inDB dbName: String, body: HTTPClientRequest.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> HTTPClientResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) @@ -608,14 +608,14 @@ public class CouchDBClient { return response } - /// Update data in DB. + /// Update data in a database. /// /// Examples: /// /// Define your document model: /// ```swift /// // Example struct - /// struct ExpectedDoc: CouchDBRepresentable, Codable { + /// struct ExpectedDoc: CouchDBRepresentable { /// var name: String /// var _id: String? /// var _rev: String? @@ -623,17 +623,17 @@ public class CouchDBClient { /// ``` /// Get document by ID and update it: /// ```swift - /// // get data from DB by document ID + /// // get data from the database by document ID /// var response = try await couchDBClient.get(dbName: "databaseName", uri: "documentId") /// /// // parse JSON /// let bytes = response.body!.readBytes(length: response.body!.readableBytes)! /// var doc = try JSONDecoder().decode(ExpectedDoc.self, from: Data(bytes)) /// - /// // Update value + /// // update some value /// doc.name = "Updated name" /// - /// // encode document into JSON string + /// // encode document into a JSON string /// let data = try encoder.encode(updatedData) /// let body: HTTPClientRequest.Body = .bytes(ByteBuffer(data: data)) /// @@ -648,7 +648,7 @@ public class CouchDBClient { /// /// /// - Parameters: - /// - dbName: DB name. + /// - dbName: Database name. /// - uri: URI (view or document id). /// - body: Request body data. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. @@ -701,22 +701,22 @@ public class CouchDBClient { } } - /// Update document in DB. That method will mutate `doc` to update it's `_rev` with the value from CouchDB response. + /// Update document in a database. That method will mutate `doc` to update its `_rev` with the value from CouchDB response. /// /// Examples: /// /// Define your document model: /// ```swift /// // Example struct - /// struct ExpectedDoc: CouchDBRepresentable, Codable { + /// struct ExpectedDoc: CouchDBRepresentable { /// var name: String /// var _id: String? /// var _rev: String? /// } /// ``` - /// Get document by ID and update it: + /// Get a document by ID and update it: /// ```swift - /// // get data from DB by document ID + /// // get data from the database by document ID /// var doc: ExpectedDoc = try await couchDBClient.get(dbName: "databaseName", uri: "documentId") /// print(doc) /// @@ -732,11 +732,11 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - dbName: DB name. That method will mutate `doc` to update it's `_id` and `_rev` properties from insert request. - /// - doc: Document object/struct. Should confirm to ``CouchDBRepresentable`` and Codable protocols. + /// - dbName: Database name. That method will mutate `doc` to update its `_id` and `_rev` properties from insert request. + /// - doc: Document object/struct. Should conform to the ``CouchDBRepresentable`` protocol. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Update response. - public func update (dbName: String, doc: inout T, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil ) async throws { + public func update (dbName: String, doc: inout T, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil ) async throws { guard let id = doc._id else { throw CouchDBClientError.idMissing } guard doc._rev?.isEmpty == false else { throw CouchDBClientError.revMissing } @@ -761,14 +761,14 @@ public class CouchDBClient { doc._id = updateResponse.id } - /// Insert data in DB. Accepts HTTPClientRequest.Body as body parameter. + /// Insert data in a database. Accepts `HTTPClientRequest.Body` as body parameter. /// /// Examples: /// /// Define your document model: /// ```swift /// // Example struct - /// struct ExpectedDoc: CouchDBRepresentable, Codable { + /// struct ExpectedDoc: CouchDBRepresentable { /// var name: String /// var _id: String? /// var _rev: String? @@ -791,10 +791,10 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - dbName: DB name. + /// - dbName: Database name. /// - body: Request body data. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - /// - Returns: Insert request response. + /// - Returns: Insertion response. public func insert(dbName: String, body: HTTPClientRequest.Body, eventLoopGroup: EventLoopGroup? = nil) async throws -> CouchUpdateResponse { try await authIfNeed(eventLoopGroup: eventLoopGroup) @@ -844,14 +844,14 @@ public class CouchDBClient { } } - /// Insert document in DB. That method will mutate `doc` to update it's `_id` and `_rev` with the values from CouchDB response. + /// Insert document in a database. That method will mutate `doc` to update its `_id` and `_rev` with the values from CouchDB response. /// /// Examples: /// /// Define your document model: /// ```swift /// // Example struct - /// struct ExpectedDoc: CouchDBRepresentable, Codable { + /// struct ExpectedDoc: CouchDBRepresentable { /// var name: String /// var _id: String? /// var _rev: String? @@ -871,10 +871,10 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - dbName: DB name. - /// - doc: Document object/struct. Should confirm to ``CouchDBRepresentable`` protocol. + /// - dbName: Database name. + /// - doc: Document object/struct. Should conform to the ``CouchDBRepresentable`` protocol. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. - public func insert (dbName: String, doc: inout T, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil ) async throws { + public func insert (dbName: String, doc: inout T, dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .secondsSince1970, eventLoopGroup: EventLoopGroup? = nil ) async throws { let encoder = JSONEncoder() encoder.dateEncodingStrategy = dateEncodingStrategy let insertEncodeData = try encoder.encode(doc) @@ -895,7 +895,7 @@ public class CouchDBClient { doc._id = insertResponse.id } - /// Delete document from DB by URI. + /// Delete a document from a database by URI. /// /// Examples: /// @@ -904,7 +904,7 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - dbName: DB name. + /// - dbName: Database name. /// - uri: document uri (usually _id). /// - rev: document revision. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. @@ -946,7 +946,7 @@ public class CouchDBClient { return try JSONDecoder().decode(CouchUpdateResponse.self, from: data) } - /// Delete document from DB. + /// Delete a document from a database. /// /// Examples: /// @@ -955,8 +955,8 @@ public class CouchDBClient { /// ``` /// /// - Parameters: - /// - dbName: DB name. - /// - doc: Document object/struct. Should confirm to ``CouchDBRepresentable`` protocol. + /// - dbName: Database name. + /// - doc: Document object/struct. Should conform to the ``CouchDBRepresentable`` protocol. /// - eventLoopGroup: NIO's EventLoopGroup object. New will be created if nil value provided. /// - Returns: Delete request response. public func delete(fromDb dbName: String, doc: CouchDBRepresentable, eventLoopGroup: EventLoopGroup? = nil) async throws -> CouchUpdateResponse { From cc290e1fa3002753352b6adcebf07bd4b3996319 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 22:57:23 +0300 Subject: [PATCH 53/63] docs and refactoring --- README.md | 12 ++++++------ .../Tutorials/macOS/macOSTutorial-4.swift | 2 +- .../Tutorials/macOS/macOSTutorial-5.swift | 2 +- .../Tutorials/macOS/macOSTutorial-6.swift | 2 +- .../Tutorials/macOS/macOSTutorial-7.swift | 2 +- .../Tutorials/macOS/macOSTutorial-8.swift | 2 +- .../Tutorials/macOS/macOSTutorial.tutorial | 2 +- .../CouchDBClient/Models/CouchDBFindResponse.swift | 2 +- Tests/CouchDBClientTests/CouchDBClientTests.swift | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 4b867f9..c02d456 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This is a simple lib to work with CouchDB in Swift. -- Latest version is based on async/await and requires Swift 5.6 and newer. Works with Vapor 4.50 and newer. +- Latest version is based on async/await and requires Swift 5.7.1 or newer. Works with Vapor 4.50 and newer. - Version 1.0.0 can be used with Vapor 4 without async/await. Swift 5.3 is required - You can use the old version for Vapor 3 from vapor3 branch or using version < 1.0.0. @@ -55,7 +55,7 @@ let couchDBClient = CouchDBClient( ) ``` -If you don’t want to have your password in the code you can pass COUCHDB_PASS param in your command line. For example you can run your Server Side Swift project: +If you don’t want to have your password in the code you can pass `COUCHDB_PASS` param in your command line. For example you can run your Server Side Swift project: ```bash COUCHDB_PASS=myPassword /path/.build/x86_64-unknown-linux-gnu/release/Run ``` @@ -76,7 +76,7 @@ Define your document model: ```swift // Example struct -struct ExpectedDoc: CouchDBRepresentable, Codable { +struct ExpectedDoc: CouchDBRepresentable { var name: String var _id: String? var _rev: String? @@ -98,7 +98,7 @@ print(testDoc) // testDoc has _id and _rev values now ### Update data ```swift -// get data from DB by document ID +// get data from a database by document ID var doc: ExpectedDoc = try await couchDBClient.get(dbName: "databaseName", uri: "documentId") print(doc) @@ -121,7 +121,7 @@ let response = try await couchDBClient.delete(fromDb: "databaseName", doc: doc) let response = try await couchDBClient.delete(fromDb: "databaseName", uri: doc._id,rev: doc._rev) ``` -Get all DBs example: +Get all databases example: ```swift let dbs = try await couchDBClient.getAllDBs() @@ -129,7 +129,7 @@ print(dbs) // prints: ["_global_changes", "_replicator", "_users", "yourDBname"] ``` -Find documents in DB by selector: +Find documents in a database by selector: ```swift let selector = ["selector": ["name": "Sam"]] let docs: [ExpectedDoc] = try await couchDBClient.find(in: "databaseName", selector: selector) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-4.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-4.swift index 409aa4b..dfe5228 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-4.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-4.swift @@ -11,7 +11,7 @@ let couchDBClient = CouchDBClient( let dbName = "fortests" -struct MyDoc: CouchDBRepresentable, Codable { +struct MyDoc: CouchDBRepresentable { var _id: String? var _rev: String? var title: String diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-5.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-5.swift index 53b56fd..54a647c 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-5.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-5.swift @@ -11,7 +11,7 @@ let couchDBClient = CouchDBClient( let dbName = "fortests" -struct MyDoc: CouchDBRepresentable, Codable { +struct MyDoc: CouchDBRepresentable { var _id: String? var _rev: String? var title: String diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-6.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-6.swift index e8e4c81..1307d3b 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-6.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-6.swift @@ -11,7 +11,7 @@ let couchDBClient = CouchDBClient( let dbName = "fortests" -struct MyDoc: CouchDBRepresentable, Codable { +struct MyDoc: CouchDBRepresentable { var _id: String? var _rev: String? var title: String diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-7.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-7.swift index a0ab1b3..533ca10 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-7.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-7.swift @@ -11,7 +11,7 @@ let couchDBClient = CouchDBClient( let dbName = "fortests" -struct MyDoc: CouchDBRepresentable, Codable { +struct MyDoc: CouchDBRepresentable { var _id: String? var _rev: String? var title: String diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-8.swift b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-8.swift index a0d6a01..aa1672e 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-8.swift +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial-8.swift @@ -11,7 +11,7 @@ let couchDBClient = CouchDBClient( let dbName = "fortests" -struct MyDoc: CouchDBRepresentable, Codable { +struct MyDoc: CouchDBRepresentable { var _id: String? var _rev: String? var title: String diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial index 6cd9d2d..6f60572 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial @@ -38,7 +38,7 @@ } @Step { - Define a model for your CouchDB document. It should conform to `Codable` and `CouchDBRepresentable` protocols. + Define a model for your CouchDB document. It should conform to the `CouchDBRepresentable` protocol. @Code(name: "main.swift", file: macOSTutorial-4.swift) } diff --git a/Sources/CouchDBClient/Models/CouchDBFindResponse.swift b/Sources/CouchDBClient/Models/CouchDBFindResponse.swift index cd40015..dc5b045 100644 --- a/Sources/CouchDBClient/Models/CouchDBFindResponse.swift +++ b/Sources/CouchDBClient/Models/CouchDBFindResponse.swift @@ -7,7 +7,7 @@ import Foundation -public struct CouchDBFindResponse: Codable { +public struct CouchDBFindResponse: Codable { var docs: [T] var bookmark: String? } diff --git a/Tests/CouchDBClientTests/CouchDBClientTests.swift b/Tests/CouchDBClientTests/CouchDBClientTests.swift index 6da3631..7e04c4a 100644 --- a/Tests/CouchDBClientTests/CouchDBClientTests.swift +++ b/Tests/CouchDBClientTests/CouchDBClientTests.swift @@ -6,7 +6,7 @@ import AsyncHTTPClient final class CouchDBClientTests: XCTestCase { - struct ExpectedDoc: CouchDBRepresentable, Codable { + struct ExpectedDoc: CouchDBRepresentable { var name: String var _id: String? var _rev: String? From d8343b1a200b185dec6a5db5138ef2418bfc398f Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 23:02:20 +0300 Subject: [PATCH 54/63] Tutorials updated --- .../ErrorsHandlingTutorial.tutorial | 6 +-- .../Tutorials/macOS/macOSTutorial.tutorial | 50 +++++++++---------- .../Tutorials/vapor/VaporTutorial.tutorial | 46 ++++++++--------- 3 files changed, 51 insertions(+), 51 deletions(-) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/ErrorsHandling/ErrorsHandlingTutorial.tutorial b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/ErrorsHandling/ErrorsHandlingTutorial.tutorial index 313adfa..6af9e6f 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/ErrorsHandling/ErrorsHandlingTutorial.tutorial +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/ErrorsHandling/ErrorsHandlingTutorial.tutorial @@ -1,13 +1,13 @@ @Tutorial(time: 3) { @Intro(title: "Handling CouchDB errors") { - Use CouchDBClient in macOS app + Use CouchDBClient in a macOS app @Image(source: chapter1.png, alt: "Application icon") } - @Section(title: "Use CouchDBClient in macOS app") { + @Section(title: "macOS app example") { @ContentAndMedia { - Use CouchDBClient in macOS app + Use CouchDBClient in a macOS app @Image(source: chapter1.png, alt: "Application icon") } diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial index 6f60572..f894b6d 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/macOS/macOSTutorial.tutorial @@ -1,29 +1,29 @@ @Tutorial(time: 10) { - @Intro(title: "Using in macOS app") { + @Intro(title: "Using in macOS app") { Learn how to use CouchDBClient in your macOS app - + @Image(source: chapter1.png, alt: "Application icon") - } - - @Section(title: "Initializaton") { - @ContentAndMedia { + } + + @Section(title: "Initialization") { + @ContentAndMedia { Adding CouchDBClient to your project. - - @Image(source: chapter1.png, alt: "Application icon") - } - - @Steps { - @Step { + + @Image(source: chapter1.png, alt: "Application icon") + } + + @Steps { + @Step { Add `CouchDBClient` as a Swift Package dependency. - - @Image(source: macOSTutorial-1.png, alt: "Add CouchDBClient as a Swift Package dependency.") - } - - @Step { - Here's an example app. - - @Code(name: "main.swift", file: macOSTutorial-1.swift) - } + + @Image(source: macOSTutorial-1.png, alt: "Add CouchDBClient as a Swift Package dependency.") + } + + @Step { + Here's an example app. + + @Code(name: "main.swift", file: macOSTutorial-1.swift) + } @Step { Import `CouchDBClient`. @@ -56,13 +56,13 @@ } @Step { - Getting a document by its `_id` from DB with that method will parse JSON into your model if you provide it as a generic type. + Getting a document by its `_id` from a database with that method will parse JSON into your model if you provide it as a generic type. @Code(name: "main.swift", file: macOSTutorial-7.swift) } @Step { - Deleting a document from DB is also easy. + Deleting a document from a database is also easy. @Code(name: "main.swift", file: macOSTutorial-8.swift) } @@ -72,6 +72,6 @@ @Image(source: macOSTutorial-2.png, alt: "Add CouchDBClient as a Swift Package dependency.") } - } - } + } + } } diff --git a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial index 386d729..b4e2fed 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial +++ b/Sources/CouchDBClient/CouchDBClient.docc/Tutorials/vapor/VaporTutorial.tutorial @@ -1,29 +1,29 @@ @Tutorial(time: 15) { - @Intro(title: "Using with Vapor in server-side app") { + @Intro(title: "Using with Vapor in server-side app") { Use CouchDBClient for developing server-side apps built with Vapor. - + @Image(source: chapter1.png, alt: "Application icon") - } - - @Section(title: "Use CouchDBClient in macOS app") { - @ContentAndMedia { + } + + @Section(title: "Use CouchDBClient in macOS app") { + @ContentAndMedia { Vapor is built on top of Apple's [SwiftNIO](https://github.com/apple/swift-nio). `CouchDBClient` is compatible with SwiftNIO and can be used for server-side development. - - @Image(source: chapter1.png, alt: "Application icon") - } - - @Steps { - @Step { + + @Image(source: chapter1.png, alt: "Application icon") + } + + @Steps { + @Step { Add `CouchDBClient` as a Swift Package dependency. - - @Image(source: VaporTutorial-1.png, alt: "Add CouchDBClient as a Swift Package dependency.") - } - - @Step { + + @Image(source: VaporTutorial-1.png, alt: "Add CouchDBClient as a Swift Package dependency.") + } + + @Step { Open `routes.swift` in your Vapor project. - - @Code(name: "main.swift", file: VaporTutorial-1.swift) - } + + @Code(name: "main.swift", file: VaporTutorial-1.swift) + } @Step { Import `CouchDBClient` and create a client instance. @@ -38,7 +38,7 @@ } @Step { - Get your document from DB. That example is using `CouchDB View` to find the document by the url field. Its map function needs a `key` param which is `appUrl` in our case. + Get your document from a database. That example is using `CouchDB View` to find the document by the url field. Its map function needs a `key` param which is `appUrl` in our case. @Code(name: "main.swift", file: VaporTutorial-4.swift) } @@ -54,6 +54,6 @@ @Code(name: "main.swift", file: VaporTutorial-6.swift) } - } - } + } + } } From 952f8e2636daec7dbf6d72899c5b30c6fe728159 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 23:03:41 +0300 Subject: [PATCH 55/63] docs --- Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md b/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md index 0a95d58..64f10e9 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md +++ b/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md @@ -15,7 +15,7 @@ Currently CouchDBClient supports: - Create DB. - Delete DB. - Get databases list. -- Get document by id or documents using view. +- Get a document by id or documents using a view. - Insert/update documents. - Find documents by selector. - Delete documents. From 093c7c336f3e89f4b0b199123241ffe8d5601abc Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 23:21:57 +0300 Subject: [PATCH 56/63] platforms list updated --- Package.swift | 2 +- Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 3c96474..915f47e 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "couchdb-vapor", - platforms: [.macOS(.v10_15), .iOS(.v13)], + platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library(name: "CouchDBClient", targets: ["CouchDBClient"]), diff --git a/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md b/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md index 64f10e9..b5bc23f 100644 --- a/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md +++ b/Sources/CouchDBClient/CouchDBClient.docc/CouchDBClient.md @@ -6,7 +6,7 @@ A simple CouchDB client written in Swift. Source code is available on [GitHub](https://github.com/makoni/couchdb-vapor). -CouchDBClient allows you to make simple requests to CouchDB. It's using Swift Concurrency (async/await) and supports Linux, iOS 13+ and macOS 10.15+. +CouchDBClient allows you to make simple requests to CouchDB. It's using Swift Concurrency (async/await) and supports Linux, iOS 13+, iPadOS 13+, tvOS 13+, watchOS 6+, visionOS 1.0+ and macOS 10.15+. It's using [AsyncHTTPClient](https://github.com/swift-server/async-http-client) which makes it easy to use CouchDBClient for server-side development with Vapor 4. But it's easy to use it with any iOS or macOS app. Check the Essentials section for examples. From 79bd6570cbb8050a78237db44bc5a47cc33b2bb0 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 23:38:21 +0300 Subject: [PATCH 57/63] minimum swift version 5.8 --- Package.swift | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 915f47e..27c9a46 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7.1 +// swift-tools-version:5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/README.md b/README.md index c02d456..b09e7c1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This is a simple lib to work with CouchDB in Swift. -- Latest version is based on async/await and requires Swift 5.7.1 or newer. Works with Vapor 4.50 and newer. +- Latest version is based on async/await and requires Swift 5.8 or newer. Works with Vapor 4.50 and newer. - Version 1.0.0 can be used with Vapor 4 without async/await. Swift 5.3 is required - You can use the old version for Vapor 3 from vapor3 branch or using version < 1.0.0. From e1737a79a77a87f31887bb615845a9e8058dda88 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Sun, 7 Apr 2024 23:41:04 +0300 Subject: [PATCH 58/63] workflow updated --- .github/workflows/build-ubuntu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index bb991a2..e76defe 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -11,7 +11,7 @@ jobs: name: Build on Ubuntu with Swift ${{matrix.swift}} strategy: matrix: - swift: [5.9, 5.8.1, 5.7.3] + swift: [5.10, 5.9, 5.8] runs-on: ubuntu-latest container: image: swift:${{matrix.swift}} From 2a6c9641899cf4ff7509691af5f1a9db68293ca4 Mon Sep 17 00:00:00 2001 From: Sergey Date: Sun, 7 Apr 2024 23:43:38 +0300 Subject: [PATCH 59/63] Update build-ubuntu.yml --- .github/workflows/build-ubuntu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index e76defe..5eeb04c 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -11,7 +11,7 @@ jobs: name: Build on Ubuntu with Swift ${{matrix.swift}} strategy: matrix: - swift: [5.10, 5.9, 5.8] + swift: ["5.10", "5.9", "5.8"] runs-on: ubuntu-latest container: image: swift:${{matrix.swift}} From 04bcd15d2fc4cb608fabc3b2d41cbe1a19ddc319 Mon Sep 17 00:00:00 2001 From: Sergey Date: Sun, 7 Apr 2024 23:49:58 +0300 Subject: [PATCH 60/63] Update build-macos.yml --- .github/workflows/build-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 6528098..1b848ce 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -12,7 +12,7 @@ on: jobs: macOS: name: Build on macOS - runs-on: macOS-latest + runs-on: macOS-14 steps: - name: Print Swift version run: swift --version From a70d23ea1f9567b8f846931c217e885c5bfe3d03 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Mon, 8 Apr 2024 00:08:23 +0300 Subject: [PATCH 61/63] import NIOFoundationCompat to fix building on Ubuntu --- Sources/CouchDBClient/CouchDBClient.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index 2e9d625..a9146e2 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -8,6 +8,7 @@ import Foundation import NIO import NIOHTTP1 +import NIOFoundationCompat import AsyncHTTPClient /// CouchDB client errors. From 7acc1b63ca0f3f6f007214ae67c4e0612103ef53 Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Mon, 8 Apr 2024 17:40:00 +0300 Subject: [PATCH 62/63] using async-http-client from 1.21.0. Not calling http --- Package.resolved | 4 ++-- Package.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Package.resolved b/Package.resolved index a2013f7..422111b 100644 --- a/Package.resolved +++ b/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swift-server/async-http-client.git", "state" : { - "revision" : "291438696abdd48d2a83b52465c176efbd94512b", - "version" : "1.20.1" + "revision" : "fb308ee72f3d4c082a507033f94afa7395963ef3", + "version" : "1.21.0" } }, { diff --git a/Package.swift b/Package.swift index 27c9a46..e57410c 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( .library(name: "CouchDBClient", targets: ["CouchDBClient"]), ], dependencies: [ - .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0"), + .package(url: "https://github.com/swift-server/async-http-client", from: "1.21.0"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.3.0") ], targets: [ From c9ec8c1338ec233dd99e16796ea1d1a5bcfb4e9a Mon Sep 17 00:00:00 2001 From: Sergei Armodin Date: Mon, 8 Apr 2024 17:40:49 +0300 Subject: [PATCH 63/63] Using HTTPClient.shared if eventLoopGroup not provided. No more calls httpClient.syncShutdown() if using shared singleton --- .../CouchDBClient/CouchDB+Deprecated.swift | 16 ++-- Sources/CouchDBClient/CouchDBClient.swift | 80 ++++++++++++------- 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/Sources/CouchDBClient/CouchDB+Deprecated.swift b/Sources/CouchDBClient/CouchDB+Deprecated.swift index 3ef03e5..fb253f3 100644 --- a/Sources/CouchDBClient/CouchDB+Deprecated.swift +++ b/Sources/CouchDBClient/CouchDB+Deprecated.swift @@ -64,12 +64,14 @@ extension CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -110,12 +112,14 @@ extension CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } diff --git a/Sources/CouchDBClient/CouchDBClient.swift b/Sources/CouchDBClient/CouchDBClient.swift index a9146e2..34039fd 100644 --- a/Sources/CouchDBClient/CouchDBClient.swift +++ b/Sources/CouchDBClient/CouchDBClient.swift @@ -166,12 +166,14 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -214,12 +216,14 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -253,12 +257,14 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -312,12 +318,14 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -426,12 +434,14 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -580,12 +590,14 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -661,12 +673,14 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -803,12 +817,14 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -915,12 +931,14 @@ public class CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } } @@ -1006,12 +1024,14 @@ internal extension CouchDBClient { if let eventLoopGroup = eventLoopGroup { httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup)) } else { - httpClient = HTTPClient(eventLoopGroupProvider: .singleton) + httpClient = HTTPClient.shared } defer { - DispatchQueue.main.async { - try? httpClient.syncShutdown() + if eventLoopGroup != nil { + DispatchQueue.main.async { + try? httpClient.syncShutdown() + } } }