Skip to content

Commit 758ad9e

Browse files
authored
Adds Get, Post, Encoding and Decoding helpers
Adds helper methods to Engine
2 parents b2a8374 + 7f45517 commit 758ad9e

File tree

2 files changed

+236
-0
lines changed

2 files changed

+236
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import Foundation
2+
import Combine
3+
4+
public extension HTTPEngine {
5+
6+
/// Makes a request via HTTP and Decodes the response
7+
/// - Parameters:
8+
/// - decodableResponse: Decodable - An object that represents the response body
9+
/// - method: HTTPMethod - `.get, .put. post` etc.,
10+
/// - urlString: URL domain + path as a string: `"abc.com/some/path"`
11+
/// - header: A dictionary of HTTP Request Headers - `["Content-Type": "text", "Some Key": "Some Value"]`
12+
/// - validator: `(Int) -> Bool` - A function to validate the response code of the request. By default, makeRequest() will fail if the status code does not fall within the 200 - 299 range. To override this, pass in a function that compares the status code and returns a boolean. True == success, False == failure. Upon failure an error will be thrown that contains the HTTPURLResponse for inspection.
13+
///
14+
/// - Returns: AnyPubliser<Data, Error>
15+
///
16+
/// -- Headers
17+
///
18+
/// By default all requests have the `["Accept-Encoding": "gzip;q=1.0,compress;q=0.5"]` header included.
19+
///
20+
/// All `.post, .put, & .patch` requests also contain `["Content-Type": "application/json"]` by default.
21+
///
22+
/// These values can be overridden by including those headers as arguments when calling this function
23+
///
24+
/// -- Validation
25+
///
26+
/// By default the validation checks for a 200-299 status code and fails if the code is out of bounds
27+
/// ```swift
28+
/// // example validator
29+
/// validator: { $0 == 202 }
30+
/// ```
31+
public func makeRequestAndParseResponse<Response: Decodable>(
32+
_ decodableResponse: Response.Type,
33+
method: HTTPMethod,
34+
url: String,
35+
header: Header? = nil,
36+
validator: ResponseValidationClosure? = nil
37+
) -> AnyPublisher<Response, Error> {
38+
makeRequestAndParseResponse(decodableResponse, method: method, url: url, body: nil as Data?, header: header, validator: validator)
39+
}
40+
41+
42+
/// Makes a request via HTTP, Encodes the body and Decodes the response
43+
/// - Parameters:
44+
/// - decodableResponse: Decodable - An object that represents the response body
45+
/// - method: HTTPMethod - `.get, .put. post` etc.,
46+
/// - urlString: URL domain + path as a string: `"abc.com/some/path"`
47+
/// - body: Encodable?: The encodable object that represents body data to send with a request
48+
/// - header: A dictionary of HTTP Request Headers - `["Content-Type": "text", "Some Key": "Some Value"]`
49+
/// - validator: `(Int) -> Bool` - A function to validate the response code of the request. By default, makeRequest() will fail if the status code does not fall within the 200 - 299 range. To override this, pass in a function that compares the status code and returns a boolean. True == success, False == failure. Upon failure an error will be thrown that contains the HTTPURLResponse for inspection.
50+
///
51+
/// - Returns: AnyPubliser<Data, Error>
52+
///
53+
/// -- Headers
54+
///
55+
/// By default all requests have the `["Accept-Encoding": "gzip;q=1.0,compress;q=0.5"]` header included.
56+
///
57+
/// All `.post, .put, & .patch` requests also contain `["Content-Type": "application/json"]` by default.
58+
///
59+
/// These values can be overridden by including those headers as arguments when calling this function
60+
///
61+
/// -- Validation
62+
///
63+
/// By default the validation checks for a 200-299 status code and fails if the code is out of bounds
64+
/// ```swift
65+
/// // example validator
66+
/// validator: { $0 == 202 }
67+
/// ```
68+
public func makeRequestAndParseResponse<Body: Encodable, Response: Decodable>(
69+
_ decodableResponse: Response.Type,
70+
method: HTTPMethod,
71+
url: String,
72+
body: Body?,
73+
header: Header? = nil,
74+
validator: ResponseValidationClosure? = nil
75+
) -> AnyPublisher<Response, Error> {
76+
Just(body)
77+
.tryMap { $0 != nil ? try JSONEncoder().encode($0) : nil }
78+
.flatMap { self.makeRequest(method: method, url: url, body: $0, header: header, validator: validator) }
79+
.decode(type: decodableResponse.self, decoder: JSONDecoder())
80+
.eraseToAnyPublisher()
81+
}
82+
83+
84+
/// Makes a request via HTTP and Decodes the response
85+
/// - Parameters:
86+
/// - decodableResponse: Decodable - An object that represents the response body
87+
/// - urlString: URL domain + path as a string: `"abc.com/some/path"`
88+
/// - validator: `(Int) -> Bool` - A function to validate the response code of the request. By default, makeRequest() will fail if the status code does not fall within the 200 - 299 range. To override this, pass in a function that compares the status code and returns a boolean. True == success, False == failure. Upon failure an error will be thrown that contains the HTTPURLResponse for inspection.
89+
///
90+
/// - Returns: AnyPubliser<Data, Error>
91+
///
92+
/// -- Validation
93+
///
94+
/// By default the validation checks for a 200-299 status code and fails if the code is out of bounds
95+
/// ```swift
96+
/// // example validator
97+
/// validator: { $0 == 202 }
98+
/// ```
99+
func get<Response: Decodable>(
100+
_ value: Response.Type,
101+
url: String,
102+
validator: ResponseValidationClosure? = nil
103+
) -> AnyPublisher<Response, Error> {
104+
makeRequestAndParseResponse(value.self, method: .get, url: url, validator: validator)
105+
}
106+
107+
108+
/// Makes a request via HTTP, Encodes the body and Decodes the response
109+
/// - Parameters:
110+
/// - decodableResponse: Decodable - An object that represents the response body
111+
/// - urlString: URL domain + path as a string: `"abc.com/some/path"`
112+
/// - body: Encodable?: The encodable object that represents body data to send with a request
113+
/// - validator: `(Int) -> Bool` - A function to validate the response code of the request. By default, makeRequest() will fail if the status code does not fall within the 200 - 299 range. To override this, pass in a function that compares the status code and returns a boolean. True == success, False == failure. Upon failure an error will be thrown that contains the HTTPURLResponse for inspection.
114+
///
115+
/// - Returns: AnyPubliser<Data, Error>
116+
///
117+
/// -- Validation
118+
///
119+
/// By default the validation checks for a 200-299 status code and fails if the code is out of bounds
120+
/// ```swift
121+
/// // example validator
122+
/// validator: { $0 == 202 }
123+
/// ```
124+
func post<Response: Decodable, Body: Encodable>(
125+
_ value: Response.Type,
126+
url: String,
127+
body: Body? = nil,
128+
validator: ResponseValidationClosure? = nil
129+
) -> AnyPublisher<Response, Error> {
130+
makeRequestAndParseResponse(value.self, method: .post, url: url, body: body, validator: validator)
131+
}
132+
133+
134+
/// Makes a request via HTTP, Encodes the body and Decodes the response
135+
/// - Parameters:
136+
/// - decodableResponse: Decodable - An object that represents the response body
137+
/// - urlString: URL domain + path as a string: `"abc.com/some/path"`
138+
/// - validator: `(Int) -> Bool` - A function to validate the response code of the request. By default, makeRequest() will fail if the status code does not fall within the 200 - 299 range. To override this, pass in a function that compares the status code and returns a boolean. True == success, False == failure. Upon failure an error will be thrown that contains the HTTPURLResponse for inspection.
139+
///
140+
/// - Returns: AnyPubliser<Data, Error>
141+
///
142+
/// -- Validation
143+
///
144+
/// By default the validation checks for a 200-299 status code and fails if the code is out of bounds
145+
/// ```swift
146+
/// // example validator
147+
/// validator: { $0 == 202 }
148+
/// ```
149+
func post<Response: Decodable>(
150+
_ value: Response.Type,
151+
url: String,
152+
validator: ResponseValidationClosure? = nil
153+
) -> AnyPublisher<Response, Error> {
154+
post(value.self, url: url, body: nil as NilBody?, validator: validator)
155+
}
156+
}
157+
158+
private struct NilBody: Encodable {}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import XCTest
2+
import Foundation
3+
import OHHTTPStubs
4+
import OHHTTPStubsSwift
5+
@testable import HTTPEngine
6+
7+
final class HTTPEngineConvenienceMethodTests: XCTestCase {
8+
static var allTests = [
9+
("make request and parse response decodes into type", testMakeRequestAndParseResponseDecodesIntoType),
10+
("make request and parse response throws if Decode fails", testMakeRequestAndParseResponseThrowsIfDecodeFails),
11+
("make request and parse response Encodes and Decodes into type", testMakeRequestAndParseResponseEncodesAndDecodesIntoType),
12+
("get succeeds", testGetSucceeds),
13+
("post succeeds", testPostSucceeds),
14+
]
15+
16+
func testMakeRequestAndParseResponseDecodesIntoType() {
17+
stub(condition: isHost("google.com") && isMethodGET()) { _ in
18+
HTTPStubsResponse(jsonObject: ["key":"value"], statusCode: 200, headers: nil)
19+
}
20+
21+
HTTPEngine()
22+
.makeRequestAndParseResponse(TestResponseBody.self, method: .get, url: "https://google.com")
23+
.assertResult(test: self) {
24+
XCTAssertEqual($0.key, "value")
25+
}
26+
27+
}
28+
29+
func testMakeRequestAndParseResponseThrowsIfDecodeFails() {
30+
stub(condition: isHost("google.com") && isMethodGET()) { _ in
31+
HTTPStubsResponse(jsonObject: [:], statusCode: 200, headers: nil)
32+
}
33+
34+
HTTPEngine()
35+
.makeRequestAndParseResponse(TestResponseBody.self, method: .get, url: "https://google.com")
36+
.assertError(test: self) {
37+
XCTAssertNotNil($0)
38+
}
39+
40+
}
41+
42+
func testMakeRequestAndParseResponseEncodesAndDecodesIntoType() {
43+
HTTPEngine()
44+
.makeRequestAndParseResponse(TestResponseBody.self, method: .get, url: "https://google.com", body: TestResponseBody(key: "something"))
45+
.assertResult(test: self) {
46+
XCTAssertEqual($0.key, "value")
47+
}
48+
}
49+
50+
func testGetSucceeds() {
51+
stub(condition: isHost("google.com") && isMethodGET()) { _ in
52+
HTTPStubsResponse(jsonObject: ["key": "value"], statusCode: 200, headers: nil)
53+
}
54+
55+
HTTPEngine()
56+
.get(TestResponseBody.self, url: "https://google.com")
57+
.assertResult(test: self) {
58+
XCTAssertEqual($0.key, "value")
59+
60+
}
61+
}
62+
63+
func testPostSucceeds() {
64+
stub(condition: isHost("google.com") && isMethodPOST()) { _ in
65+
HTTPStubsResponse(jsonObject: ["key": "value"], statusCode: 200, headers: nil)
66+
}
67+
68+
HTTPEngine()
69+
.post(TestResponseBody.self, url: "https://google.com")
70+
.assertResult(test: self) {
71+
XCTAssertEqual($0.key, "value")
72+
}
73+
}
74+
}
75+
76+
struct TestResponseBody: Codable {
77+
let key: String
78+
}

0 commit comments

Comments
 (0)