diff --git a/README.md b/README.md index bc4ae15..d963639 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"])) } ``` @@ -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 body, 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 { @@ -271,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 @@ -281,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 @@ -304,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 @@ -314,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 diff --git a/Sources/Networking/Async Api/Delete.swift b/Sources/Networking/Async Api/Delete.swift new file mode 100644 index 0000000..01129e5 --- /dev/null +++ b/Sources/Networking/Async Api/Delete.swift @@ -0,0 +1,62 @@ +// +// 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() + } +} + +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 new file mode 100644 index 0000000..534961c --- /dev/null +++ b/Sources/Networking/Async Api/Get.swift @@ -0,0 +1,67 @@ +// +// Get.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation + +public extension NetworkingClient { + + func get(_ route: String, params: Params? = nil) async throws { + let _:Data = try await get(route, params: params) + } + + func get(_ route: String, + params: Params? = nil, + keypath: String? = nil) async throws -> T { + let json: Any = try await get(route, params: params) + return try self.toModel(json, keypath: keypath) + } + + func get(_ route: String, + params: Params? = nil, + 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 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, params: Params? = nil) async throws -> Data { + try await request(.get, route, params: params).execute() + } +} + +public extension NetworkingService { + + func get(_ route: String, params: Params? = nil) async throws { + return try await network.get(route, params: params) + } + + func get(_ route: String, + params: Params? = nil, + keypath: String? = nil) async throws -> T { + try await network.get(route, params: params, keypath: keypath) + } + + func get(_ route: String, + params: Params? = nil, + keypath: String? = nil) async throws -> T where T: Collection { + try await network.get(route, params: params, keypath: keypath) + } + + + func get(_ route: String, params: Params? = nil) async throws -> Any { + try await network.get(route, params: params) + } + + func get(_ route: String, params: Params? = nil) async throws -> Data { + try await network.get(route, params: params) + } +} diff --git a/Sources/Networking/Async Api/Patch.swift b/Sources/Networking/Async Api/Patch.swift new file mode 100644 index 0000000..9f5147d --- /dev/null +++ b/Sources/Networking/Async Api/Patch.swift @@ -0,0 +1,69 @@ +// +// 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() + } + +} + +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 new file mode 100644 index 0000000..8efa886 --- /dev/null +++ b/Sources/Networking/Async Api/Post.swift @@ -0,0 +1,66 @@ +// +// 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() + } +} + +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 new file mode 100644 index 0000000..fde13fd --- /dev/null +++ b/Sources/Networking/Async Api/Put.swift @@ -0,0 +1,66 @@ +// +// 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() + } +} + +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/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 deleted file mode 100644 index e48ff5b..0000000 --- a/Sources/Networking/Calls/NetworkingClient+Decodable.swift +++ /dev/null @@ -1,229 +0,0 @@ -// -// NetworkingClient+Decodable.swift -// -// -// Created by Sacha DSO on 12/04/2022. -// - -import Foundation -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(), - 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) - .eraseToAnyPublisher() - } - - // Array version - func post(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return post(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher { - return put(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // Array version - func put(_ route: String, - params: Params = Params(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return put(route, params: params) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - func patch(_ route: String, - params: Params = Params(), - 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(), - keypath: String? = nil) -> AnyPublisher where T: Collection { - let keypath = keypath ?? defaultCollectionParsingKeyPath - return patch(route, params: params) - .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) - .tryMap { json -> T in try self.toModel(json, keypath: keypath) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } - - // 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) - .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 deleted file mode 100644 index 677b6db..0000000 --- a/Sources/Networking/Calls/NetworkingClient+JSON.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// NetworkingClient+JSON.swift -// -// -// Created by Sacha on 13/03/2020. -// - -import Foundation -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 { - 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 delete(_ route: String, params: Params = Params()) -> AnyPublisher { - delete(route, params: params).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: []) - } -} - -// 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 3207083..0000000 --- a/Sources/Networking/Calls/NetworkingClient+Multipart.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// NetworkingClient+Multipart.swift -// -// -// Created by Sacha on 13/03/2020. -// - -import Foundation -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 - func post(_ route: String, - params: Params = Params(), - multipartData: [MultipartData]) -> AnyPublisher<(Data?, Progress), Error> { - let req = request(.post, route, params: params) - req.multipartData = 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.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() - } -} diff --git a/Sources/Networking/Calls/NetworkingClient+Requests.swift b/Sources/Networking/Calls/NetworkingClient+Requests.swift deleted file mode 100644 index e891f5d..0000000 --- a/Sources/Networking/Calls/NetworkingClient+Requests.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// NetworkingClient+Requests.swift -// -// -// Created by Sacha on 13/03/2020. -// - -import Foundation -import Combine - -public extension NetworkingClient { - - func getRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.get, route, params: params) - } - - func postRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.post, route, params: params) - } - - func putRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.put, route, params: params) - } - - func patchRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.patch, route, params: params) - } - - func deleteRequest(_ route: String, params: Params = Params()) -> NetworkingRequest { - request(.delete, route, params: params) - } - - internal func request(_ httpMethod: HTTPMethod, - _ route: String, - params: Params = Params() - ) -> NetworkingRequest { - let req = NetworkingRequest() - req.httpMethod = httpMethod - req.route = route - req.params = params - - 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() - req.httpMethod = httpMethod - req.route = route - req.params = Params() - req.encodableBody = encodableBody - - 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 - } -} diff --git a/Sources/Networking/Calls/NetworkingClient+Void.swift b/Sources/Networking/Calls/NetworkingClient+Void.swift deleted file mode 100644 index dea4a75..0000000 --- a/Sources/Networking/Calls/NetworkingClient+Void.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// NetworkingClient+Void.swift -// -// -// Created by Sacha on 13/03/2020. -// - -import Foundation -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 { - 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) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func patch(_ route: String, body: Encodable) -> AnyPublisher { - patch(route, body: body) - .map { (data: Data) -> Void in () } - .eraseToAnyPublisher() - } - - func delete(_ route: String, params: Params = Params()) -> AnyPublisher { - delete(route, params: params) - .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() - } -} diff --git a/Sources/Networking/Combine Api/Delete+Combine.swift b/Sources/Networking/Combine Api/Delete+Combine.swift new file mode 100644 index 0000000..7e8799a --- /dev/null +++ b/Sources/Networking/Combine Api/Delete+Combine.swift @@ -0,0 +1,81 @@ +// +// 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() + } +} + +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 new file mode 100644 index 0000000..991ced9 --- /dev/null +++ b/Sources/Networking/Combine Api/Get+Combine.swift @@ -0,0 +1,89 @@ +// +// Get+Combine.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + get(route, params: params) + .map { (data: Data) -> Void in () } + .eraseToAnyPublisher() + } + + func get(_ route: String, + params: Params? = nil, + 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? = nil, + 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 get(_ route: String, params: Params? = nil) -> AnyPublisher { + get(route, params: params).toJSON() + } + + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + request(.get, route, params: params).publisher() + } +} + +public extension NetworkingService { + + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + network.get(route, params: params) + } + + func get(_ route: String, + params: Params? = nil, + keypath: String? = nil) -> AnyPublisher { + network.get(route, params: params, keypath: keypath) + } + + func get(_ route: String, + params: Params? = nil, + keypath: String? = nil) -> AnyPublisher where T: Collection { + network.get(route, params: params, keypath: keypath) + } + + func get(_ route: String, + params: Params? = nil, + keypath: String? = nil) -> AnyPublisher { + network.get(route, params: params, keypath: keypath) + } + + func get(_ route: String, + params: Params? = nil, + keypath: String? = nil) -> AnyPublisher<[T], Error> { + network.get(route, params: params, keypath: keypath) + } + + + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + network.get(route, params: params) + } + + func get(_ route: String, params: Params? = nil) -> AnyPublisher { + network.get(route, params: params) + } + + + +} diff --git a/Sources/Networking/Combine Api/Patch+Combine.swift b/Sources/Networking/Combine Api/Patch+Combine.swift new file mode 100644 index 0000000..dab8bef --- /dev/null +++ b/Sources/Networking/Combine Api/Patch+Combine.swift @@ -0,0 +1,92 @@ +// +// 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? = 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 new file mode 100644 index 0000000..b889a8b --- /dev/null +++ b/Sources/Networking/Combine Api/Post+Combine.swift @@ -0,0 +1,92 @@ +// +// 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() + } + +} + +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/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..f7f8cb7 --- /dev/null +++ b/Sources/Networking/Combine Api/Put+Combine.swift @@ -0,0 +1,91 @@ +// +// 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() + } +} + +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 new file mode 100644 index 0000000..2bf4c61 --- /dev/null +++ b/Sources/Networking/HTTPBody.swift @@ -0,0 +1,64 @@ +// +// HTTPBody.swift +// +// +// Created by Sacha Durand Saint Omer on 12/11/2023. +// + +import Foundation + +public enum HTTPBody { + case urlEncoded(_ params: Params) + case json(_ encodable: Encodable) + case multipart(params: Params? = nil, 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 .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 .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/Calls/NetworkingClient+NetworkingJSONDecodable.swift b/Sources/Networking/NetworkingClient+NetworkingJSONDecodable.swift similarity index 79% rename from Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift rename to Sources/Networking/NetworkingClient+NetworkingJSONDecodable.swift index 68944be..e80ab68 100644 --- a/Sources/Networking/Calls/NetworkingClient+NetworkingJSONDecodable.swift +++ b/Sources/Networking/NetworkingClient+NetworkingJSONDecodable.swift @@ -17,7 +17,7 @@ public protocol NetworkingJSONDecodable { public extension NetworkingClient { func get(_ route: String, - params: Params = Params(), + params: Params? = nil, keypath: String? = nil) -> AnyPublisher { return get(route, params: params) .tryMap { json -> T in try self.toModel(json, keypath: keypath) } @@ -27,7 +27,7 @@ public extension NetworkingClient { // Array version func get(_ route: String, - params: Params = Params(), + params: Params? = nil, keypath: String? = nil) -> AnyPublisher<[T], Error> { let keypath = keypath ?? defaultCollectionParsingKeyPath return get(route, params: params) @@ -37,9 +37,9 @@ public extension NetworkingClient { } 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/NetworkingClient+Requests.swift b/Sources/Networking/NetworkingClient+Requests.swift new file mode 100644 index 0000000..3ff0e6c --- /dev/null +++ b/Sources/Networking/NetworkingClient+Requests.swift @@ -0,0 +1,62 @@ +// +// NetworkingClient+Requests.swift +// +// +// Created by Sacha on 13/03/2020. +// + +import Foundation +import Combine + +public extension NetworkingClient { + + func getRequest(_ route: String, params: Params? = nil) -> NetworkingRequest { + request(.get, route, params: params) + } + + func postRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { + request(.post, route, body: body) + } + + func putRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { + request(.put, route, body: body) + } + + func patchRequest(_ route: String, body: HTTPBody? = nil) -> NetworkingRequest { + request(.patch, route, body: body) + } + + func deleteRequest(_ route: String) -> NetworkingRequest { + request(.delete, route) + } + + internal func request(_ httpMethod: HTTPMethod, + _ route: String, + params: Params? = nil, + body: HTTPBody? = nil + ) -> NetworkingRequest { + let req = NetworkingRequest() + req.httpMethod = httpMethod + req.route = route + req.params = params ?? Params() + req.httpBody = 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?.sessionConfiguration = self.sessionConfiguration + req?.timeout = self.timeout + } + updateRequest() + req.requestRetrier = { [weak self] in + self?.requestRetrier?($0, $1)? + .handleEvents(receiveOutput: { _ in + updateRequest() + }) + .eraseToAnyPublisher() + } + return req + } +} diff --git a/Sources/Networking/NetworkingClient.swift b/Sources/Networking/NetworkingClient.swift index b3c9dfb..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", @@ -11,11 +16,13 @@ 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? 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/NetworkingRequest.swift b/Sources/Networking/NetworkingRequest.swift index 8b5794d..e4b1b2e 100644 --- a/Sources/Networking/NetworkingRequest.swift +++ b/Sources/Networking/NetworkingRequest.swift @@ -8,18 +8,17 @@ import Foundation import Combine + public typealias NetworkRequestRetrier = (_ request: URLRequest, _ error: Error) -> AnyPublisher? public class NetworkingRequest: NSObject, URLSessionTaskDelegate { - var parameterEncoding = ParameterEncoding.urlEncoded var baseURL = "" var route = "" var httpMethod = HTTPMethod.get - public var params = Params() - public var encodableBody: Encodable? + var httpBody: HTTPBody? = nil + public var params: Params = Params() var headers = [String: String]() - var multipartData: [MultipartData]? var logLevel: NetworkingLogLevel { get { return logger.logLevel } set { logger.logLevel = newValue } @@ -167,66 +166,24 @@ 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 { request.setValue(value, forHTTPHeaderField: key) } - if let timeout = timeout { + if let timeout { request.timeoutInterval = timeout } - - if httpMethod != .get && multipartData == nil { - if let encodableBody { - let jsonEncoder = JSONEncoder() - do { - let data = try jsonEncoder.encode(encodableBody) - 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 - } - } - } - - // Multipart - if let multiparts = multipartData { - // Construct a unique boundary to separate values + + if let httpBody { let boundary = "Boundary-\(UUID().uuidString)" - request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") - request.httpBody = buildMultipartHttpBody(params: params, multiparts: multiparts, boundary: boundary) + 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, @@ -251,8 +208,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 096083e..85fafbe 100644 --- a/Sources/Networking/NetworkingService.swift +++ b/Sources/Networking/NetworkingService.swift @@ -6,380 +6,7 @@ // import Foundation -import Combine public protocol NetworkingService { var network: NetworkingClient { get } } - -// Sugar, just forward calls to underlying network client - -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) - } -} - -// 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) - } -} - 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/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/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 efb7243..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 @@ -320,11 +321,11 @@ 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(creds)) 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") @@ -339,7 +340,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(creds)).sink { completion in switch completion { case .failure: XCTFail() @@ -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")