From 3b146486a3ebd8b0d1721eac01b152e26afdadf4 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Sun, 12 Nov 2023 21:27:58 -1000 Subject: [PATCH 01/12] WIP - simplify params --- .../Calls/NetworkingClient+Multipart.swift | 10 +++--- .../Calls/NetworkingClient+Requests.swift | 31 ++++++++++--------- Sources/Networking/NetworkingRequest.swift | 11 +++++-- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/Sources/Networking/Calls/NetworkingClient+Multipart.swift b/Sources/Networking/Calls/NetworkingClient+Multipart.swift index 3207083..480cf34 100644 --- a/Sources/Networking/Calls/NetworkingClient+Multipart.swift +++ b/Sources/Networking/Calls/NetworkingClient+Multipart.swift @@ -11,29 +11,28 @@ import Combine public extension NetworkingClient { func post(_ route: String, - params: Params = Params(), multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { return post(route, params: params, multipartData: [multipartData]) } func put(_ route: String, - params: Params = Params(), multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { return put(route, params: params, multipartData: [multipartData]) } func patch(_ route: String, - params: Params = Params(), multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { return patch(route, params: params, multipartData: [multipartData]) } - // Allow multiple multipart data +//TODO // Allow multiple multipart data + + func post(_ route: String, params: Params = Params(), multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { let req = request(.post, route, params: params) - req.multipartData = multipartData + req.body = HTTPBody.multipart(params: params, parts: multipartData) return req.uploadPublisher() } @@ -41,6 +40,7 @@ public extension NetworkingClient { params: Params = Params(), multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { let req = request(.put, route, params: params) + req.body = HTTPBody.multipart(params: params, parts: multipartData) req.multipartData = multipartData return req.uploadPublisher() } diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/Calls/NetworkingClient+Requests.swift index e891f5d..a782e37 100644 --- a/Sources/Networking/Calls/NetworkingClient+Requests.swift +++ b/Sources/Networking/Calls/NetworkingClient+Requests.swift @@ -10,34 +10,36 @@ import Combine public extension NetworkingClient { - func getRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.get, route, params: params) + + func getRequest(_ route: String, urlParams: Params? = nil) -> NetworkingRequest { + request(.get, route, urlParams: urlParams) } - func postRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.post, route, params: params) + func postRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { + request(.post, route, body: body) } - func putRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.put, route, params: params) + func putRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { + request(.put, route, body: body) } - func patchRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.patch, route, params: params) + func patchRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { + request(.patch, route, body: body) } - func deleteRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { + func deleteRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { request(.delete, route, params: params) } internal func request(_ httpMethod: HTTPMethod, _ route: String, - params: Params = Params() - ) -> NetworkingRequest { + urlParams: Params? = nil, + body: HTTPBody? = nil + ) -> NetworkingRequest { let req = NetworkingRequest() req.httpMethod = httpMethod req.route = route - req.params = params + req.body = body let updateRequest = { [weak req, weak self] in guard let self = self else { return } @@ -64,11 +66,12 @@ public extension NetworkingClient { params: Params = Params(), encodableBody: Encodable? = nil ) -> NetworkingRequest { + let req = NetworkingRequest() + body: HTTPBody? = nil) -> NetworkingRequest { let req = NetworkingRequest() req.httpMethod = httpMethod req.route = route - req.params = Params() - req.encodableBody = encodableBody + req.body = body let updateRequest = { [weak req, weak self] in guard let self = self else { return } diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index 8b5794d..a3a84c2 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -8,6 +8,12 @@ import Foundation import Combine +public enum HTTPBody { + case urlEncoded(params: Params) + case json(encodable: Encodable) + case multipart(params:Params, parts:[MultipartData]) +} + public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? public class NetworkingRequest: NSObject, URLSessionTaskDelegate { @@ -16,10 +22,9 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { var baseURL = "" var route = "" var httpMethod = HTTPMethod.get - public var params = Params() - public var encodableBody: Encodable? + var body: HTTPBody? = nil + public var urlParams: Params? = nil var headers = [String: String]() - var multipartData: [MultipartData]? var logLevel: NetworkingLogLevel { get { return logger.logLevel } set { logger.logLevel = newValue } From ef8e3d8312c65705201f5f0f4d7ad7871127f891 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Sun, 12 Nov 2023 23:47:33 -1000 Subject: [PATCH 02/12] WIP - Experiment with HTTPBody enum --- .../Networking/Calls/Async Api/Delete.swift | 37 ++++ Sources/Networking/Calls/Async Api/Get.swift | 39 +++++ .../Networking/Calls/Async Api/Patch.swift | 42 +++++ Sources/Networking/Calls/Async Api/Post.swift | 40 +++++ Sources/Networking/Calls/Async Api/Put.swift | 39 +++++ .../Calls/Combine Api/Get+Combine.swift | 46 +++++ .../NetworkingClient+AnyPublisherData.swift | 28 +++ .../Calls/NetworkingClient+Data.swift | 67 ------- .../Calls/NetworkingClient+Decodable.swift | 164 ++---------------- .../Calls/NetworkingClient+JSON.swift | 71 +------- .../Calls/NetworkingClient+Multipart.swift | 104 +++++------ ...orkingClient+NetworkingJSONDecodable.swift | 38 ++-- .../Calls/NetworkingClient+Requests.swift | 40 +---- .../Calls/NetworkingClient+Void.swift | 67 +------ Sources/Networking/NetworkingRequest.swift | 56 +++--- Sources/Networking/NetworkingService.swift | 3 +- Tests/NetworkingTests/GetRequestTests.swift | 2 +- .../MultipartRequestTests.swift | 18 +- Tests/NetworkingTests/PostRequestTests.swift | 4 +- 19 files changed, 410 insertions(+), 495 deletions(-) create mode 100644 Sources/Networking/Calls/Async Api/Delete.swift create mode 100644 Sources/Networking/Calls/Async Api/Get.swift create mode 100644 Sources/Networking/Calls/Async Api/Patch.swift create mode 100644 Sources/Networking/Calls/Async Api/Post.swift create mode 100644 Sources/Networking/Calls/Async Api/Put.swift create mode 100644 Sources/Networking/Calls/Combine Api/Get+Combine.swift create mode 100644 Sources/Networking/Calls/Combine Api/NetworkingClient+AnyPublisherData.swift delete mode 100644 Sources/Networking/Calls/NetworkingClient+Data.swift diff --git a/Sources/Networking/Calls/Async Api/Delete.swift b/Sources/Networking/Calls/Async Api/Delete.swift new file mode 100644 index 0000000..48bf82c --- /dev/null +++ b/Sources/Networking/Calls/Async Api/Delete.swift @@ -0,0 +1,37 @@ +// +// Delete.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation + +public extension NetworkingClient { + + func delete(_ route: String) async throws { + let _:Data = try await delete(route) + } + + func delete(_ route: String, + keypath: String? = nil) async throws -> T { + let json: Any = try await delete(route) + return try self.toModel(json, keypath: keypath) + } + + func delete(_ route: String, + keypath: String? = nil) async throws -> T where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + let json: Any = try await delete(route) + return try self.toModel(json, keypath: keypath) + } + + func delete(_ route: String) async throws -> Any { + let data: Data = try await delete(route) + return try JSONSerialization.jsonObject(with: data, options: []) + } + + func delete(_ route: String) async throws -> Data { + try await request(.delete, route).execute() + } +} diff --git a/Sources/Networking/Calls/Async Api/Get.swift b/Sources/Networking/Calls/Async Api/Get.swift new file mode 100644 index 0000000..373c98b --- /dev/null +++ b/Sources/Networking/Calls/Async Api/Get.swift @@ -0,0 +1,39 @@ +// +// Get.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation + +public extension NetworkingClient { + + func get(_ route: String, urlParams: Params? = nil) async throws { + let _:Data = try await get(route, urlParams: urlParams) + } + + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) async throws -> T { + let json: Any = try await get(route, urlParams: urlParams) + return try self.toModel(json, keypath: keypath) + } + + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) async throws -> T where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + let json: Any = try await get(route, urlParams: urlParams) + return try self.toModel(json, keypath: keypath) + } + + func get(_ route: String, urlParams: Params? = nil) async throws -> Any { + let data: Data = try await get(route, urlParams: urlParams) + return try JSONSerialization.jsonObject(with: data, options: []) + } + + func get(_ route: String, urlParams: Params? = nil) async throws -> Data { + try await request(.get, route, urlParams: urlParams).execute() + } +} diff --git a/Sources/Networking/Calls/Async Api/Patch.swift b/Sources/Networking/Calls/Async Api/Patch.swift new file mode 100644 index 0000000..e5fc5a8 --- /dev/null +++ b/Sources/Networking/Calls/Async Api/Patch.swift @@ -0,0 +1,42 @@ +// +// Patch.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation + +public extension NetworkingClient { + + func patch(_ route: String, body: HTTPBody? = nil) async throws { + let req = request(.patch, route, body: body) + _ = try await req.execute() + } + + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T { + let json: Any = try await patch(route, body: body) + return try self.toModel(json, keypath: keypath) + } + + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + let json: Any = try await patch(route, body: body) + return try self.toModel(json, keypath: keypath) + } + + func patch(_ route: String, body: HTTPBody? = nil) async throws -> Any { + let req = request(.patch, route, body: body) + let data = try await req.execute() + return try JSONSerialization.jsonObject(with: data, options: []) + } + + func patch(_ route: String, body: HTTPBody? = nil) async throws -> Data { + try await request(.patch, route, body: body).execute() + } + +} diff --git a/Sources/Networking/Calls/Async Api/Post.swift b/Sources/Networking/Calls/Async Api/Post.swift new file mode 100644 index 0000000..5ef0e52 --- /dev/null +++ b/Sources/Networking/Calls/Async Api/Post.swift @@ -0,0 +1,40 @@ +// +// Post.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation + +public extension NetworkingClient { + + func post(_ route: String, body: HTTPBody? = nil) async throws { + let _: Data = try await post(route, body: body) + } + + + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T { + let json: Any = try await post(route, body: body) + return try toModel(json, keypath: keypath) + } + + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + let json: Any = try await post(route, body: body) + return try toModel(json, keypath: keypath) + } + + func post(_ route: String, body: HTTPBody? = nil) async throws -> Any { + let data: Data = try await post(route, body: body) + return try JSONSerialization.jsonObject(with: data, options: []) + } + + func post(_ route: String, body: HTTPBody? = nil) async throws -> Data { + try await request(.post, route, body: body).execute() + } +} diff --git a/Sources/Networking/Calls/Async Api/Put.swift b/Sources/Networking/Calls/Async Api/Put.swift new file mode 100644 index 0000000..e8d3a11 --- /dev/null +++ b/Sources/Networking/Calls/Async Api/Put.swift @@ -0,0 +1,39 @@ +// +// Put.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation + +public extension NetworkingClient { + + func put(_ route: String, body: HTTPBody? = nil) async throws { + let _: Data = try await put(route, body: body) + } + + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T { + let json: Any = try await put(route, body: body) + return try self.toModel(json, keypath: keypath) + } + + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + let json: Any = try await put(route, body: body) + return try self.toModel(json, keypath: keypath) + } + + func put(_ route: String, body: HTTPBody? = nil) async throws -> Any { + let data: Data = try await put(route, body: body) + return try JSONSerialization.jsonObject(with: data, options: []) + } + + func put(_ route: String, body: HTTPBody? = nil) async throws -> Data { + try await request(.put, route, body: body).execute() + } +} diff --git a/Sources/Networking/Calls/Combine Api/Get+Combine.swift b/Sources/Networking/Calls/Combine Api/Get+Combine.swift new file mode 100644 index 0000000..235f5cd --- /dev/null +++ b/Sources/Networking/Calls/Combine Api/Get+Combine.swift @@ -0,0 +1,46 @@ +// +// Get+Combine.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { + get(route, urlParams: urlParams) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { + request(.get, route, urlParams: urlParams).publisher() + } + + func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { + get(route, urlParams: urlParams).toJSON() + } + + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) -> AnyPublisher { + return get(route, urlParams: urlParams) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + // Array version + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return get(route, urlParams: urlParams) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } +} diff --git a/Sources/Networking/Calls/Combine Api/NetworkingClient+AnyPublisherData.swift b/Sources/Networking/Calls/Combine Api/NetworkingClient+AnyPublisherData.swift new file mode 100644 index 0000000..76be2e0 --- /dev/null +++ b/Sources/Networking/Calls/Combine Api/NetworkingClient+AnyPublisherData.swift @@ -0,0 +1,28 @@ +// +// NetworkingClient+AnyPublisherData.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + request(.post, route, body: body).publisher() + } + + func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + request(.put, route, body: body).publisher() + } + + func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + request(.patch, route, body: body).publisher() + } + + func delete(_ route: String) -> AnyPublisher { + request(.delete, route).publisher() + } +} diff --git a/Sources/Networking/Calls/NetworkingClient+Data.swift b/Sources/Networking/Calls/NetworkingClient+Data.swift deleted file mode 100644 index e912393..0000000 --- a/Sources/Networking/Calls/NetworkingClient+Data.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// NetworkingClient+Data.swift -// -// -// Created by Sacha on 13/03/2020. -// - -import Foundation -import Combine - -public extension NetworkingClient { - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.get, route, params: params).publisher() - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.post, route, params: params).publisher() - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - request(.post, route, encodableBody: body).publisher() - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.put, route, params: params).publisher() - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.patch, route, params: params).publisher() - } - - func patch(_ route: String, body: Encodable) -> AnyPublisher { - request(.patch, route, encodableBody: body).publisher() - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - request(.delete, route, params: params).publisher() - } -} - -public extension NetworkingClient { - - func get(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.get, route, params: params).execute() - } - - func post(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.post, route, params: params).execute() - } - - func post(_ route: String, body: Encodable) async throws -> Data { - try await request(.post, route, encodableBody: body).execute() - } - - func put(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.put, route, params: params).execute() - } - - func patch(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.patch, route, params: params).execute() - } - - func delete(_ route: String, params: Params = Params()) async throws -> Data { - try await request(.delete, route, params: params).execute() - } -} diff --git a/Sources/Networking/Calls/NetworkingClient+Decodable.swift b/Sources/Networking/Calls/NetworkingClient+Decodable.swift index e48ff5b..5971012 100644 --- a/Sources/Networking/Calls/NetworkingClient+Decodable.swift +++ b/Sources/Networking/Calls/NetworkingClient+Decodable.swift @@ -10,39 +10,9 @@ import Combine public extension NetworkingClient { - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - return get(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return get(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - func post(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher { - return post(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func post(_ route: String, - body: Encodable, - keypath: String? = nil - ) -> AnyPublisher { return post(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) @@ -51,19 +21,19 @@ public extension NetworkingClient { // Array version func post(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - return post(route, params: params) + return post(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } func put(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher { - return put(route, params: params) + return put(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() @@ -71,50 +41,39 @@ public extension NetworkingClient { // Array version func put(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - return put(route, params: params) + return put(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } func patch(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher { - return patch(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - - func patch(_ route: String, - body: Encodable, - keypath: String? = nil - ) -> AnyPublisher { return patch(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } + // Array version func patch(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - return patch(route, params: params) + return patch(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } func delete(_ route: String, - params: Params = Params(), keypath: String? = nil) -> AnyPublisher { - return delete(route, params: params) + return delete(route) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() @@ -122,108 +81,11 @@ public extension NetworkingClient { // Array version func delete(_ route: String, - params: Params = Params(), keypath: String? = nil) -> AnyPublisher where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - return delete(route, params: params) + return delete(route) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } } - - -public extension NetworkingClient { - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - let json: Any = try await get(route, params: params) - let model:T = try self.toModel(json, keypath: keypath) - return model - } - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await get(route, params: params) - return try self.toModel(json, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - let json: Any = try await post(route, params: params) - return try self.toModel(json, keypath: keypath) - } - - func post(_ route: String, - body: Encodable, - keypath: String? = nil - ) async throws -> T { - let json: Any = try await post(route, body: body) - return try self.toModel(json, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await post(route, params: params) - return try self.toModel(json, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - let json: Any = try await put(route, params: params) - return try self.toModel(json, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await put(route, params: params) - return try self.toModel(json, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - let json: Any = try await patch(route, params: params) - return try self.toModel(json, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await patch(route, params: params) - return try self.toModel(json, keypath: keypath) - } - - func patch(_ route: String, - body: Encodable, - keypath: String? = nil - ) async throws -> T { - let json: Any = try await patch(route, body: body) - return try self.toModel(json, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - let json: Any = try await delete(route, params: params) - return try self.toModel(json, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await delete(route, params: params) - return try self.toModel(json, keypath: keypath) - } -} diff --git a/Sources/Networking/Calls/NetworkingClient+JSON.swift b/Sources/Networking/Calls/NetworkingClient+JSON.swift index 677b6db..d3972d0 100644 --- a/Sources/Networking/Calls/NetworkingClient+JSON.swift +++ b/Sources/Networking/Calls/NetworkingClient+JSON.swift @@ -10,77 +10,20 @@ import Combine public extension NetworkingClient { - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - get(route, params: params).toJSON() - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - post(route, params: params).toJSON() - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { + func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { post(route, body: body).toJSON() } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - put(route, params: params).toJSON() - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - patch(route, params: params).toJSON() - } - func patch(_ route: String, body: Encodable) -> AnyPublisher { - patch(route, body: body).toJSON() + func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + put(route, body: body).toJSON() } - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - delete(route, params: params).toJSON() + func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + patch(route, body: body).toJSON() } -} - -public extension NetworkingClient { - func get(_ route: String, params: Params = Params()) async throws -> Any { - let req = request(.get, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) - } - - func post(_ route: String, params: Params = Params()) async throws -> Any { - let req = request(.post, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) - } - - func post(_ route: String, body: Encodable) async throws -> Any { - let req = request(.post, route, encodableBody: body) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) - } - - func put(_ route: String, params: Params = Params()) async throws -> Any { - let req = request(.put, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) - } - - func patch(_ route: String, params: Params = Params()) async throws -> Any { - let req = request(.patch, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) - } - - func patch(_ route: String, body: Encodable) async throws -> Any { - let req = request(.patch, route, encodableBody: body) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) - } - - func delete(_ route: String, params: Params = Params()) async throws -> Any { - let req = request(.delete, route, params: params) - let data = try await req.execute() - return try JSONSerialization.jsonObject(with: data, options: []) + func delete(_ route: String) -> AnyPublisher { + delete(route).toJSON() } } diff --git a/Sources/Networking/Calls/NetworkingClient+Multipart.swift b/Sources/Networking/Calls/NetworkingClient+Multipart.swift index 480cf34..e122129 100644 --- a/Sources/Networking/Calls/NetworkingClient+Multipart.swift +++ b/Sources/Networking/Calls/NetworkingClient+Multipart.swift @@ -1,55 +1,57 @@ +//// +//// NetworkingClient+Multipart.swift +//// +//// +//// Created by Sacha on 13/03/2020. +//// // -// NetworkingClient+Multipart.swift +//import Foundation +//import Combine // +//public extension NetworkingClient { // -// Created by Sacha on 13/03/2020. +// func post(_ route: String, +// multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { +// return post(route, params: params, multipartData: [multipartData]) +// } // - -import Foundation -import Combine - -public extension NetworkingClient { - - func post(_ route: String, - multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { - return post(route, params: params, multipartData: [multipartData]) - } - - func put(_ route: String, - multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { - return put(route, params: params, multipartData: [multipartData]) - } - - func patch(_ route: String, - multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { - return patch(route, params: params, multipartData: [multipartData]) - } - -//TODO // Allow multiple multipart data - - - func post(_ route: String, - params: Params = Params(), - multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { - let req = request(.post, route, params: params) - req.body = HTTPBody.multipart(params: params, parts: multipartData) - return req.uploadPublisher() - } - - func put(_ route: String, - params: Params = Params(), - multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { - let req = request(.put, route, params: params) - req.body = HTTPBody.multipart(params: params, parts: multipartData) - req.multipartData = multipartData - return req.uploadPublisher() - } - - func patch(_ route: String, - params: Params = Params(), - multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { - let req = request(.patch, route, params: params) - req.multipartData = multipartData - return req.uploadPublisher() - } -} +// func put(_ route: String, +// multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { +// return put(route, params: params, multipartData: [multipartData]) +// } +// +// func patch(_ route: String, +// multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { +// return patch(route, params: params, multipartData: [multipartData]) +// } +// +////TODO // Allow multiple multipart data +// +// +// func post(_ route: String, +// params: Params = Params(), +// multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { +// let req = request(.post, route, params: params) +// req.body = HTTPBody.multipart(params: params, parts: multipartData) +// return req.uploadPublisher() +// } +// +// func put(_ route: String, +// params: Params = Params(), +// multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { +// let req = request(.put, route, params: params) +// req.body = HTTPBody.multipart(params: params, parts: multipartData) +// req.multipartData = multipartData +// return req.uploadPublisher() +// } +// +// func patch(_ route: String, +// params: Params = Params(), +// multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { +// let req = request(.patch, route, params: params) +// req.multipartData = multipartData +// return req.uploadPublisher() +// } +//} + +//TODO put back diff --git a/Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift b/Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift index 68944be..1bd60b0 100644 --- a/Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift +++ b/Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift @@ -17,9 +17,9 @@ public protocol NetworkingJSONDecodable { public extension NetworkingClient { func get(_ route: String, - params: Params = Params(), + urlParams: Params? = nil, keypath: String? = nil) -> AnyPublisher { - return get(route, params: params) + return get(route, urlParams: urlParams) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() @@ -27,19 +27,19 @@ public extension NetworkingClient { // Array version func get(_ route: String, - params: Params = Params(), + urlParams: Params? = nil, keypath: String? = nil) -> AnyPublisher<[T], Error> { let keypath = keypath ?? defaultCollectionParsingKeyPath - return get(route, params: params) + return get(route, urlParams: urlParams) .tryMap { json -> [T] in try self.toModels(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } func post(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher { - return post(route, params: params) + return post(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() @@ -47,19 +47,19 @@ public extension NetworkingClient { // Array version func post(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher<[T], Error> { let keypath = keypath ?? defaultCollectionParsingKeyPath - return post(route, params: params) + return post(route, body: body) .tryMap { json -> [T] in try self.toModels(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } func put(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher { - return put(route, params: params) + return put(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() @@ -68,19 +68,19 @@ public extension NetworkingClient { // Array version func put(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher<[T], Error> { let keypath = keypath ?? defaultCollectionParsingKeyPath - return put(route, params: params) + return put(route, body: body) .tryMap { json -> [T] in try self.toModels(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } func patch(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher { - return patch(route, params: params) + return patch(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() @@ -88,19 +88,18 @@ public extension NetworkingClient { // Array version func patch(_ route: String, - params: Params = Params(), + body: HTTPBody? = nil, keypath: String? = nil) -> AnyPublisher<[T], Error> { let keypath = keypath ?? defaultCollectionParsingKeyPath - return patch(route, params: params) + return patch(route, body: body) .tryMap { json -> [T] in try self.toModels(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } func delete(_ route: String, - params: Params = Params(), keypath: String? = nil) -> AnyPublisher { - return delete(route, params: params) + return delete(route) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() @@ -108,10 +107,9 @@ public extension NetworkingClient { // Array version func delete(_ route: String, - params: Params = Params(), keypath: String? = nil) -> AnyPublisher<[T], Error> { let keypath = keypath ?? defaultCollectionParsingKeyPath - return delete(route, params: params) + return delete(route) .tryMap { json -> [T] in try self.toModels(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/Calls/NetworkingClient+Requests.swift index a782e37..4e22d1d 100644 --- a/Sources/Networking/Calls/NetworkingClient+Requests.swift +++ b/Sources/Networking/Calls/NetworkingClient+Requests.swift @@ -10,7 +10,6 @@ import Combine public extension NetworkingClient { - func getRequest(_ route: String, urlParams: Params? = nil) -> NetworkingRequest { request(.get, route, urlParams: urlParams) } @@ -27,8 +26,8 @@ public extension NetworkingClient { request(.patch, route, body: body) } - func deleteRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { - request(.delete, route, params: params) + func deleteRequest(_ route: String) -> NetworkingRequest { + request(.delete, route) } internal func request(_ httpMethod: HTTPMethod, @@ -39,39 +38,8 @@ public extension NetworkingClient { let req = NetworkingRequest() req.httpMethod = httpMethod req.route = route - req.body = body - - let updateRequest = { [weak req, weak self] in - guard let self = self else { return } - req?.baseURL = self.baseURL - req?.logLevel = self.logLevel - req?.headers = self.headers - req?.parameterEncoding = self.parameterEncoding - req?.sessionConfiguration = self.sessionConfiguration - req?.timeout = self.timeout - } - updateRequest() - req.requestRetrier = { [weak self] in - self?.requestRetrier?($0, $1)? - .handleEvents(receiveOutput: { _ in - updateRequest() - }) - .eraseToAnyPublisher() - } - return req - } - - internal func request(_ httpMethod: HTTPMethod, - _ route: String, - params: Params = Params(), - encodableBody: Encodable? = nil - ) -> NetworkingRequest { - let req = NetworkingRequest() - body: HTTPBody? = nil) -> NetworkingRequest { - let req = NetworkingRequest() - req.httpMethod = httpMethod - req.route = route - req.body = body + req.urlParams = urlParams + req.httpBody = body let updateRequest = { [weak req, weak self] in guard let self = self else { return } diff --git a/Sources/Networking/Calls/NetworkingClient+Void.swift b/Sources/Networking/Calls/NetworkingClient+Void.swift index dea4a75..ba7aeac 100644 --- a/Sources/Networking/Calls/NetworkingClient+Void.swift +++ b/Sources/Networking/Calls/NetworkingClient+Void.swift @@ -10,78 +10,27 @@ import Combine public extension NetworkingClient { - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - get(route, params: params) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - post(route, params: params) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { + func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { post(route, body: body) .map { (data: Data) -> Void in () } .eraseToAnyPublisher() } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - put(route, params: params) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - patch(route, params: params) + func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + put(route, body: body) .map { (data: Data) -> Void in () } .eraseToAnyPublisher() } - func patch(_ route: String, body: Encodable) -> AnyPublisher { + func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { patch(route, body: body) .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() + .eraseToAnyPublisher() } - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - delete(route, params: params) + func delete(_ route: String) -> AnyPublisher { + delete(route) .map { (data: Data) -> Void in () } .eraseToAnyPublisher() } -} - -public extension NetworkingClient { - - func get(_ route: String, params: Params = Params()) async throws { - let req = request(.get, route, params: params) - _ = try await req.execute() - } - - func post(_ route: String, params: Params = Params()) async throws { - let req = request(.post, route, params: params) - _ = try await req.execute() - } - - func post(_ route: String, body: Encodable) async throws { - let req = request(.post, route, encodableBody: body) - _ = try await req.execute() - } - - func put(_ route: String, params: Params = Params()) async throws { - let req = request(.put, route, params: params) - _ = try await req.execute() - } - - func patch(_ route: String, params: Params = Params()) async throws { - let req = request(.patch, route, params: params) - _ = try await req.execute() - } - - func delete(_ route: String, params: Params = Params()) async throws { - let req = request(.delete, route, params: params) - _ = try await req.execute() - } -} +} \ No newline at end of file diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index a3a84c2..2fb73d4 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -11,7 +11,8 @@ import Combine public enum HTTPBody { case urlEncoded(params: Params) case json(encodable: Encodable) - case multipart(params:Params, parts:[MultipartData]) + case jsonParams(params: Params) + case multipart(params: Params?, parts:[MultipartData]) } public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? @@ -22,7 +23,7 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { var baseURL = "" var route = "" var httpMethod = HTTPMethod.get - var body: HTTPBody? = nil + var httpBody: HTTPBody? = nil public var urlParams: Params? = nil var headers = [String: String]() var logLevel: NetworkingLogLevel { @@ -140,13 +141,14 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { private func getURLWithParams() -> String { let urlString = baseURL + route - if params.isEmpty { return urlString } + guard let urlParams else { return urlString } + if urlParams.isEmpty { return urlString } guard let url = URL(string: urlString) else { return urlString } if var urlComponents = URLComponents(url: url ,resolvingAgainstBaseURL: false) { var queryItems = urlComponents.queryItems ?? [URLQueryItem]() - params.forEach { param in + urlParams.forEach { param in // arrayParam[] syntax if let array = param.value as? [CustomStringConvertible] { array.forEach { @@ -172,14 +174,6 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { } var request = URLRequest(url: url) - if httpMethod != .get && multipartData == nil { - switch parameterEncoding { - case .urlEncoded: - request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - case .json: - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - } - } request.httpMethod = httpMethod.rawValue for (key, value) in headers { @@ -189,33 +183,33 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { if let timeout = timeout { request.timeoutInterval = timeout } + - if httpMethod != .get && multipartData == nil { - if let encodableBody { + if let httpBody { + switch httpBody { + case .urlEncoded(params: let params): + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + request.httpBody = params.asPercentEncodedString().data(using: .utf8) + case .jsonParams(params: let params): + request.setValue("application/json", forHTTPHeaderField: "Content-Type") // Todo httpbody extension to simplify + let jsonData = try? JSONSerialization.data(withJSONObject: params) + request.httpBody = jsonData + case .json(encodable: let encodable): + // request.setValue("application/json", forHTTPHeaderField: "Content-Type") // Todo needed? let jsonEncoder = JSONEncoder() do { - let data = try jsonEncoder.encode(encodableBody) + let data = try jsonEncoder.encode(encodable) request.httpBody = data } catch { print(error) } - } else { - switch parameterEncoding { - case .urlEncoded: - request.httpBody = params.asPercentEncodedString().data(using: .utf8) - case .json: - let jsonData = try? JSONSerialization.data(withJSONObject: params) - request.httpBody = jsonData - } + case .multipart(params: let params, parts: let parts): + // Construct a unique boundary to separate values + let boundary = "Boundary-\(UUID().uuidString)" + request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + request.httpBody = buildMultipartHttpBody(params: params ?? [:], multiparts: parts, boundary: boundary) } - } - - // Multipart - if let multiparts = multipartData { - // Construct a unique boundary to separate values - let boundary = "Boundary-\(UUID().uuidString)" - request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") - request.httpBody = buildMultipartHttpBody(params: params, multiparts: multiparts, boundary: boundary) + } return request } diff --git a/Sources/Networking/NetworkingService.swift b/Sources/Networking/NetworkingService.swift index 096083e..861a847 100644 --- a/Sources/Networking/NetworkingService.swift +++ b/Sources/Networking/NetworkingService.swift @@ -14,6 +14,7 @@ public protocol NetworkingService { // Sugar, just forward calls to underlying network client +/* public extension NetworkingService { // Data @@ -382,4 +383,4 @@ public extension NetworkingService { try await network.delete(route, params: params, keypath: keypath) } } - +*/ diff --git a/Tests/NetworkingTests/GetRequestTests.swift b/Tests/NetworkingTests/GetRequestTests.swift index 4d22f88..1e8805e 100644 --- a/Tests/NetworkingTests/GetRequestTests.swift +++ b/Tests/NetworkingTests/GetRequestTests.swift @@ -63,7 +63,7 @@ final class GetRequestTests: XCTestCase { { "response": "OK" } """ - let _:Void = try await network.get("/users", params: ["search" : "lion"]) + let _:Void = try await network.get("/users", urlParams: ["search" : "lion"]) XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users?search=lion") } diff --git a/Tests/NetworkingTests/MultipartRequestTests.swift b/Tests/NetworkingTests/MultipartRequestTests.swift index 8d014d3..8d6a4fc 100644 --- a/Tests/NetworkingTests/MultipartRequestTests.swift +++ b/Tests/NetworkingTests/MultipartRequestTests.swift @@ -17,16 +17,14 @@ final class MultipartRequestTests: XCTestCase { let route = "/api/test" func testRequestGenerationWithSingleFile() { - // Set up test - let params: Params = [:] let multipartData = MultipartData(name: "test_name", fileData: "test data".data(using: .utf8)!, fileName: "file.txt", mimeType: "text/plain") // Construct request - let request = baseClient.request(.post, route, params: params) - request.multipartData = [multipartData] + let request = baseClient.request(.post, route, body: .multipart(params: nil, parts: [multipartData])) + if let urlRequest = request.buildURLRequest(), let body = urlRequest.httpBody, @@ -46,7 +44,6 @@ final class MultipartRequestTests: XCTestCase { } func testRequestGenerationWithParams() { - // Set up test let params: Params = ["test_name": "test_value"] let multipartData = MultipartData(name: "test_name", fileData: "test data".data(using: .utf8)!, @@ -54,8 +51,7 @@ final class MultipartRequestTests: XCTestCase { mimeType: "text/plain") // Construct request - let request = baseClient.request(.post, route, params: params) - request.multipartData = [multipartData] + let request = baseClient.request(.post, route, body: .multipart(params: params, parts: [multipartData])) if let urlRequest = request.buildURLRequest(), let body = urlRequest.httpBody, @@ -76,9 +72,8 @@ final class MultipartRequestTests: XCTestCase { } func testRequestGenerationWithMultipleFiles() { - // Set up test - let params: Params = [:] - let multipartData = [ + + let parts = [ MultipartData(name: "test_name", fileData: "test data".data(using: .utf8)!, fileName: "file.txt", @@ -90,8 +85,7 @@ final class MultipartRequestTests: XCTestCase { ] // Construct request - let request = baseClient.request(.post, route, params: params) - request.multipartData = multipartData + let request = baseClient.request(.post, route, body: .multipart(params: nil, parts: parts)) if let urlRequest = request.buildURLRequest(), let body = urlRequest.httpBody, diff --git a/Tests/NetworkingTests/PostRequestTests.swift b/Tests/NetworkingTests/PostRequestTests.swift index efb7243..5e50b31 100644 --- a/Tests/NetworkingTests/PostRequestTests.swift +++ b/Tests/NetworkingTests/PostRequestTests.swift @@ -320,7 +320,7 @@ class PostRequestTests: XCTestCase { """ let creds = Credentials(username: "john", password: "doe") - let data: Data = try await network.post("/users", body: creds) + let data: Data = try await network.post("/users", body: .json(encodable: creds)) XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) @@ -339,7 +339,7 @@ class PostRequestTests: XCTestCase { let expectationFinished = expectation(description: "Finished called") let creds = Credentials(username: "Alan", password: "Turing") - network.post("/users", body: creds).sink { completion in + network.post("/users", body: .json(encodable: creds)).sink { completion in switch completion { case .failure: XCTFail() From 7031e7d15010a4d56b8460483ff7cc238d2f77bc Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Mon, 13 Nov 2023 00:17:53 -1000 Subject: [PATCH 03/12] WIP - Reorganize files --- .../{Calls => }/Async Api/Delete.swift | 0 .../{Calls => }/Async Api/Get.swift | 0 .../{Calls => }/Async Api/Patch.swift | 0 .../{Calls => }/Async Api/Post.swift | 0 .../{Calls => }/Async Api/Put.swift | 0 .../NetworkingClient+AnyPublisherData.swift | 28 ------ .../Calls/NetworkingClient+Decodable.swift | 91 ------------------- .../Calls/NetworkingClient+JSON.swift | 38 -------- .../Calls/NetworkingClient+Multipart.swift | 57 ------------ .../Calls/NetworkingClient+Void.swift | 36 -------- .../Combine Api/Delete+Combine.swift | 44 +++++++++ .../{Calls => }/Combine Api/Get+Combine.swift | 0 .../Combine Api/Patch+Combine.swift | 53 +++++++++++ .../Networking/Combine Api/Post+Combine.swift | 53 +++++++++++ .../Combine Api/Publisher+JSON.swift | 19 ++++ .../Networking/Combine Api/Put+Combine.swift | 52 +++++++++++ Sources/Networking/HTTPBody.swift | 15 +++ ...orkingClient+NetworkingJSONDecodable.swift | 0 .../NetworkingClient+Requests.swift | 0 Sources/Networking/NetworkingRequest.swift | 6 -- .../Networking/Params+PercentEncoding.swift | 33 +++++++ Sources/Networking/Params.swift | 29 +----- Tests/NetworkingTests/NetworkingTests.swift | 2 +- Tests/NetworkingTests/PostRequestTests.swift | 4 +- 24 files changed, 274 insertions(+), 286 deletions(-) rename Sources/Networking/{Calls => }/Async Api/Delete.swift (100%) rename Sources/Networking/{Calls => }/Async Api/Get.swift (100%) rename Sources/Networking/{Calls => }/Async Api/Patch.swift (100%) rename Sources/Networking/{Calls => }/Async Api/Post.swift (100%) rename Sources/Networking/{Calls => }/Async Api/Put.swift (100%) delete mode 100644 Sources/Networking/Calls/Combine Api/NetworkingClient+AnyPublisherData.swift delete mode 100644 Sources/Networking/Calls/NetworkingClient+Decodable.swift delete mode 100644 Sources/Networking/Calls/NetworkingClient+JSON.swift delete mode 100644 Sources/Networking/Calls/NetworkingClient+Multipart.swift delete mode 100644 Sources/Networking/Calls/NetworkingClient+Void.swift create mode 100644 Sources/Networking/Combine Api/Delete+Combine.swift rename Sources/Networking/{Calls => }/Combine Api/Get+Combine.swift (100%) create mode 100644 Sources/Networking/Combine Api/Patch+Combine.swift create mode 100644 Sources/Networking/Combine Api/Post+Combine.swift create mode 100644 Sources/Networking/Combine Api/Publisher+JSON.swift create mode 100644 Sources/Networking/Combine Api/Put+Combine.swift create mode 100644 Sources/Networking/HTTPBody.swift rename Sources/Networking/{Calls => }/NetworkingClient+NetworkingJSONDecodable.swift (100%) rename Sources/Networking/{Calls => }/NetworkingClient+Requests.swift (100%) create mode 100644 Sources/Networking/Params+PercentEncoding.swift diff --git a/Sources/Networking/Calls/Async Api/Delete.swift b/Sources/Networking/Async Api/Delete.swift similarity index 100% rename from Sources/Networking/Calls/Async Api/Delete.swift rename to Sources/Networking/Async Api/Delete.swift diff --git a/Sources/Networking/Calls/Async Api/Get.swift b/Sources/Networking/Async Api/Get.swift similarity index 100% rename from Sources/Networking/Calls/Async Api/Get.swift rename to Sources/Networking/Async Api/Get.swift diff --git a/Sources/Networking/Calls/Async Api/Patch.swift b/Sources/Networking/Async Api/Patch.swift similarity index 100% rename from Sources/Networking/Calls/Async Api/Patch.swift rename to Sources/Networking/Async Api/Patch.swift diff --git a/Sources/Networking/Calls/Async Api/Post.swift b/Sources/Networking/Async Api/Post.swift similarity index 100% rename from Sources/Networking/Calls/Async Api/Post.swift rename to Sources/Networking/Async Api/Post.swift diff --git a/Sources/Networking/Calls/Async Api/Put.swift b/Sources/Networking/Async Api/Put.swift similarity index 100% rename from Sources/Networking/Calls/Async Api/Put.swift rename to Sources/Networking/Async Api/Put.swift diff --git a/Sources/Networking/Calls/Combine Api/NetworkingClient+AnyPublisherData.swift b/Sources/Networking/Calls/Combine Api/NetworkingClient+AnyPublisherData.swift deleted file mode 100644 index 76be2e0..0000000 --- a/Sources/Networking/Calls/Combine Api/NetworkingClient+AnyPublisherData.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// NetworkingClient+AnyPublisherData.swift -// -// -// Created by Sacha Durand Saint Omer on 12/11/2023. -// - -import Foundation -import Combine - -public extension NetworkingClient { - - func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { - request(.post, route, body: body).publisher() - } - - func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { - request(.put, route, body: body).publisher() - } - - func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { - request(.patch, route, body: body).publisher() - } - - func delete(_ route: String) -> AnyPublisher { - request(.delete, route).publisher() - } -} diff --git a/Sources/Networking/Calls/NetworkingClient+Decodable.swift b/Sources/Networking/Calls/NetworkingClient+Decodable.swift deleted file mode 100644 index 5971012..0000000 --- a/Sources/Networking/Calls/NetworkingClient+Decodable.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// NetworkingClient+Decodable.swift -// -// -// Created by Sacha DSO on 12/04/2022. -// - -import Foundation -import Combine - -public extension NetworkingClient { - - func post(_ route: String, - body: HTTPBody? = nil, - keypath: String? = nil) -> AnyPublisher { - return post(route, body: body) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func post(_ route: String, - body: HTTPBody? = nil, - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return post(route, body: body) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func put(_ route: String, - body: HTTPBody? = nil, - keypath: String? = nil) -> AnyPublisher { - return put(route, body: body) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func put(_ route: String, - body: HTTPBody? = nil, - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return put(route, body: body) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func patch(_ route: String, - body: HTTPBody? = nil, - keypath: String? = nil) -> AnyPublisher { - return patch(route, body: body) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - - // Array version - func patch(_ route: String, - body: HTTPBody? = nil, - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return patch(route, body: body) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func delete(_ route: String, - keypath: String? = nil) -> AnyPublisher { - return delete(route) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func delete(_ route: String, - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return delete(route) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } -} diff --git a/Sources/Networking/Calls/NetworkingClient+JSON.swift b/Sources/Networking/Calls/NetworkingClient+JSON.swift deleted file mode 100644 index d3972d0..0000000 --- a/Sources/Networking/Calls/NetworkingClient+JSON.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// NetworkingClient+JSON.swift -// -// -// Created by Sacha on 13/03/2020. -// - -import Foundation -import Combine - -public extension NetworkingClient { - - func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { - post(route, body: body).toJSON() - } - - func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { - put(route, body: body).toJSON() - } - - func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { - patch(route, body: body).toJSON() - } - - func delete(_ route: String) -> AnyPublisher { - delete(route).toJSON() - } -} - -// Data to JSON -extension Publisher where Output == Data { - - public func toJSON() -> AnyPublisher { - tryMap { data -> Any in - return try JSONSerialization.jsonObject(with: data, options: []) - }.eraseToAnyPublisher() - } -} diff --git a/Sources/Networking/Calls/NetworkingClient+Multipart.swift b/Sources/Networking/Calls/NetworkingClient+Multipart.swift deleted file mode 100644 index e122129..0000000 --- a/Sources/Networking/Calls/NetworkingClient+Multipart.swift +++ /dev/null @@ -1,57 +0,0 @@ -//// -//// NetworkingClient+Multipart.swift -//// -//// -//// Created by Sacha on 13/03/2020. -//// -// -//import Foundation -//import Combine -// -//public extension NetworkingClient { -// -// func post(_ route: String, -// multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { -// return post(route, params: params, multipartData: [multipartData]) -// } -// -// func put(_ route: String, -// multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { -// return put(route, params: params, multipartData: [multipartData]) -// } -// -// func patch(_ route: String, -// multipartData: MultipartData) -> AnyPublisher<(Data?, Progress), Error> { -// return patch(route, params: params, multipartData: [multipartData]) -// } -// -////TODO // Allow multiple multipart data -// -// -// func post(_ route: String, -// params: Params = Params(), -// multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { -// let req = request(.post, route, params: params) -// req.body = HTTPBody.multipart(params: params, parts: multipartData) -// return req.uploadPublisher() -// } -// -// func put(_ route: String, -// params: Params = Params(), -// multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { -// let req = request(.put, route, params: params) -// req.body = HTTPBody.multipart(params: params, parts: multipartData) -// req.multipartData = multipartData -// return req.uploadPublisher() -// } -// -// func patch(_ route: String, -// params: Params = Params(), -// multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { -// let req = request(.patch, route, params: params) -// req.multipartData = multipartData -// return req.uploadPublisher() -// } -//} - -//TODO put back diff --git a/Sources/Networking/Calls/NetworkingClient+Void.swift b/Sources/Networking/Calls/NetworkingClient+Void.swift deleted file mode 100644 index ba7aeac..0000000 --- a/Sources/Networking/Calls/NetworkingClient+Void.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// NetworkingClient+Void.swift -// -// -// Created by Sacha on 13/03/2020. -// - -import Foundation -import Combine - -public extension NetworkingClient { - - func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { - post(route, body: body) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { - put(route, body: body) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { - patch(route, body: body) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func delete(_ route: String) -> AnyPublisher { - delete(route) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } -} \ No newline at end of file diff --git a/Sources/Networking/Combine Api/Delete+Combine.swift b/Sources/Networking/Combine Api/Delete+Combine.swift new file mode 100644 index 0000000..eb3e57c --- /dev/null +++ b/Sources/Networking/Combine Api/Delete+Combine.swift @@ -0,0 +1,44 @@ +// +// Delete+Combine.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func delete(_ route: String) -> AnyPublisher { + delete(route) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func delete(_ route: String, + keypath: String? = nil) -> AnyPublisher { + return delete(route) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + // Array version + func delete(_ route: String, + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return delete(route) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + func delete(_ route: String) -> AnyPublisher { + delete(route).toJSON() + } + + func delete(_ route: String) -> AnyPublisher { + request(.delete, route).publisher() + } +} diff --git a/Sources/Networking/Calls/Combine Api/Get+Combine.swift b/Sources/Networking/Combine Api/Get+Combine.swift similarity index 100% rename from Sources/Networking/Calls/Combine Api/Get+Combine.swift rename to Sources/Networking/Combine Api/Get+Combine.swift diff --git a/Sources/Networking/Combine Api/Patch+Combine.swift b/Sources/Networking/Combine Api/Patch+Combine.swift new file mode 100644 index 0000000..28ca4c5 --- /dev/null +++ b/Sources/Networking/Combine Api/Patch+Combine.swift @@ -0,0 +1,53 @@ +// +// Patch+Combine.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + patch(route, body: body) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher { + return patch(route, body: body) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + + // Array version + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return patch(route, body: body) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + patch(route, body: body).toJSON() + } + + func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + request(.patch, route, body: body).publisher() + } + + func patch(_ route: String, + body: HTTPBody) -> AnyPublisher<(Data?, Progress), Error> { + let req = request(.patch, route, body: body) + return req.uploadPublisher() + } +} diff --git a/Sources/Networking/Combine Api/Post+Combine.swift b/Sources/Networking/Combine Api/Post+Combine.swift new file mode 100644 index 0000000..d0703d7 --- /dev/null +++ b/Sources/Networking/Combine Api/Post+Combine.swift @@ -0,0 +1,53 @@ +// +// Post+Combine.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + post(route, body: body) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher { + return post(route, body: body) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + // Array version + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return post(route, body: body) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + post(route, body: body).toJSON() + } + + func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + request(.post, route, body: body).publisher() + } + + func post(_ route: String, + body: HTTPBody) -> AnyPublisher<(Data?, Progress), Error> { + let req = request(.post, route, body: body) + return req.uploadPublisher() + } + +} diff --git a/Sources/Networking/Combine Api/Publisher+JSON.swift b/Sources/Networking/Combine Api/Publisher+JSON.swift new file mode 100644 index 0000000..082aa37 --- /dev/null +++ b/Sources/Networking/Combine Api/Publisher+JSON.swift @@ -0,0 +1,19 @@ +// +// NetworkingClient+JSON.swift +// +// +// Created by Sacha on 13/03/2020. +// + +import Foundation +import Combine + +// Data to JSON +extension Publisher where Output == Data { + + public func toJSON() -> AnyPublisher { + tryMap { data -> Any in + return try JSONSerialization.jsonObject(with: data, options: []) + }.eraseToAnyPublisher() + } +} diff --git a/Sources/Networking/Combine Api/Put+Combine.swift b/Sources/Networking/Combine Api/Put+Combine.swift new file mode 100644 index 0000000..f6cd4ba --- /dev/null +++ b/Sources/Networking/Combine Api/Put+Combine.swift @@ -0,0 +1,52 @@ +// +// Put+Combine.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + put(route, body: body) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher { + return put(route, body: body) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + // Array version + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher where T: Collection { + let keypath = keypath ?? defaultCollectionParsingKeyPath + return put(route, body: body) + .tryMap { json -> T in try self.toModel(json, keypath: keypath) } + .receive(on: DispatchQueue.main) + .eraseToAnyPublisher() + } + + func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + put(route, body: body).toJSON() + } + + func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + request(.put, route, body: body).publisher() + } + + func put(_ route: String, + body: HTTPBody) -> AnyPublisher<(Data?, Progress), Error> { + let req = request(.put, route, body: body) + return req.uploadPublisher() + } +} diff --git a/Sources/Networking/HTTPBody.swift b/Sources/Networking/HTTPBody.swift new file mode 100644 index 0000000..899614f --- /dev/null +++ b/Sources/Networking/HTTPBody.swift @@ -0,0 +1,15 @@ +// +// HTTPBody.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation + +public enum HTTPBody { + case urlEncoded(params: Params) + case json(Encodable) + case jsonParams(params: Params) + case multipart(params: Params?, parts:[MultipartData]) +} diff --git a/Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift b/Sources/Networking/NetworkingClient+NetworkingJSONDecodable.swift similarity index 100% rename from Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift rename to Sources/Networking/NetworkingClient+NetworkingJSONDecodable.swift diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/NetworkingClient+Requests.swift similarity index 100% rename from Sources/Networking/Calls/NetworkingClient+Requests.swift rename to Sources/Networking/NetworkingClient+Requests.swift diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index 2fb73d4..b347320 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -8,12 +8,6 @@ import Foundation import Combine -public enum HTTPBody { - case urlEncoded(params: Params) - case json(encodable: Encodable) - case jsonParams(params: Params) - case multipart(params: Params?, parts:[MultipartData]) -} public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? diff --git a/Sources/Networking/Params+PercentEncoding.swift b/Sources/Networking/Params+PercentEncoding.swift new file mode 100644 index 0000000..9e95089 --- /dev/null +++ b/Sources/Networking/Params+PercentEncoding.swift @@ -0,0 +1,33 @@ +// +// Params.swift +// +// +// Created by Sacha on 13/03/2020. +// + +import Foundation + +extension Params { + public func asPercentEncodedString(parentKey: String? = nil) -> String { + return self.map { key, value in + var escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" + if let `parentKey` = parentKey { + escapedKey = "\(parentKey)[\(escapedKey)]" + } + + if let dict = value as? Params { + return dict.asPercentEncodedString(parentKey: escapedKey) + } else if let array = value as? [CustomStringConvertible] { + return array.map { entry in + let escapedValue = "\(entry)" + .addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" + return "\(escapedKey)[]=\(escapedValue)" + }.joined(separator: "&") + } else { + let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" + return "\(escapedKey)=\(escapedValue)" + } + } + .joined(separator: "&") + } +} diff --git a/Sources/Networking/Params.swift b/Sources/Networking/Params.swift index 129824e..4214acd 100644 --- a/Sources/Networking/Params.swift +++ b/Sources/Networking/Params.swift @@ -1,35 +1,10 @@ // // Params.swift -// // -// Created by Sacha on 13/03/2020. +// +// Created by Sacha Durand Saint Omer on 13/11/2023. // import Foundation public typealias Params = [String: CustomStringConvertible] - -extension Params { - public func asPercentEncodedString(parentKey: String? = nil) -> String { - return self.map { key, value in - var escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" - if let `parentKey` = parentKey { - escapedKey = "\(parentKey)[\(escapedKey)]" - } - - if let dict = value as? Params { - return dict.asPercentEncodedString(parentKey: escapedKey) - } else if let array = value as? [CustomStringConvertible] { - return array.map { entry in - let escapedValue = "\(entry)" - .addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" - return "\(escapedKey)[]=\(escapedValue)" - }.joined(separator: "&") - } else { - let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" - return "\(escapedKey)=\(escapedValue)" - } - } - .joined(separator: "&") - } -} diff --git a/Tests/NetworkingTests/NetworkingTests.swift b/Tests/NetworkingTests/NetworkingTests.swift index 0c8ff14..49944f2 100644 --- a/Tests/NetworkingTests/NetworkingTests.swift +++ b/Tests/NetworkingTests/NetworkingTests.swift @@ -26,6 +26,6 @@ final class NetworkingTests: XCTestCase { receiveValue: { (json: Any) in print(json) }).store(in: &cancellables) - waitForExpectations(timeout: 1, handler: nil) + waitForExpectations(timeout: 3, handler: nil) } } diff --git a/Tests/NetworkingTests/PostRequestTests.swift b/Tests/NetworkingTests/PostRequestTests.swift index 5e50b31..8667f7d 100644 --- a/Tests/NetworkingTests/PostRequestTests.swift +++ b/Tests/NetworkingTests/PostRequestTests.swift @@ -320,7 +320,7 @@ class PostRequestTests: XCTestCase { """ let creds = Credentials(username: "john", password: "doe") - let data: Data = try await network.post("/users", body: .json(encodable: creds)) + let data: Data = try await network.post("/users", body: .json(creds)) XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) @@ -339,7 +339,7 @@ class PostRequestTests: XCTestCase { let expectationFinished = expectation(description: "Finished called") let creds = Credentials(username: "Alan", password: "Turing") - network.post("/users", body: .json(encodable: creds)).sink { completion in + network.post("/users", body: .json(creds)).sink { completion in switch completion { case .failure: XCTFail() From e70259556c861a1ea3798794ad015beaeb5290d0 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Mon, 13 Nov 2023 15:24:45 -1000 Subject: [PATCH 04/12] Update NetworkingClient+Requests.swift --- Sources/Networking/NetworkingClient+Requests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Networking/NetworkingClient+Requests.swift b/Sources/Networking/NetworkingClient+Requests.swift index 4e22d1d..8efaf40 100644 --- a/Sources/Networking/NetworkingClient+Requests.swift +++ b/Sources/Networking/NetworkingClient+Requests.swift @@ -34,7 +34,7 @@ public extension NetworkingClient { _ route: String, urlParams: Params? = nil, body: HTTPBody? = nil - ) -> NetworkingRequest { + ) -> NetworkingRequest { let req = NetworkingRequest() req.httpMethod = httpMethod req.route = route From f92cf27f921a454def2393bbeb17a46ef30d888d Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Mon, 13 Nov 2023 17:48:14 -1000 Subject: [PATCH 05/12] Puts back async NetworkingService shortcuts --- Sources/Networking/Async Api/Delete.swift | 31 ++++- Sources/Networking/Async Api/Get.swift | 28 ++++ Sources/Networking/Async Api/Patch.swift | 27 ++++ Sources/Networking/Async Api/Post.swift | 28 +++- Sources/Networking/Async Api/Put.swift | 27 ++++ Sources/Networking/NetworkingService.swift | 155 +-------------------- 6 files changed, 138 insertions(+), 158 deletions(-) diff --git a/Sources/Networking/Async Api/Delete.swift b/Sources/Networking/Async Api/Delete.swift index 48bf82c..01129e5 100644 --- a/Sources/Networking/Async Api/Delete.swift +++ b/Sources/Networking/Async Api/Delete.swift @@ -14,18 +14,18 @@ public extension NetworkingClient { } func delete(_ route: String, - keypath: String? = nil) async throws -> T { + keypath: String? = nil) async throws -> T { let json: Any = try await delete(route) return try self.toModel(json, keypath: keypath) } func delete(_ route: String, - keypath: String? = nil) async throws -> T where T: Collection { + keypath: String? = nil) async throws -> T where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath let json: Any = try await delete(route) return try self.toModel(json, keypath: keypath) } - + func delete(_ route: String) async throws -> Any { let data: Data = try await delete(route) return try JSONSerialization.jsonObject(with: data, options: []) @@ -35,3 +35,28 @@ public extension NetworkingClient { try await request(.delete, route).execute() } } + +public extension NetworkingService { + + func delete(_ route: String) async throws { + return try await network.delete(route) + } + + func delete(_ route: String, + keypath: String? = nil) async throws -> T { + try await network.delete(route, keypath: keypath) + } + + func delete(_ route: String, + keypath: String? = nil) async throws -> T where T: Collection { + try await network.delete(route, keypath: keypath) + } + + func delete(_ route: String) async throws -> Any { + try await network.delete(route) + } + + func delete(_ route: String) async throws -> Data { + try await network.delete(route) + } +} diff --git a/Sources/Networking/Async Api/Get.swift b/Sources/Networking/Async Api/Get.swift index 373c98b..a53b12f 100644 --- a/Sources/Networking/Async Api/Get.swift +++ b/Sources/Networking/Async Api/Get.swift @@ -37,3 +37,31 @@ public extension NetworkingClient { try await request(.get, route, urlParams: urlParams).execute() } } + +public extension NetworkingService { + + func get(_ route: String, urlParams: Params? = nil) async throws { + return try await network.get(route, urlParams: urlParams) + } + + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) async throws -> T { + try await network.get(route, urlParams: urlParams, keypath: keypath) + } + + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) async throws -> T where T: Collection { + try await network.get(route, urlParams: urlParams, keypath: keypath) + } + + + func get(_ route: String, urlParams: Params? = nil) async throws -> Any { + try await network.get(route, urlParams: urlParams) + } + + func get(_ route: String, urlParams: Params? = nil) async throws -> Data { + try await network.get(route, urlParams: urlParams) + } +} diff --git a/Sources/Networking/Async Api/Patch.swift b/Sources/Networking/Async Api/Patch.swift index e5fc5a8..9f5147d 100644 --- a/Sources/Networking/Async Api/Patch.swift +++ b/Sources/Networking/Async Api/Patch.swift @@ -40,3 +40,30 @@ public extension NetworkingClient { } } + +public extension NetworkingService { + + func patch(_ route: String, body: HTTPBody? = nil) async throws { + return try await network.patch(route, body: body) + } + + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T { + try await network.patch(route, body: body, keypath: keypath) + } + + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T where T: Collection { + try await network.patch(route, body: body, keypath: keypath) + } + + func patch(_ route: String, body: HTTPBody? = nil) async throws -> Any { + try await network.patch(route, body: body) + } + + func patch(_ route: String, body: HTTPBody? = nil) async throws -> Data { + try await network.patch(route, body: body) + } +} diff --git a/Sources/Networking/Async Api/Post.swift b/Sources/Networking/Async Api/Post.swift index 5ef0e52..8efa886 100644 --- a/Sources/Networking/Async Api/Post.swift +++ b/Sources/Networking/Async Api/Post.swift @@ -13,7 +13,6 @@ public extension NetworkingClient { let _: Data = try await post(route, body: body) } - func post(_ route: String, body: HTTPBody? = nil, keypath: String? = nil) async throws -> T { @@ -38,3 +37,30 @@ public extension NetworkingClient { try await request(.post, route, body: body).execute() } } + +public extension NetworkingService { + + func post(_ route: String, body: HTTPBody? = nil) async throws { + return try await network.post(route, body: body) + } + + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T { + try await network.post(route, body: body, keypath: keypath) + } + + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T where T: Collection { + try await network.post(route, body: body, keypath: keypath) + } + + func post(_ route: String, body: HTTPBody? = nil) async throws -> Any { + try await network.post(route, body: body) + } + + func post(_ route: String, body: HTTPBody? = nil) async throws -> Data { + try await network.post(route, body: body) + } +} diff --git a/Sources/Networking/Async Api/Put.swift b/Sources/Networking/Async Api/Put.swift index e8d3a11..fde13fd 100644 --- a/Sources/Networking/Async Api/Put.swift +++ b/Sources/Networking/Async Api/Put.swift @@ -37,3 +37,30 @@ public extension NetworkingClient { try await request(.put, route, body: body).execute() } } + +public extension NetworkingService { + + func put(_ route: String, body: HTTPBody? = nil) async throws { + return try await network.put(route, body: body) + } + + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T { + try await network.put(route, body: body, keypath: keypath) + } + + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) async throws -> T where T: Collection { + try await network.put(route, body: body, keypath: keypath) + } + + func put(_ route: String, body: HTTPBody? = nil) async throws -> Any { + try await network.put(route, body: body) + } + + func put(_ route: String, body: HTTPBody? = nil) async throws -> Data { + try await network.put(route, body: body) + } +} diff --git a/Sources/Networking/NetworkingService.swift b/Sources/Networking/NetworkingService.swift index 861a847..3873218 100644 --- a/Sources/Networking/NetworkingService.swift +++ b/Sources/Networking/NetworkingService.swift @@ -14,6 +14,7 @@ public protocol NetworkingService { // Sugar, just forward calls to underlying network client +// TODO Put back /* public extension NetworkingService { @@ -229,158 +230,4 @@ public extension NetworkingService { network.delete(route, params: params, keypath: keypath) } } - -// Async -public extension NetworkingService { - - // Data - - func get(_ route: String, params: Params = Params()) async throws -> Data { - try await network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) async throws -> Data { - try await network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) async throws -> Data { - try await network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) async throws -> Data { - try await network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) async throws -> Data { - try await network.patch(route, params: params) - } - - func delete(_ route: String, params: Params = Params()) async throws -> Data { - try await network.delete(route, params: params) - } - - // Void - - func get(_ route: String, params: Params = Params()) async throws { - return try await network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) async throws { - return try await network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) async throws { - return try await network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) async throws { - return try await network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) async throws { - return try await network.patch(route, params: params) - } - - func delete(_ route: String, params: Params = Params()) async throws { - return try await network.delete(route, params: params) - } - - // JSON - - func get(_ route: String, params: Params = Params()) async throws -> Any { - try await network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) async throws -> Any { - try await network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) async throws -> Any { - try await network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) async throws -> Any { - try await network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) async throws -> Any { - try await network.patch(route, params: params) - } - - func delete(_ route: String, params: Params = Params()) async throws -> Any { - try await network.delete(route, params: params) - } - - // Decodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - try await network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - try await network.post(route, params: params, keypath: keypath) - } - - func post(_ route: String, body: Encodable) async throws -> T { - try await network.post(route, body: body) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - try await network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - try await network.patch(route, params: params, keypath: keypath) - } - - func patch(_ route: String, body: Encodable) async throws -> T { - try await network.patch(route, body: body) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T { - try await network.delete(route, params: params, keypath: keypath) - } - - // Array Decodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - try await network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - try await network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - try await network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - try await network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) async throws -> T where T: Collection { - try await network.delete(route, params: params, keypath: keypath) - } -} */ From 6b7cb72e395bb7112f306b8a610fe248565d3669 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Tue, 14 Nov 2023 14:16:15 -1000 Subject: [PATCH 06/12] Finish reorganizing NetworkingService --- .../Combine Api/Delete+Combine.swift | 41 +++- .../Networking/Combine Api/Get+Combine.swift | 59 ++++- .../Combine Api/Patch+Combine.swift | 47 +++- .../Networking/Combine Api/Post+Combine.swift | 39 ++++ .../Networking/Combine Api/Put+Combine.swift | 39 ++++ Sources/Networking/HTTPBody.swift | 54 +++++ .../NetworkingClient+Requests.swift | 3 +- Sources/Networking/NetworkingClient.swift | 1 - Sources/Networking/NetworkingRequest.swift | 60 +---- Sources/Networking/NetworkingService.swift | 220 ------------------ Tests/NetworkingTests/PostRequestTests.swift | 7 +- 11 files changed, 282 insertions(+), 288 deletions(-) diff --git a/Sources/Networking/Combine Api/Delete+Combine.swift b/Sources/Networking/Combine Api/Delete+Combine.swift index eb3e57c..7e8799a 100644 --- a/Sources/Networking/Combine Api/Delete+Combine.swift +++ b/Sources/Networking/Combine Api/Delete+Combine.swift @@ -17,7 +17,7 @@ public extension NetworkingClient { } func delete(_ route: String, - keypath: String? = nil) -> AnyPublisher { + keypath: String? = nil) -> AnyPublisher { return delete(route) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) @@ -26,7 +26,7 @@ public extension NetworkingClient { // Array version func delete(_ route: String, - keypath: String? = nil) -> AnyPublisher where T: Collection { + keypath: String? = nil) -> AnyPublisher where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath return delete(route) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } @@ -42,3 +42,40 @@ public extension NetworkingClient { request(.delete, route).publisher() } } + +public extension NetworkingService { + + func delete(_ route: String) -> AnyPublisher { + network.delete(route) + } + + func delete(_ route: String, + keypath: String? = nil) -> AnyPublisher { + network.delete(route, keypath: keypath) + } + + func delete(_ route: String, + params: Params = Params(), + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.delete(route, keypath: keypath) + } + + func delete(_ route: String, + keypath: String? = nil) -> AnyPublisher { + network.delete(route, keypath: keypath) + } + + func delete(_ route: String, + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.delete(route, keypath: keypath) + } + + func delete(_ route: String) -> AnyPublisher { + network.delete(route) + } + + func delete(_ route: String) -> AnyPublisher { + network.delete(route) + } +} + diff --git a/Sources/Networking/Combine Api/Get+Combine.swift b/Sources/Networking/Combine Api/Get+Combine.swift index 235f5cd..e003d34 100644 --- a/Sources/Networking/Combine Api/Get+Combine.swift +++ b/Sources/Networking/Combine Api/Get+Combine.swift @@ -16,14 +16,6 @@ public extension NetworkingClient { .eraseToAnyPublisher() } - func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { - request(.get, route, urlParams: urlParams).publisher() - } - - func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { - get(route, urlParams: urlParams).toJSON() - } - func get(_ route: String, urlParams: Params? = nil, keypath: String? = nil) -> AnyPublisher { @@ -43,4 +35,55 @@ public extension NetworkingClient { .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } + + func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { + get(route, urlParams: urlParams).toJSON() + } + + func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { + request(.get, route, urlParams: urlParams).publisher() + } +} + +public extension NetworkingService { + + func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { + network.get(route, urlParams: urlParams) + } + + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) -> AnyPublisher { + network.get(route, urlParams: urlParams, keypath: keypath) + } + + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.get(route, urlParams: urlParams, keypath: keypath) + } + + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) -> AnyPublisher { + network.get(route, urlParams: urlParams, keypath: keypath) + } + + func get(_ route: String, + urlParams: Params? = nil, + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.get(route, urlParams: urlParams, keypath: keypath) + } + + + func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { + network.get(route, urlParams: urlParams) + } + + func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { + network.get(route, urlParams: urlParams) + } + + + } diff --git a/Sources/Networking/Combine Api/Patch+Combine.swift b/Sources/Networking/Combine Api/Patch+Combine.swift index 28ca4c5..dab8bef 100644 --- a/Sources/Networking/Combine Api/Patch+Combine.swift +++ b/Sources/Networking/Combine Api/Patch+Combine.swift @@ -1,6 +1,6 @@ // // Patch+Combine.swift -// +// // // Created by Sacha Durand Saint Omer on 12/11/2023. // @@ -18,7 +18,7 @@ public extension NetworkingClient { func patch(_ route: String, body: HTTPBody? = nil, - keypath: String? = nil) -> AnyPublisher { + keypath: String? = nil) -> AnyPublisher { return patch(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) @@ -29,7 +29,7 @@ public extension NetworkingClient { // Array version func patch(_ route: String, body: HTTPBody? = nil, - keypath: String? = nil) -> AnyPublisher where T: Collection { + keypath: String? = nil) -> AnyPublisher where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath return patch(route, body: body) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } @@ -46,8 +46,47 @@ public extension NetworkingClient { } func patch(_ route: String, - body: HTTPBody) -> AnyPublisher<(Data?, Progress), Error> { + body: HTTPBody? = nil) -> AnyPublisher<(Data?, Progress), Error> { let req = request(.patch, route, body: body) return req.uploadPublisher() } } + +public extension NetworkingService { + + func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + network.patch(route, body: body) + } + + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher { + network.patch(route, body: body, keypath: keypath) + } + + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.patch(route, body: body, keypath: keypath) + } + + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher { + network.patch(route, body: body, keypath: keypath) + } + + func patch(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.patch(route, body: body, keypath: keypath) + } + + func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + network.patch(route, body: body) + } + + func patch(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + network.patch(route, body: body) + } +} diff --git a/Sources/Networking/Combine Api/Post+Combine.swift b/Sources/Networking/Combine Api/Post+Combine.swift index d0703d7..b889a8b 100644 --- a/Sources/Networking/Combine Api/Post+Combine.swift +++ b/Sources/Networking/Combine Api/Post+Combine.swift @@ -49,5 +49,44 @@ public extension NetworkingClient { let req = request(.post, route, body: body) return req.uploadPublisher() } + +} +public extension NetworkingService { + + func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + network.post(route, body: body) + } + + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher { + network.post(route, body: body, keypath: keypath) + } + + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.post(route, body: body, keypath: keypath) + } + + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher { + network.post(route, body: body, keypath: keypath) + } + + func post(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.post(route, body: body, keypath: keypath) + } + + func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + network.post(route, body: body) + } + + func post(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + network.post(route, body: body) + } } diff --git a/Sources/Networking/Combine Api/Put+Combine.swift b/Sources/Networking/Combine Api/Put+Combine.swift index f6cd4ba..f7f8cb7 100644 --- a/Sources/Networking/Combine Api/Put+Combine.swift +++ b/Sources/Networking/Combine Api/Put+Combine.swift @@ -50,3 +50,42 @@ public extension NetworkingClient { return req.uploadPublisher() } } + +public extension NetworkingService { + + func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + network.put(route, body: body) + } + + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher { + network.put(route, body: body, keypath: keypath) + } + + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.put(route, body: body, keypath: keypath) + } + + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher { + network.put(route, body: body, keypath: keypath) + } + + func put(_ route: String, + body: HTTPBody? = nil, + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.put(route, body: body, keypath: keypath) + } + + func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + network.put(route, body: body) + } + + func put(_ route: String, body: HTTPBody? = nil) -> AnyPublisher { + network.put(route, body: body) + } +} diff --git a/Sources/Networking/HTTPBody.swift b/Sources/Networking/HTTPBody.swift index 899614f..8df41fd 100644 --- a/Sources/Networking/HTTPBody.swift +++ b/Sources/Networking/HTTPBody.swift @@ -13,3 +13,57 @@ public enum HTTPBody { case jsonParams(params: Params) case multipart(params: Params?, parts:[MultipartData]) } + + +extension HTTPBody { + func header(for boundary: String) -> String { + switch self { + case .urlEncoded(_): + return "application/x-www-form-urlencoded" + case .json(_): + return "application/json" + case .jsonParams(_): + return "application/json" + case .multipart(_, _): + return "multipart/form-data; boundary=\(boundary)" + } + } +} + +extension HTTPBody { + + func body(for boundary: String) -> Data? { + switch self { + case .urlEncoded(params: let params): + return params.asPercentEncodedString().data(using: .utf8) + case .json(let encodable): + do { + let jsonEncoder = JSONEncoder() + let data = try jsonEncoder.encode(encodable) + return data + } catch { + print(error) + return nil + } + case .jsonParams(params: let params): + return try? JSONSerialization.data(withJSONObject: params) + case .multipart(params: let params, parts: let parts): + return buildMultipartHttpBody(params: params ?? [:], multiparts: parts, boundary: boundary) + } + } + + private func buildMultipartHttpBody(params: Params, multiparts: [MultipartData], boundary: String) -> Data { + // Combine all multiparts together + let allMultiparts: [HttpBodyConvertible] = [params] + multiparts + let boundaryEnding = "--\(boundary)--".data(using: .utf8)! + + // Convert multiparts to boundary-seperated Data and combine them + return allMultiparts + .map { (multipart: HttpBodyConvertible) -> Data in + return multipart.buildHttpBodyPart(boundary: boundary) + } + .reduce(Data.init(), +) + + boundaryEnding + } +} + diff --git a/Sources/Networking/NetworkingClient+Requests.swift b/Sources/Networking/NetworkingClient+Requests.swift index 8efaf40..eab693b 100644 --- a/Sources/Networking/NetworkingClient+Requests.swift +++ b/Sources/Networking/NetworkingClient+Requests.swift @@ -38,7 +38,7 @@ public extension NetworkingClient { let req = NetworkingRequest() req.httpMethod = httpMethod req.route = route - req.urlParams = urlParams + req.queryParams = urlParams req.httpBody = body let updateRequest = { [weak req, weak self] in @@ -46,7 +46,6 @@ public extension NetworkingClient { req?.baseURL = self.baseURL req?.logLevel = self.logLevel req?.headers = self.headers - req?.parameterEncoding = self.parameterEncoding req?.sessionConfiguration = self.sessionConfiguration req?.timeout = self.timeout } diff --git a/Sources/Networking/NetworkingClient.swift b/Sources/Networking/NetworkingClient.swift index b3c9dfb..378e718 100644 --- a/Sources/Networking/NetworkingClient.swift +++ b/Sources/Networking/NetworkingClient.swift @@ -11,7 +11,6 @@ public class NetworkingClient { public var defaultCollectionParsingKeyPath: String? let baseURL: String public var headers = [String: String]() - public var parameterEncoding = ParameterEncoding.urlEncoded public var timeout: TimeInterval? public var sessionConfiguration = URLSessionConfiguration.default public var requestRetrier: NetworkRequestRetrier? diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index b347320..dea07ed 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -13,12 +13,11 @@ public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) public class NetworkingRequest: NSObject, URLSessionTaskDelegate { - var parameterEncoding = ParameterEncoding.urlEncoded var baseURL = "" var route = "" var httpMethod = HTTPMethod.get var httpBody: HTTPBody? = nil - public var urlParams: Params? = nil + public var queryParams: Params? = nil var headers = [String: String]() var logLevel: NetworkingLogLevel { get { return logger.logLevel } @@ -135,14 +134,14 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { private func getURLWithParams() -> String { let urlString = baseURL + route - guard let urlParams else { return urlString } - if urlParams.isEmpty { return urlString } + guard let queryParams else { return urlString } + if queryParams.isEmpty { return urlString } guard let url = URL(string: urlString) else { return urlString } if var urlComponents = URLComponents(url: url ,resolvingAgainstBaseURL: false) { var queryItems = urlComponents.queryItems ?? [URLQueryItem]() - urlParams.forEach { param in + queryParams.forEach { param in // arrayParam[] syntax if let array = param.value as? [CustomStringConvertible] { array.forEach { @@ -170,56 +169,22 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { request.httpMethod = httpMethod.rawValue + for (key, value) in headers { request.setValue(value, forHTTPHeaderField: key) } - if let timeout = timeout { + if let timeout { request.timeoutInterval = timeout } - if let httpBody { - switch httpBody { - case .urlEncoded(params: let params): - request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - request.httpBody = params.asPercentEncodedString().data(using: .utf8) - case .jsonParams(params: let params): - request.setValue("application/json", forHTTPHeaderField: "Content-Type") // Todo httpbody extension to simplify - let jsonData = try? JSONSerialization.data(withJSONObject: params) - request.httpBody = jsonData - case .json(encodable: let encodable): - // request.setValue("application/json", forHTTPHeaderField: "Content-Type") // Todo needed? - let jsonEncoder = JSONEncoder() - do { - let data = try jsonEncoder.encode(encodable) - request.httpBody = data - } catch { - print(error) - } - case .multipart(params: let params, parts: let parts): - // Construct a unique boundary to separate values - let boundary = "Boundary-\(UUID().uuidString)" - request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") - request.httpBody = buildMultipartHttpBody(params: params ?? [:], multiparts: parts, boundary: boundary) - } - + let boundary = "Boundary-\(UUID().uuidString)" + request.setValue(httpBody.header(for: boundary), forHTTPHeaderField: "Content-Type") + request.httpBody = httpBody.body(for: boundary) } - return request - } - - private func buildMultipartHttpBody(params: Params, multiparts: [MultipartData], boundary: String) -> Data { - // Combine all multiparts together - let allMultiparts: [HttpBodyConvertible] = [params] + multiparts - let boundaryEnding = "--\(boundary)--".data(using: .utf8)! - // Convert multiparts to boundary-seperated Data and combine them - return allMultiparts - .map { (multipart: HttpBodyConvertible) -> Data in - return multipart.buildHttpBodyPart(boundary: boundary) - } - .reduce(Data.init(), +) - + boundaryEnding + return request } public func urlSession(_ session: URLSession, @@ -244,8 +209,3 @@ extension CharacterSet { return allowed }() } - -public enum ParameterEncoding { - case urlEncoded - case json -} diff --git a/Sources/Networking/NetworkingService.swift b/Sources/Networking/NetworkingService.swift index 3873218..78855ec 100644 --- a/Sources/Networking/NetworkingService.swift +++ b/Sources/Networking/NetworkingService.swift @@ -11,223 +11,3 @@ import Combine public protocol NetworkingService { var network: NetworkingClient { get } } - -// Sugar, just forward calls to underlying network client - -// TODO Put back -/* -public extension NetworkingService { - - // Data - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - network.patch(route, params: params) - } - - func patch(_ route: String, body: Encodable) -> AnyPublisher { - network.patch(route, body: body) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - network.delete(route, params: params) - } - - // Void - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - network.patch(route, params: params) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - network.delete(route, params: params) - } - - // JSON - - func get(_ route: String, params: Params = Params()) -> AnyPublisher { - network.get(route, params: params) - } - - func post(_ route: String, params: Params = Params()) -> AnyPublisher { - network.post(route, params: params) - } - - func post(_ route: String, body: Encodable) -> AnyPublisher { - network.post(route, body: body) - } - - func put(_ route: String, params: Params = Params()) -> AnyPublisher { - network.put(route, params: params) - } - - func patch(_ route: String, params: Params = Params()) -> AnyPublisher { - network.patch(route, params: params) - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - network.delete(route, params: params) - } - - // Decodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.delete(route, params: params, keypath: keypath) - } - - // Array Decodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - network.delete(route, params: params, keypath: keypath) - } - - // NetworkingJSONDecodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - network.delete(route, params: params, keypath: keypath) - } - - - - // Array NetworkingJSONDecodable - - func get(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.get(route, params: params, keypath: keypath) - } - - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.post(route, params: params, keypath: keypath) - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.put(route, params: params, keypath: keypath) - } - - func patch(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.patch(route, params: params, keypath: keypath) - } - - func delete(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.delete(route, params: params, keypath: keypath) - } -} -*/ diff --git a/Tests/NetworkingTests/PostRequestTests.swift b/Tests/NetworkingTests/PostRequestTests.swift index 8667f7d..be37296 100644 --- a/Tests/NetworkingTests/PostRequestTests.swift +++ b/Tests/NetworkingTests/PostRequestTests.swift @@ -300,6 +300,7 @@ class PostRequestTests: XCTestCase { case .finished: XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") + expectationFinished.fulfill() } } receiveValue: { (userJSON: [UserJSON]) in @@ -324,7 +325,7 @@ class PostRequestTests: XCTestCase { XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "POST") XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users") XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8)) - + XCTAssertEqual(MockingURLProtocol.currentRequest?.value(forHTTPHeaderField: "Content-Type"), "application/json") let body = MockingURLProtocol.currentRequest?.httpBodyStreamAsDictionary() XCTAssertEqual(body?["username"] as? String, "john") XCTAssertEqual(body?["password"] as? String, "doe") @@ -367,3 +368,7 @@ struct Credentials: Encodable { let username: String let password: String } + + +//test Content type header +//XCTAssertEqual(MockingURLProtocol.currentRequest?.value(forHTTPHeaderField: "Content-Type"), "application/json") From 47e39cdd9a299afe18d0ef7cc38ed417ff64463f Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Tue, 14 Nov 2023 14:58:58 -1000 Subject: [PATCH 07/12] Removes unnecessary HTTPBody .jsonParams --- Sources/Networking/Async Api/Get.swift | 40 ++++++++-------- .../Networking/Combine Api/Get+Combine.swift | 48 +++++++++---------- Sources/Networking/HTTPBody.swift | 9 +--- ...orkingClient+NetworkingJSONDecodable.swift | 8 ++-- .../NetworkingClient+Requests.swift | 8 ++-- Sources/Networking/NetworkingClient.swift | 8 ++++ Sources/Networking/NetworkingService.swift | 1 - 7 files changed, 62 insertions(+), 60 deletions(-) diff --git a/Sources/Networking/Async Api/Get.swift b/Sources/Networking/Async Api/Get.swift index a53b12f..9481f3a 100644 --- a/Sources/Networking/Async Api/Get.swift +++ b/Sources/Networking/Async Api/Get.swift @@ -9,59 +9,59 @@ import Foundation public extension NetworkingClient { - func get(_ route: String, urlParams: Params? = nil) async throws { - let _:Data = try await get(route, urlParams: urlParams) + func get(_ route: String, params: Params? = nil) async throws { + let _:Data = try await get(route, params: params) } func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) async throws -> T { - let json: Any = try await get(route, urlParams: urlParams) + let json: Any = try await get(route, params: params) return try self.toModel(json, keypath: keypath) } func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) async throws -> T where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - let json: Any = try await get(route, urlParams: urlParams) + let json: Any = try await get(route, params: params) return try self.toModel(json, keypath: keypath) } - func get(_ route: String, urlParams: Params? = nil) async throws -> Any { - let data: Data = try await get(route, urlParams: urlParams) + func get(_ route: String, params: Params? = nil) async throws -> Any { + let data: Data = try await get(route, params: params) return try JSONSerialization.jsonObject(with: data, options: []) } - func get(_ route: String, urlParams: Params? = nil) async throws -> Data { - try await request(.get, route, urlParams: urlParams).execute() + func get(_ route: String, params: Params? = nil) async throws -> Data { + try await request(.get, route, queryParams: params).execute() } } public extension NetworkingService { - func get(_ route: String, urlParams: Params? = nil) async throws { - return try await network.get(route, urlParams: urlParams) + func get(_ route: String, params: Params? = nil) async throws { + return try await network.get(route, params: params) } func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) async throws -> T { - try await network.get(route, urlParams: urlParams, keypath: keypath) + try await network.get(route, params: params, keypath: keypath) } func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) async throws -> T where T: Collection { - try await network.get(route, urlParams: urlParams, keypath: keypath) + try await network.get(route, params: params, keypath: keypath) } - func get(_ route: String, urlParams: Params? = nil) async throws -> Any { - try await network.get(route, urlParams: urlParams) + func get(_ route: String, params: Params? = nil) async throws -> Any { + try await network.get(route, params: params) } - func get(_ route: String, urlParams: Params? = nil) async throws -> Data { - try await network.get(route, urlParams: urlParams) + func get(_ route: String, params: Params? = nil) async throws -> Data { + try await network.get(route, params: params) } } diff --git a/Sources/Networking/Combine Api/Get+Combine.swift b/Sources/Networking/Combine Api/Get+Combine.swift index e003d34..97263ed 100644 --- a/Sources/Networking/Combine Api/Get+Combine.swift +++ b/Sources/Networking/Combine Api/Get+Combine.swift @@ -10,16 +10,16 @@ import Combine public extension NetworkingClient { - func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { - get(route, urlParams: urlParams) + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + get(route, params: params) .map { (data: Data) -> Void in () } .eraseToAnyPublisher() } func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) -> AnyPublisher { - return get(route, urlParams: urlParams) + return get(route, params: params) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() @@ -27,61 +27,61 @@ public extension NetworkingClient { // Array version func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) -> AnyPublisher where T: Collection { let keypath = keypath ?? defaultCollectionParsingKeyPath - return get(route, urlParams: urlParams) + return get(route, params: params) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } - func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { - get(route, urlParams: urlParams).toJSON() + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + get(route, params: params).toJSON() } - func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { - request(.get, route, urlParams: urlParams).publisher() + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + request(.get, route, queryParams: params).publisher() } } public extension NetworkingService { - func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { - network.get(route, urlParams: urlParams) + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + network.get(route, params: params) } func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) -> AnyPublisher { - network.get(route, urlParams: urlParams, keypath: keypath) + network.get(route, params: params, keypath: keypath) } func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) -> AnyPublisher where T: Collection { - network.get(route, urlParams: urlParams, keypath: keypath) + network.get(route, params: params, keypath: keypath) } func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) -> AnyPublisher { - network.get(route, urlParams: urlParams, keypath: keypath) + network.get(route, params: params, keypath: keypath) } func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) -> AnyPublisher<[T], Error> { - network.get(route, urlParams: urlParams, keypath: keypath) + network.get(route, params: params, keypath: keypath) } - func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { - network.get(route, urlParams: urlParams) + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + network.get(route, params: params) } - func get(_ route: String, urlParams: Params? = nil) -> AnyPublisher { - network.get(route, urlParams: urlParams) + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + network.get(route, params: params) } diff --git a/Sources/Networking/HTTPBody.swift b/Sources/Networking/HTTPBody.swift index 8df41fd..268ae76 100644 --- a/Sources/Networking/HTTPBody.swift +++ b/Sources/Networking/HTTPBody.swift @@ -9,9 +9,8 @@ import Foundation public enum HTTPBody { case urlEncoded(params: Params) - case json(Encodable) - case jsonParams(params: Params) - case multipart(params: Params?, parts:[MultipartData]) + case json(_ encodable: Encodable) + case multipart(params: Params? = nil, parts:[MultipartData]) } @@ -22,8 +21,6 @@ extension HTTPBody { return "application/x-www-form-urlencoded" case .json(_): return "application/json" - case .jsonParams(_): - return "application/json" case .multipart(_, _): return "multipart/form-data; boundary=\(boundary)" } @@ -45,8 +42,6 @@ extension HTTPBody { print(error) return nil } - case .jsonParams(params: let params): - return try? JSONSerialization.data(withJSONObject: params) case .multipart(params: let params, parts: let parts): return buildMultipartHttpBody(params: params ?? [:], multiparts: parts, boundary: boundary) } diff --git a/Sources/Networking/NetworkingClient+NetworkingJSONDecodable.swift b/Sources/Networking/NetworkingClient+NetworkingJSONDecodable.swift index 1bd60b0..e80ab68 100644 --- a/Sources/Networking/NetworkingClient+NetworkingJSONDecodable.swift +++ b/Sources/Networking/NetworkingClient+NetworkingJSONDecodable.swift @@ -17,9 +17,9 @@ public protocol NetworkingJSONDecodable { public extension NetworkingClient { func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) -> AnyPublisher { - return get(route, urlParams: urlParams) + return get(route, params: params) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() @@ -27,10 +27,10 @@ public extension NetworkingClient { // Array version func get(_ route: String, - urlParams: Params? = nil, + params: Params? = nil, keypath: String? = nil) -> AnyPublisher<[T], Error> { let keypath = keypath ?? defaultCollectionParsingKeyPath - return get(route, urlParams: urlParams) + return get(route, params: params) .tryMap { json -> [T] in try self.toModels(json, keypath: keypath) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() diff --git a/Sources/Networking/NetworkingClient+Requests.swift b/Sources/Networking/NetworkingClient+Requests.swift index eab693b..3782554 100644 --- a/Sources/Networking/NetworkingClient+Requests.swift +++ b/Sources/Networking/NetworkingClient+Requests.swift @@ -10,8 +10,8 @@ import Combine public extension NetworkingClient { - func getRequest(_ route: String, urlParams: Params? = nil) -> NetworkingRequest { - request(.get, route, urlParams: urlParams) + func getRequest(_ route: String, queryParams: Params? = nil) -> NetworkingRequest { + request(.get, route, queryParams: queryParams) } func postRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { @@ -32,13 +32,13 @@ public extension NetworkingClient { internal func request(_ httpMethod: HTTPMethod, _ route: String, - urlParams: Params? = nil, + queryParams: Params? = nil, body: HTTPBody? = nil ) -> NetworkingRequest { let req = NetworkingRequest() req.httpMethod = httpMethod req.route = route - req.queryParams = urlParams + req.queryParams = queryParams req.httpBody = body let updateRequest = { [weak req, weak self] in diff --git a/Sources/Networking/NetworkingClient.swift b/Sources/Networking/NetworkingClient.swift index 378e718..2e20ae6 100644 --- a/Sources/Networking/NetworkingClient.swift +++ b/Sources/Networking/NetworkingClient.swift @@ -1,6 +1,11 @@ import Foundation import Combine +public enum ParameterEncoding { + case urlEncode + case json +} + public class NetworkingClient { /** Instead of using the same keypath for every call eg: "collection", @@ -15,6 +20,9 @@ public class NetworkingClient { public var sessionConfiguration = URLSessionConfiguration.default public var requestRetrier: NetworkRequestRetrier? public var jsonDecoderFactory: (() -> JSONDecoder)? + + @available(*, deprecated, message: "Parameter encoding is now done explicitly via `body` param for POST PUT & PATCH requests.") + public var parameterEncoding: ParameterEncoding = .urlEncode /** Prints network calls to the console. diff --git a/Sources/Networking/NetworkingService.swift b/Sources/Networking/NetworkingService.swift index 78855ec..85fafbe 100644 --- a/Sources/Networking/NetworkingService.swift +++ b/Sources/Networking/NetworkingService.swift @@ -6,7 +6,6 @@ // import Foundation -import Combine public protocol NetworkingService { var network: NetworkingClient { get } From f05c24b98e4183a2dba7267970be8e3c0c3e10d0 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Tue, 14 Nov 2023 16:00:41 -1000 Subject: [PATCH 08/12] Use params over queryParams for backwards compatibility's sake --- Sources/Networking/Async Api/Get.swift | 2 +- Sources/Networking/Combine Api/Get+Combine.swift | 2 +- Sources/Networking/NetworkingClient+Requests.swift | 8 ++++---- Sources/Networking/NetworkingRequest.swift | 7 +++---- Tests/NetworkingTests/GetRequestTests.swift | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Sources/Networking/Async Api/Get.swift b/Sources/Networking/Async Api/Get.swift index 9481f3a..534961c 100644 --- a/Sources/Networking/Async Api/Get.swift +++ b/Sources/Networking/Async Api/Get.swift @@ -34,7 +34,7 @@ public extension NetworkingClient { } func get(_ route: String, params: Params? = nil) async throws -> Data { - try await request(.get, route, queryParams: params).execute() + try await request(.get, route, params: params).execute() } } diff --git a/Sources/Networking/Combine Api/Get+Combine.swift b/Sources/Networking/Combine Api/Get+Combine.swift index 97263ed..991ced9 100644 --- a/Sources/Networking/Combine Api/Get+Combine.swift +++ b/Sources/Networking/Combine Api/Get+Combine.swift @@ -41,7 +41,7 @@ public extension NetworkingClient { } func get(_ route: String, params: Params? = nil) -> AnyPublisher { - request(.get, route, queryParams: params).publisher() + request(.get, route, params: params).publisher() } } diff --git a/Sources/Networking/NetworkingClient+Requests.swift b/Sources/Networking/NetworkingClient+Requests.swift index 3782554..3ff0e6c 100644 --- a/Sources/Networking/NetworkingClient+Requests.swift +++ b/Sources/Networking/NetworkingClient+Requests.swift @@ -10,8 +10,8 @@ import Combine public extension NetworkingClient { - func getRequest(_ route: String, queryParams: Params? = nil) -> NetworkingRequest { - request(.get, route, queryParams: queryParams) + func getRequest(_ route: String, params: Params? = nil) -> NetworkingRequest { + request(.get, route, params: params) } func postRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { @@ -32,13 +32,13 @@ public extension NetworkingClient { internal func request(_ httpMethod: HTTPMethod, _ route: String, - queryParams: Params? = nil, + params: Params? = nil, body: HTTPBody? = nil ) -> NetworkingRequest { let req = NetworkingRequest() req.httpMethod = httpMethod req.route = route - req.queryParams = queryParams + req.params = params ?? Params() req.httpBody = body let updateRequest = { [weak req, weak self] in diff --git a/Sources/Networking/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index dea07ed..e4b1b2e 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -17,7 +17,7 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { var route = "" var httpMethod = HTTPMethod.get var httpBody: HTTPBody? = nil - public var queryParams: Params? = nil + public var params: Params = Params() var headers = [String: String]() var logLevel: NetworkingLogLevel { get { return logger.logLevel } @@ -134,14 +134,13 @@ public class NetworkingRequest: NSObject, URLSessionTaskDelegate { private func getURLWithParams() -> String { let urlString = baseURL + route - guard let queryParams else { return urlString } - if queryParams.isEmpty { return urlString } + if params.isEmpty { return urlString } guard let url = URL(string: urlString) else { return urlString } if var urlComponents = URLComponents(url: url ,resolvingAgainstBaseURL: false) { var queryItems = urlComponents.queryItems ?? [URLQueryItem]() - queryParams.forEach { param in + params.forEach { param in // arrayParam[] syntax if let array = param.value as? [CustomStringConvertible] { array.forEach { diff --git a/Tests/NetworkingTests/GetRequestTests.swift b/Tests/NetworkingTests/GetRequestTests.swift index 1e8805e..4d22f88 100644 --- a/Tests/NetworkingTests/GetRequestTests.swift +++ b/Tests/NetworkingTests/GetRequestTests.swift @@ -63,7 +63,7 @@ final class GetRequestTests: XCTestCase { { "response": "OK" } """ - let _:Void = try await network.get("/users", urlParams: ["search" : "lion"]) + let _:Void = try await network.get("/users", params: ["search" : "lion"]) XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "GET") XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users?search=lion") } From f36d808d1a12353b9345329fe18347e640140dec Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 15 Nov 2023 11:47:21 -1000 Subject: [PATCH 09/12] Simplify .urlEncoded params --- Sources/Networking/HTTPBody.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Networking/HTTPBody.swift b/Sources/Networking/HTTPBody.swift index 268ae76..2bf4c61 100644 --- a/Sources/Networking/HTTPBody.swift +++ b/Sources/Networking/HTTPBody.swift @@ -8,7 +8,7 @@ import Foundation public enum HTTPBody { - case urlEncoded(params: Params) + case urlEncoded(_ params: Params) case json(_ encodable: Encodable) case multipart(params: Params? = nil, parts:[MultipartData]) } From e8ac7126eb209ab96e37f26bddbaf993f94c0556 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 15 Nov 2023 15:36:55 -1000 Subject: [PATCH 10/12] Update README.md --- README.md | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bc4ae15..45f9a89 100644 --- a/README.md +++ b/README.md @@ -114,21 +114,47 @@ let postsPublisher: AnyPublisher<[Post], Error> = client.get("") ``` ### Pass params -Simply pass a `[String: CustomStringConvertible]` dictionary to the `params` parameter. + +#### GET params +GET params are query params, you can put them directly in the URL like so: + +```swift +try await client.get("/posts?limit=20&offset=60") +``` +or use the `params` parameter like so: ```swift -let response: Data = try await client.posts("/posts/1", params: ["optin" : true ]) +try await client.get("/posts", params: ["limit" : 20, "offset" : 60]) ``` -Parameters are `.urlEncoded` by default (`Content-Type: application/x-www-form-urlencoded`), to encode them as json -(`Content-Type: application/json`), you need to set the client's `parameterEncoding` to `.json` as follows: +Both are equivalent. + +#### POST, PUT, PATCH body +Parameters to POST, PUT & PATCH requests are passed in the http body. +You can specify them using the `body` parameter. + +##### URLEncoded +`Content-Type: application/x-www-form-urlencoded` +For url encoded boy, use `HttpBody.urlEncoded` type for the `body` parameter with a `[String: CustomStringConvertible]` dictionary. ```swift -client.parameterEncoding = .json +try await client.post("/posts/1", body: .urlEncoded(["liked" : true ])) +``` + +##### JSON +`Content-Type: application/json` +For JSON encoded body, use `HttpBody.json` type for the `body` parameter with an `Encodable` object. + +```swift +try await client.post("/posts/1", body: .json(["liked" : true ])) + +or +try await client.post("/posts/1", body: .json(PostBody(liked: true)) +// Where `PostBody` is Encodable` ``` ### Upload multipart data -For multipart calls (post/put), just pass a `MultipartData` struct to the `multipartData` parameter. +For multipart calls (post/put), just pass a `HttpBody.multipart` type to the `body` parameter. ```swift let params: [String: CustomStringConvertible] = [ "type_resource_id": 1, "title": photo.title] let multipartData = MultipartData(name: "file", @@ -136,8 +162,9 @@ let multipartData = MultipartData(name: "file", fileName: "photo.jpg", mimeType: "image/jpeg") client.post("/photos/upload", - params: params, - multipartData: multipartData).sink(receiveCompletion: { _ in }) { (data:Data?, progress: Progress) in + body: .multipart(params: params, + parts: [multipartData])) + .sink(receiveCompletion: { _ in }) { (data:Data?, progress: Progress) in if let data = data { print("upload is complete : \(data)") } else { From e93e4aec83e73c2a27ac2ac3d1b7754fc2385831 Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 15 Nov 2023 15:39:09 -1000 Subject: [PATCH 11/12] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45f9a89..ba806e4 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ into: let network = NetworkingClient(baseURL: "https://jsonplaceholder.typicode.com") func post() async throws -> User { - try await network.post("/users", params: ["firstname" : "Alan", "lastname" : "Turing"]) + try await network.post("/users", body: .urlEncoded(["firstname" : "Alan", "lastname" : "Turing"])) } ``` From d950f85fd5022aecaa4e5393aea438286573950c Mon Sep 17 00:00:00 2001 From: Sacha DSO Date: Wed, 15 Nov 2023 15:47:20 -1000 Subject: [PATCH 12/12] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ba806e4..d963639 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ You can specify them using the `body` parameter. ##### URLEncoded `Content-Type: application/x-www-form-urlencoded` -For url encoded boy, use `HttpBody.urlEncoded` type for the `body` parameter with a `[String: CustomStringConvertible]` dictionary. +For url encoded body, use `HttpBody.urlEncoded` type for the `body` parameter with a `[String: CustomStringConvertible]` dictionary. ```swift try await client.post("/posts/1", body: .urlEncoded(["liked" : true ])) ``` @@ -298,7 +298,7 @@ struct CRUDApi: NetworkingService { // Create func create(article a: Article) async throws -> Article { - try await post("/articles", params: ["title" : a.title, "content" : a.content]) + try await post("/articles", body: .json(a)) } // Read @@ -308,7 +308,7 @@ struct CRUDApi: NetworkingService { // Update func update(article a: Article) async throws -> Article { - try await put("/articles/\(a.id)", params: ["title" : a.title, "content" : a.content]) + try await put("/articles/\(a.id)", body: .json(["title" : a.title])) } // Delete @@ -331,7 +331,7 @@ struct CRUDApi: NetworkingService { // Create func create(article a: Article) -> AnyPublisher { - post("/articles", params: ["title" : a.title, "content" : a.content]) + post("/articles", body: .json(a)) } // Read @@ -341,7 +341,7 @@ struct CRUDApi: NetworkingService { // Update func update(article a: Article) -> AnyPublisher { - put("/articles/\(a.id)", params: ["title" : a.title, "content" : a.content]) + put("/articles/\(a.id)", body: .json(["title" : a.title])) } // Delete