Skip to content

Commit b2905e8

Browse files
committed
Introduce async-await api + tests
1 parent ec6e0ee commit b2905e8

10 files changed

+573
-1
lines changed

Sources/Networking/Calls/NetworkingClient+Data.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,26 @@ public extension NetworkingClient {
3030
request(.delete, route, params: params).publisher()
3131
}
3232
}
33+
34+
public extension NetworkingClient {
35+
36+
func get(_ route: String, params: Params = Params()) async throws -> Data {
37+
try await request(.get, route, params: params).execute()
38+
}
39+
40+
func post(_ route: String, params: Params = Params()) async throws -> Data {
41+
try await request(.post, route, params: params).execute()
42+
}
43+
44+
func put(_ route: String, params: Params = Params()) async throws -> Data {
45+
try await request(.put, route, params: params).execute()
46+
}
47+
48+
func patch(_ route: String, params: Params = Params()) async throws -> Data {
49+
try await request(.patch, route, params: params).execute()
50+
}
51+
52+
func delete(_ route: String, params: Params = Params()) async throws -> Data {
53+
try await request(.delete, route, params: params).execute()
54+
}
55+
}

Sources/Networking/Calls/NetworkingClient+Decodable.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,83 @@ public extension NetworkingClient {
110110
.eraseToAnyPublisher()
111111
}
112112
}
113+
114+
115+
public extension NetworkingClient {
116+
117+
func get<T: Decodable>(_ route: String,
118+
params: Params = Params(),
119+
keypath: String? = nil) async throws -> T {
120+
let json: Any = try await get(route, params: params)
121+
let model:T = try NetworkingParser().toModel(json, keypath: keypath)
122+
return model
123+
}
124+
125+
func get<T: Decodable>(_ route: String,
126+
params: Params = Params(),
127+
keypath: String? = nil) async throws -> T where T: Collection {
128+
let keypath = keypath ?? defaultCollectionParsingKeyPath
129+
let json: Any = try await get(route, params: params)
130+
return try NetworkingParser().toModel(json, keypath: keypath)
131+
}
132+
133+
func post<T: Decodable>(_ route: String,
134+
params: Params = Params(),
135+
keypath: String? = nil) async throws -> T {
136+
let json: Any = try await post(route, params: params)
137+
return try NetworkingParser().toModel(json, keypath: keypath)
138+
}
139+
140+
func post<T: Decodable>(_ route: String,
141+
params: Params = Params(),
142+
keypath: String? = nil) async throws -> T where T: Collection {
143+
let keypath = keypath ?? defaultCollectionParsingKeyPath
144+
let json: Any = try await post(route, params: params)
145+
return try NetworkingParser().toModel(json, keypath: keypath)
146+
}
147+
148+
func put<T: Decodable>(_ route: String,
149+
params: Params = Params(),
150+
keypath: String? = nil) async throws -> T {
151+
let json: Any = try await put(route, params: params)
152+
return try NetworkingParser().toModel(json, keypath: keypath)
153+
}
154+
155+
func put<T: Decodable>(_ route: String,
156+
params: Params = Params(),
157+
keypath: String? = nil) async throws -> T where T: Collection {
158+
let keypath = keypath ?? defaultCollectionParsingKeyPath
159+
let json: Any = try await put(route, params: params)
160+
return try NetworkingParser().toModel(json, keypath: keypath)
161+
}
162+
163+
func patch<T: Decodable>(_ route: String,
164+
params: Params = Params(),
165+
keypath: String? = nil) async throws -> T {
166+
let json: Any = try await patch(route, params: params)
167+
return try NetworkingParser().toModel(json, keypath: keypath)
168+
}
169+
170+
func patch<T: Decodable>(_ route: String,
171+
params: Params = Params(),
172+
keypath: String? = nil) async throws -> T where T: Collection {
173+
let keypath = keypath ?? defaultCollectionParsingKeyPath
174+
let json: Any = try await patch(route, params: params)
175+
return try NetworkingParser().toModel(json, keypath: keypath)
176+
}
177+
178+
func delete<T: Decodable>(_ route: String,
179+
params: Params = Params(),
180+
keypath: String? = nil) async throws -> T {
181+
let json: Any = try await delete(route, params: params)
182+
return try NetworkingParser().toModel(json, keypath: keypath)
183+
}
184+
185+
func delete<T: Decodable>(_ route: String,
186+
params: Params = Params(),
187+
keypath: String? = nil) async throws -> T where T: Collection {
188+
let keypath = keypath ?? defaultCollectionParsingKeyPath
189+
let json: Any = try await delete(route, params: params)
190+
return try NetworkingParser().toModel(json, keypath: keypath)
191+
}
192+
}

Sources/Networking/Calls/NetworkingClient+JSON.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,39 @@ public extension NetworkingClient {
3131
}
3232
}
3333

34+
public extension NetworkingClient {
35+
36+
func get(_ route: String, params: Params = Params()) async throws -> Any {
37+
let req = request(.get, route, params: params)
38+
let data = try await req.execute()
39+
return try JSONSerialization.jsonObject(with: data, options: [])
40+
}
41+
42+
func post(_ route: String, params: Params = Params()) async throws -> Any {
43+
let req = request(.post, route, params: params)
44+
let data = try await req.execute()
45+
return try JSONSerialization.jsonObject(with: data, options: [])
46+
}
47+
48+
func put(_ route: String, params: Params = Params()) async throws -> Any {
49+
let req = request(.put, route, params: params)
50+
let data = try await req.execute()
51+
return try JSONSerialization.jsonObject(with: data, options: [])
52+
}
53+
54+
func patch(_ route: String, params: Params = Params()) async throws -> Any {
55+
let req = request(.patch, route, params: params)
56+
let data = try await req.execute()
57+
return try JSONSerialization.jsonObject(with: data, options: [])
58+
}
59+
60+
func delete(_ route: String, params: Params = Params()) async throws -> Any {
61+
let req = request(.delete, route, params: params)
62+
let data = try await req.execute()
63+
return try JSONSerialization.jsonObject(with: data, options: [])
64+
}
65+
}
66+
3467
// Data to JSON
3568
extension Publisher where Output == Data {
3669

Sources/Networking/Calls/NetworkingClient+Void.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,31 @@ public extension NetworkingClient {
4040
.eraseToAnyPublisher()
4141
}
4242
}
43+
44+
public extension NetworkingClient {
45+
46+
func get(_ route: String, params: Params = Params()) async throws {
47+
let req = request(.get, route, params: params)
48+
_ = try await req.execute()
49+
}
50+
51+
func post(_ route: String, params: Params = Params()) async throws {
52+
let req = request(.post, route, params: params)
53+
_ = try await req.execute()
54+
}
55+
56+
func put(_ route: String, params: Params = Params()) async throws {
57+
let req = request(.put, route, params: params)
58+
_ = try await req.execute()
59+
}
60+
61+
func patch(_ route: String, params: Params = Params()) async throws {
62+
let req = request(.patch, route, params: params)
63+
_ = try await req.execute()
64+
}
65+
66+
func delete(_ route: String, params: Params = Params()) async throws {
67+
let req = request(.delete, route, params: params)
68+
_ = try await req.execute()
69+
}
70+
}

Sources/Networking/NetworkingRequest.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,36 @@ public class NetworkingRequest: NSObject {
112112
}.receive(on: DispatchQueue.main).eraseToAnyPublisher()
113113
}
114114

115+
func execute() async throws -> Data {
116+
guard let urlRequest = buildURLRequest() else {
117+
throw NetworkingError.unableToParseRequest
118+
}
119+
logger.log(request: urlRequest)
120+
let config = sessionConfiguration ?? URLSessionConfiguration.default
121+
let urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
122+
return try await withCheckedThrowingContinuation { continuation in
123+
urlSession.dataTask(with: urlRequest) { data, response, error in
124+
guard let data = data, let response = response else {
125+
if let error = error {
126+
continuation.resume(throwing: error)
127+
}
128+
return
129+
}
130+
self.logger.log(response: response, data: data)
131+
if let httpURLResponse = response as? HTTPURLResponse {
132+
if !(200...299 ~= httpURLResponse.statusCode) {
133+
var error = NetworkingError(errorCode: httpURLResponse.statusCode)
134+
if let json = try? JSONSerialization.jsonObject(with: data, options: []) {
135+
error.jsonPayload = json
136+
}
137+
continuation.resume(throwing: error)
138+
}
139+
}
140+
continuation.resume(returning: data)
141+
}.resume()
142+
}
143+
}
144+
115145
private func getURLWithParams() -> String {
116146
let urlString = baseURL + route
117147
if params.isEmpty { return urlString }

Tests/NetworkingTests/DeleteRequestTests.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ class DeletehRequestTests: XCTestCase {
4949
waitForExpectations(timeout: 0.1)
5050
}
5151

52+
func testDELETEVoidAsyncWorks() async throws {
53+
MockingURLProtocol.mockedResponse =
54+
"""
55+
{ "response": "OK" }
56+
"""
57+
let _: Void = try await network.delete("/users")
58+
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
59+
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users")
60+
}
61+
5262
func testDELETEDataWorks() {
5363
MockingURLProtocol.mockedResponse =
5464
"""
@@ -73,6 +83,17 @@ class DeletehRequestTests: XCTestCase {
7383
waitForExpectations(timeout: 0.1)
7484
}
7585

86+
func testDELETEDataAsyncWorks() async throws {
87+
MockingURLProtocol.mockedResponse =
88+
"""
89+
{ "response": "OK" }
90+
"""
91+
let data: Data = try await network.delete("/users")
92+
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
93+
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users")
94+
XCTAssertEqual(data, MockingURLProtocol.mockedResponse.data(using: String.Encoding.utf8))
95+
}
96+
7697
func testDELETEJSONWorks() {
7798
MockingURLProtocol.mockedResponse =
7899
"""
@@ -103,6 +124,22 @@ class DeletehRequestTests: XCTestCase {
103124
waitForExpectations(timeout: 0.1)
104125
}
105126

127+
func testDELETEJSONAsyncWorks() async throws {
128+
MockingURLProtocol.mockedResponse =
129+
"""
130+
{"response":"OK"}
131+
"""
132+
let json: Any = try await network.delete("/users")
133+
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
134+
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users")
135+
let data = try? JSONSerialization.data(withJSONObject: json, options: [])
136+
let expectedResponseData =
137+
"""
138+
{"response":"OK"}
139+
""".data(using: String.Encoding.utf8)
140+
XCTAssertEqual(data, expectedResponseData)
141+
}
142+
106143
func testDELETENetworkingJSONDecodableWorks() {
107144
MockingURLProtocol.mockedResponse =
108145
"""
@@ -131,6 +168,7 @@ class DeletehRequestTests: XCTestCase {
131168
.store(in: &cancellables)
132169
waitForExpectations(timeout: 0.1)
133170
}
171+
134172
func testDELETEDecodableWorks() {
135173
MockingURLProtocol.mockedResponse =
136174
"""
@@ -159,6 +197,21 @@ class DeletehRequestTests: XCTestCase {
159197
.store(in: &cancellables)
160198
waitForExpectations(timeout: 0.1)
161199
}
200+
201+
func testDELETEDecodableAsyncWorks() async throws {
202+
MockingURLProtocol.mockedResponse =
203+
"""
204+
{
205+
"firstname":"John",
206+
"lastname":"Doe",
207+
}
208+
"""
209+
let userJSON: UserJSON = try await network.delete("/users/1")
210+
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
211+
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users/1")
212+
XCTAssertEqual(userJSON.firstname, "John")
213+
XCTAssertEqual(userJSON.lastname, "Doe")
214+
}
162215

163216
func testDELETEArrayOfDecodableWorks() {
164217
MockingURLProtocol.mockedResponse =
@@ -196,6 +249,29 @@ class DeletehRequestTests: XCTestCase {
196249
.store(in: &cancellables)
197250
waitForExpectations(timeout: 0.1)
198251
}
252+
253+
func testDELETEArrayOfDecodableAsyncWorks() async throws {
254+
MockingURLProtocol.mockedResponse =
255+
"""
256+
[
257+
{
258+
"firstname":"John",
259+
"lastname":"Doe"
260+
},
261+
{
262+
"firstname":"Jimmy",
263+
"lastname":"Punchline"
264+
}
265+
]
266+
"""
267+
let users: [UserJSON] = try await network.delete("/users")
268+
XCTAssertEqual(MockingURLProtocol.currentRequest?.httpMethod, "DELETE")
269+
XCTAssertEqual(MockingURLProtocol.currentRequest?.url?.absoluteString, "https://mocked.com/users")
270+
XCTAssertEqual(users[0].firstname, "John")
271+
XCTAssertEqual(users[0].lastname, "Doe")
272+
XCTAssertEqual(users[1].firstname, "Jimmy")
273+
XCTAssertEqual(users[1].lastname, "Punchline")
274+
}
199275

200276
func testDELETEArrayOfDecodableWithKeypathWorks() {
201277
MockingURLProtocol.mockedResponse =

0 commit comments

Comments
 (0)