Skip to content

Commit

Permalink
Merge pull request #45 from freshOS/decodable
Browse files Browse the repository at this point in the history
Makes Decodable usable without NetworkingJSONDecodable + tests
  • Loading branch information
s4cha authored Apr 12, 2022
2 parents 84870c4 + 81d60e5 commit ec6e0ee
Show file tree
Hide file tree
Showing 12 changed files with 444 additions and 44 deletions.
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

0 comments on commit ec6e0ee

Please sign in to comment.