Skip to content

Commit

Permalink
Merge pull request #46 from freshOS/async
Browse files Browse the repository at this point in the history
Introduce async-await api + tests
  • Loading branch information
s4cha authored Apr 12, 2022
2 parents ec6e0ee + b2905e8 commit 949a1af
Show file tree
Hide file tree
Showing 10 changed files with 573 additions and 1 deletion.
23 changes: 23 additions & 0 deletions Sources/Networking/Calls/NetworkingClient+Data.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,26 @@ public extension NetworkingClient {
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 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()
}
}
80 changes: 80 additions & 0 deletions Sources/Networking/Calls/NetworkingClient+Decodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,83 @@ public extension NetworkingClient {
.eraseToAnyPublisher()
}
}


public extension NetworkingClient {

func get<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) async throws -> T {
let json: Any = try await get(route, params: params)
let model:T = try NetworkingParser().toModel(json, keypath: keypath)
return model
}

func get<T: Decodable>(_ 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 NetworkingParser().toModel(json, keypath: keypath)
}

func post<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) async throws -> T {
let json: Any = try await post(route, params: params)
return try NetworkingParser().toModel(json, keypath: keypath)
}

func post<T: Decodable>(_ 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 NetworkingParser().toModel(json, keypath: keypath)
}

func put<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) async throws -> T {
let json: Any = try await put(route, params: params)
return try NetworkingParser().toModel(json, keypath: keypath)
}

func put<T: Decodable>(_ 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 NetworkingParser().toModel(json, keypath: keypath)
}

func patch<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) async throws -> T {
let json: Any = try await patch(route, params: params)
return try NetworkingParser().toModel(json, keypath: keypath)
}

func patch<T: Decodable>(_ 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 NetworkingParser().toModel(json, keypath: keypath)
}

func delete<T: Decodable>(_ route: String,
params: Params = Params(),
keypath: String? = nil) async throws -> T {
let json: Any = try await delete(route, params: params)
return try NetworkingParser().toModel(json, keypath: keypath)
}

func delete<T: Decodable>(_ 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 NetworkingParser().toModel(json, keypath: keypath)
}
}
33 changes: 33 additions & 0 deletions Sources/Networking/Calls/NetworkingClient+JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,39 @@ public extension NetworkingClient {
}
}

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 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 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 {

Expand Down
28 changes: 28 additions & 0 deletions Sources/Networking/Calls/NetworkingClient+Void.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,31 @@ public extension NetworkingClient {
.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 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()
}
}
30 changes: 30 additions & 0 deletions Sources/Networking/NetworkingRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,36 @@ public class NetworkingRequest: NSObject {
}.receive(on: DispatchQueue.main).eraseToAnyPublisher()
}

func execute() async throws -> Data {
guard let urlRequest = buildURLRequest() else {
throw NetworkingError.unableToParseRequest
}
logger.log(request: urlRequest)
let config = sessionConfiguration ?? URLSessionConfiguration.default
let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
return try await withCheckedThrowingContinuation { continuation in
urlSession.dataTask(with: urlRequest) { data, response, error in
guard let data = data, let response = response else {
if let error = error {
continuation.resume(throwing: error)
}
return
}
self.logger.log(response: response, data: data)
if let httpURLResponse = response as? HTTPURLResponse {
if !(200...299 ~= httpURLResponse.statusCode) {
var error = NetworkingError(errorCode: httpURLResponse.statusCode)
if let json = try? JSONSerialization.jsonObject(with: data, options: []) {
error.jsonPayload = json
}
continuation.resume(throwing: error)
}
}
continuation.resume(returning: data)
}.resume()
}
}

private func getURLWithParams() -> String {
let urlString = baseURL + route
if params.isEmpty { return urlString }
Expand Down
76 changes: 76 additions & 0 deletions Tests/NetworkingTests/DeleteRequestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ class DeletehRequestTests: XCTestCase {
waitForExpectations(timeout: 0.1)
}

func testDELETEVoidAsyncWorks() async throws {
MockingURLProtocol.mockedResponse =
"""
{ "response": "OK" }
"""
let _: Void = try await network.delete("/users")
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users")
}

func testDELETEDataWorks() {
MockingURLProtocol.mockedResponse =
"""
Expand All @@ -73,6 +83,17 @@ class DeletehRequestTests: XCTestCase {
waitForExpectations(timeout: 0.1)
}

func testDELETEDataAsyncWorks() async throws {
MockingURLProtocol.mockedResponse =
"""
{ "response": "OK" }
"""
let data: Data = try await network.delete("/users")
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users")
XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8))
}

func testDELETEJSONWorks() {
MockingURLProtocol.mockedResponse =
"""
Expand Down Expand Up @@ -103,6 +124,22 @@ class DeletehRequestTests: XCTestCase {
waitForExpectations(timeout: 0.1)
}

func testDELETEJSONAsyncWorks() async throws {
MockingURLProtocol.mockedResponse =
"""
{"response":"OK"}
"""
let json: Any = try await network.delete("/users")
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users")
let data = try? JSONSerialization.data(withJSONObject: json, options: [])
let expectedResponseData =
"""
{"response":"OK"}
""".data(using: String.Encoding.utf8)
XCTAssertEqual(data, expectedResponseData)
}

func testDELETENetworkingJSONDecodableWorks() {
MockingURLProtocol.mockedResponse =
"""
Expand Down Expand Up @@ -131,6 +168,7 @@ class DeletehRequestTests: XCTestCase {
.store(in: &cancellables)
waitForExpectations(timeout: 0.1)
}

func testDELETEDecodableWorks() {
MockingURLProtocol.mockedResponse =
"""
Expand Down Expand Up @@ -159,6 +197,21 @@ class DeletehRequestTests: XCTestCase {
.store(in: &cancellables)
waitForExpectations(timeout: 0.1)
}

func testDELETEDecodableAsyncWorks() async throws {
MockingURLProtocol.mockedResponse =
"""
{
"firstname":"John",
"lastname":"Doe",
}
"""
let userJSON: UserJSON = try await network.delete("/users/1")
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1")
XCTAssertEqual(userJSON.firstname, "John")
XCTAssertEqual(userJSON.lastname, "Doe")
}

func testDELETEArrayOfDecodableWorks() {
MockingURLProtocol.mockedResponse =
Expand Down Expand Up @@ -196,6 +249,29 @@ class DeletehRequestTests: XCTestCase {
.store(in: &cancellables)
waitForExpectations(timeout: 0.1)
}

func testDELETEArrayOfDecodableAsyncWorks() async throws {
MockingURLProtocol.mockedResponse =
"""
[
{
"firstname":"John",
"lastname":"Doe"
},
{
"firstname":"Jimmy",
"lastname":"Punchline"
}
]
"""
let users: [UserJSON] = try await network.delete("/users")
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users")
XCTAssertEqual(users[0].firstname, "John")
XCTAssertEqual(users[0].lastname, "Doe")
XCTAssertEqual(users[1].firstname, "Jimmy")
XCTAssertEqual(users[1].lastname, "Punchline")
}

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

0 comments on commit 949a1af

Please sign in to comment.