Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Makes Decodable usable without NetworkingJSONDecodable + tests #45

Merged
merged 1 commit into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions Sources/Networking/Calls/NetworkingClient+Decodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// NetworkingClient+Decodable.swift
//
//
// Created by Sacha DSO on 12/04/2022.
//

import Foundation
import Combine

public extension NetworkingClient {

func get<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
return get(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

// Array version
func get<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
let keypath = keypath ?? defaultCollectionParsingKeyPath
return get(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

func post<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
return post(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

// Array version
func post<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
let keypath = keypath ?? defaultCollectionParsingKeyPath
return post(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

func put<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
return put(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

// Array version
func put<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
let keypath = keypath ?? defaultCollectionParsingKeyPath
return put(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

func patch<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
return patch(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

// Array version
func patch<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
let keypath = keypath ?? defaultCollectionParsingKeyPath
return patch(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

func delete<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
return delete(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

// Array version
func delete<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
let keypath = keypath ?? defaultCollectionParsingKeyPath
return delete(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,3 @@ public extension NetworkingClient {
.eraseToAnyPublisher()
}
}

// Provide default implementation for Decodable models.
public extension NetworkingJSONDecodable where Self: Decodable {

static func decode(_ json: Any) throws -> Self {
let decoder = JSONDecoder()
let data = try JSONSerialization.data(withJSONObject: json, options: [])
let model = try decoder.decode(Self.self, from: data)
return model
}
}

28 changes: 28 additions & 0 deletions Sources/Networking/NetworkingParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ public struct NetworkingParser {
throw error
}
}

public func toModel<T: Decodable>(_ json: Any, keypath: String? = nil) throws -> T {
do {
let jsonObject = resourceData(from: json, keypath: keypath)
let decoder = JSONDecoder()
let data = try JSONSerialization.data(withJSONObject: jsonObject, options: [])
let model = try decoder.decode(T.self, from: data)
return model
} catch (let error) {
throw error
}
}

public func toModels<T: NetworkingJSONDecodable>(_ json: Any, keypath: String? = nil) throws -> [T] {
do {
Expand All @@ -33,6 +45,22 @@ public struct NetworkingParser {
throw error
}
}

public func toModels<T: Decodable>(_ json: Any, keypath: String? = nil) throws -> [T] {
do {
guard let array = resourceData(from: json, keypath: keypath) as? [Any] else {
return [T]()
}
return try array.map { jsonObject in
let decoder = JSONDecoder()
let data = try JSONSerialization.data(withJSONObject: jsonObject, options: [])
let model = try decoder.decode(T.self, from: data)
return model
}.compactMap { $0 }
} catch (let error) {
throw error
}
}

private func resourceData(from json: Any, keypath: String?) -> Any {
if let keypath = keypath, !keypath.isEmpty, let dic = json as? [String: Any], let val = dic[keypath] {
Expand Down
96 changes: 95 additions & 1 deletion Sources/Networking/NetworkingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,73 @@ public extension NetworkingService {
network.delete(route, params: params)
}

// Decodable

func get<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
return get(route, params: params)
.tryMap { json -> T in try NetworkingParser().toModel(json, keypath: keypath) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

func post<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
network.post(route, params: params, keypath: keypath)
}

func put<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
network.put(route, params: params, keypath: keypath)
}

func patch<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
network.patch(route, params: params, keypath: keypath)
}

func delete<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> {
network.delete(route, params: params, keypath: keypath)
}

// Array Decodable

func get<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
network.get(route, params: params, keypath: keypath)
}

func post<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
network.post(route, params: params, keypath: keypath)
}

func put<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
network.put(route, params: params, keypath: keypath)
}

func patch<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
network.patch(route, params: params, keypath: keypath)
}

func delete<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<T, Error> where T: Collection {
network.delete(route, params: params, keypath: keypath)
}

// NetworkingJSONDecodable

func get<T: NetworkingJSONDecodable>(_ route: String,
Expand Down Expand Up @@ -117,10 +184,37 @@ public extension NetworkingService {
network.delete(route, params: params, keypath: keypath)
}

// Array version


// Array NetworkingJSONDecodable

func get<T: NetworkingJSONDecodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<[T], Error> {
network.get(route, params: params, keypath: keypath)
}

func post<T: NetworkingJSONDecodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<[T], Error> {
network.post(route, params: params, keypath: keypath)
}

func put<T: NetworkingJSONDecodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<[T], Error> {
network.put(route, params: params, keypath: keypath)
}

func patch<T: NetworkingJSONDecodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<[T], Error> {
network.patch(route, params: params, keypath: keypath)
}

func delete<T: NetworkingJSONDecodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) -> AnyPublisher<[T], Error> {
network.delete(route, params: params, keypath: keypath)
}
}
40 changes: 34 additions & 6 deletions Tests/NetworkingTests/DeleteRequestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DeletehRequestTests: XCTestCase {
MockingURLProtocol.currentRequest = nil
}

func testPOSTVoidWorks() {
func testDELETEVoidWorks() {
MockingURLProtocol.mockedResponse =
"""
{ "response": "OK" }
Expand All @@ -49,7 +49,7 @@ class DeletehRequestTests: XCTestCase {
waitForExpectations(timeout: 0.1)
}

func testPOSTDataWorks() {
func testDELETEDataWorks() {
MockingURLProtocol.mockedResponse =
"""
{ "response": "OK" }
Expand All @@ -73,7 +73,7 @@ class DeletehRequestTests: XCTestCase {
waitForExpectations(timeout: 0.1)
}

func testPOSTJSONWorks() {
func testDELETEJSONWorks() {
MockingURLProtocol.mockedResponse =
"""
{"response":"OK"}
Expand Down Expand Up @@ -103,7 +103,35 @@ class DeletehRequestTests: XCTestCase {
waitForExpectations(timeout: 0.1)
}

func testPOSTNetworkingJSONDecodableWorks() {
func testDELETENetworkingJSONDecodableWorks() {
MockingURLProtocol.mockedResponse =
"""
{
"title":"Hello",
"content":"World",
}
"""
let expectationWorks = expectation(description: "ReceiveValue called")
let expectationFinished = expectation(description: "Finished called")
network.delete("/posts/1")
.sink { completion in
switch completion {
case .failure:
XCTFail()
case .finished:
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/posts/1")
expectationFinished.fulfill()
}
} receiveValue: { (post: Post) in
XCTAssertEqual(post.title, "Hello")
XCTAssertEqual(post.content, "World")
expectationWorks.fulfill()
}
.store(in: &cancellables)
waitForExpectations(timeout: 0.1)
}
func testDELETEDecodableWorks() {
MockingURLProtocol.mockedResponse =
"""
{
Expand Down Expand Up @@ -132,7 +160,7 @@ class DeletehRequestTests: XCTestCase {
waitForExpectations(timeout: 0.1)
}

func testPOSTArrayOfNetworkingJSONDecodableWorks() {
func testDELETEArrayOfDecodableWorks() {
MockingURLProtocol.mockedResponse =
"""
[
Expand Down Expand Up @@ -169,7 +197,7 @@ class DeletehRequestTests: XCTestCase {
waitForExpectations(timeout: 0.1)
}

func testPOSTArrayOfNetworkingJSONDecodableWithKeypathWorks() {
func testDELETEArrayOfDecodableWithKeypathWorks() {
MockingURLProtocol.mockedResponse =
"""
{
Expand Down
Loading