Skip to content

Commit fb919ea

Browse files
authored
feat(request): Add APIs to Request (#14)
1 parent d5cbca6 commit fb919ea

File tree

5 files changed

+118
-60
lines changed

5 files changed

+118
-60
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ struct UserBody: Encodable {}
6767

6868
extension Request {
6969
static func login(_ body: UserBody) -> Self where Output == LoginResponse {
70-
.post("login", body: .encodable(body))
70+
.post("login", body: body)
7171
}
7272
}
7373
```
@@ -86,13 +86,13 @@ extension Request {
8686
static func send(audio: URL) throws -> Self where Output == SendAudioResponse {
8787
var multipart = MultipartFormData()
8888
try multipart.add(url: audio, name: "define_your_name")
89-
return .post("sendAudio", body: .multipart(multipart))
89+
return .post("sendAudio", body: multipart)
9090
}
9191

9292
static func send(audio: Data) throws -> Self where Output == SendAudioResponse {
9393
var multipart = MultipartFormData()
9494
try multipart.add(data: data, name: "your_name", fileName: "your_fileName", mimeType: "right_mimeType")
95-
return .post("sendAudio", body: .multipart(multipart))
95+
return .post("sendAudio", body: multipart)
9696
}
9797
}
9898
```
@@ -107,7 +107,7 @@ extension Request {
107107
var multipart = MultipartFormData()
108108
try multipart.add(url: audio, name: "define_your_name")
109109
try multipart.add(data: image, name: "your_name", fileName: "your_fileName", mimeType: "right_mimeType")
110-
return .post("sendAudioImage", body: .multipart(multipart))
110+
return .post("sendAudioImage", body: multipart)
111111
}
112112
}
113113
```

Sources/SimpleHTTP/Request/Endpoint.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import Foundation
2121
///
2222
/// let user: Endpoint = .myEndpoints.user
2323
/// ```
24-
public struct Endpoint: Equatable, ExpressibleByStringLiteral {
24+
public struct Endpoint: Equatable, ExpressibleByStringLiteral, ExpressibleByStringInterpolation {
2525
/// relative path
26-
let path: String
26+
public let path: String
2727

2828
init(path: String) {
2929
self.path = path
@@ -32,7 +32,11 @@ public struct Endpoint: Equatable, ExpressibleByStringLiteral {
3232
public init(stringLiteral value: StringLiteralType) {
3333
self.init(path: value)
3434
}
35-
35+
36+
public init(stringInterpolation: DefaultStringInterpolation) {
37+
self.init(path: stringInterpolation.description)
38+
}
39+
3640
public static func ==(lhs: Endpoint, rhs: String) -> Bool {
3741
lhs.path == rhs
3842
}

Sources/SimpleHTTP/Request/Request+URLRequest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extension Request {
2424
var urlRequest = try URLRequest(url: URL(from: self))
2525

2626
urlRequest.httpMethod = method.rawValue.uppercased()
27+
urlRequest.cachePolicy = cachePolicy
2728
urlRequest.setHeaders(headers)
2829

2930
if let body = body {

Sources/SimpleHTTP/Request/Request.swift

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public enum Method: String {
44
case get
55
case post
66
case put
7+
case patch
78
case delete
89
}
910

@@ -12,49 +13,92 @@ public enum Body {
1213
case multipart(MultipartFormData)
1314
}
1415

15-
/// A Http request expecting an `Output` response
16+
/// A HTTP request safely typed for an `Output` response
1617
///
1718
/// Highly inspired by https://swiftwithmajid.com/2021/02/10/building-type-safe-networking-in-swift/
1819
public struct Request<Output> {
19-
2020
/// request relative endpoint
2121
public let endpoint: Endpoint
2222
public let method: Method
2323
public let body: Body?
2424
public let query: [String: QueryParam]
25+
public private(set) var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
2526
public private(set) var headers: HTTPHeaderFields = [:]
2627

28+
/// Creates a request suitable for a HTTP GET
2729
public static func get(_ endpoint: Endpoint, query: [String: QueryParam] = [:]) -> Self {
2830
self.init(endpoint: endpoint, method: .get, query: query, body: nil)
2931
}
3032

31-
public static func post(_ endpoint: Endpoint, body: Body?, query: [String: QueryParam] = [:])
33+
/// Creates a request suitable for a HTTP POST with a `Encodable` body
34+
public static func post(_ endpoint: Endpoint, body: Encodable?, query: [String: QueryParam] = [:])
35+
-> Self {
36+
self.init(endpoint: endpoint, method: .post, query: query, body: body.map(Body.encodable))
37+
}
38+
39+
/// Creates a request suitable for a HTTP POST with a `MultipartFormData` body
40+
@_disfavoredOverload
41+
public static func post(_ endpoint: Endpoint, body: MultipartFormData?, query: [String: QueryParam] = [:])
42+
-> Self {
43+
self.init(endpoint: endpoint, method: .post, query: query, body: body.map(Body.multipart))
44+
}
45+
46+
/// Creates a request suitable for a HTTP PUT with a `Encodable` body
47+
public static func put(_ endpoint: Endpoint, body: Encodable, query: [String: QueryParam] = [:])
48+
-> Self {
49+
self.init(endpoint: endpoint, method: .put, query: query, body: .encodable(body))
50+
}
51+
52+
/// Creates a request suitable for a HTTP PUT with a `MultipartFormData` body
53+
public static func put(_ endpoint: Endpoint, body: MultipartFormData, query: [String: QueryParam] = [:])
3254
-> Self {
33-
self.init(endpoint: endpoint, method: .post, query: query, body: body)
55+
self.init(endpoint: endpoint, method: .put, query: query, body: .multipart(body))
3456
}
3557

36-
public static func put(_ endpoint: Endpoint, body: Body, query: [String: QueryParam] = [:])
58+
/// Creates a request suitable for a HTTP PATCH with a `Encodable` body
59+
public static func patch(_ endpoint: Endpoint, body: Encodable, query: [String: QueryParam] = [:])
3760
-> Self {
38-
self.init(endpoint: endpoint, method: .put, query: query, body: body)
61+
self.init(endpoint: endpoint, method: .patch, query: query, body: .encodable(body))
3962
}
4063

64+
/// Creates a request suitable for a HTTP PATCH with a `MultipartFormData` body
65+
public static func patch(_ endpoint: Endpoint, body: MultipartFormData, query: [String: QueryParam] = [:])
66+
-> Self {
67+
self.init(endpoint: endpoint, method: .patch, query: query, body: .multipart(body))
68+
}
69+
70+
/// Creates a request suitable for a HTTP DELETE
71+
/// Default implementation does not allow for sending a body. If you need such a case extend Request with your
72+
/// own init method
4173
public static func delete(_ endpoint: Endpoint, query: [String: QueryParam] = [:]) -> Self {
4274
self.init(endpoint: endpoint, method: .delete, query: query, body: nil)
4375
}
4476

45-
private init(endpoint: Endpoint, method: Method, query: [String: QueryParam], body: Body?) {
77+
/// Creates a Request.
78+
///
79+
/// Use this init only if default provided static initializers (`.get`, `.post`, `.put`, `patch`, `.delete`) do not suit your needs.
80+
public init(endpoint: Endpoint, method: Method, query: [String: QueryParam], body: Body?) {
4681
self.endpoint = endpoint
4782
self.method = method
4883
self.body = body
4984
self.query = query
5085
}
5186

52-
/// add headers to the request
87+
/// Adds headers to the request
5388
public func headers(_ newHeaders: [HTTPHeader: String]) -> Self {
5489
var request = self
5590

5691
request.headers.merge(newHeaders) { $1 }
5792

5893
return request
5994
}
95+
96+
/// Configures request cache policy
97+
public func cachePolicy(_ policy: URLRequest.CachePolicy) -> Self {
98+
var request = self
99+
100+
request.cachePolicy = policy
101+
102+
return request
103+
}
60104
}

Tests/SimpleHTTPTests/Request/RequestTests.swift

Lines changed: 54 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,62 +18,71 @@ class RequestTests: XCTestCase {
1818

1919
XCTAssertEqual(request.httpMethod, "POST")
2020
}
21-
22-
func test_toURLRequest_EncodeBody() throws {
23-
let request = try Request<Void>.post(.test, body: .encodable(Body()))
21+
22+
func test_toURLRequest_encodeBody() throws {
23+
let request = try Request<Void>.post(.test, body: BodyMock())
2424
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
25-
26-
XCTAssertEqual(request.httpBody, try JSONEncoder().encode(Body()))
25+
26+
XCTAssertEqual(request.httpBody, try JSONEncoder().encode(BodyMock()))
2727
}
28-
28+
29+
func test_toURLRequest_setCachePolicy() throws {
30+
let request = try Request<Void>
31+
.get(.test)
32+
.cachePolicy(.returnCacheDataDontLoad)
33+
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
34+
35+
XCTAssertEqual(request.cachePolicy, .returnCacheDataDontLoad)
36+
}
37+
2938
func test_toURLRequest_encodeMultipartBody() throws {
30-
let crlf = EncodingCharacters.crlf
31-
let boundary = "boundary"
32-
var multipart = MultipartFormData(boundary: boundary)
33-
let url = try url(forResource: "swift", withExtension: "png")
34-
let name = "swift"
35-
try multipart.add(url: url, name: name)
36-
37-
let request = try Request<Void>.post(.test, body: .multipart(multipart))
39+
let crlf = EncodingCharacters.crlf
40+
let boundary = "boundary"
41+
var multipart = MultipartFormData(boundary: boundary)
42+
let url = try url(forResource: "swift", withExtension: "png")
43+
let name = "swift"
44+
try multipart.add(url: url, name: name)
45+
46+
let request = try Request<Void>.post(.test, body: multipart)
3847
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
39-
40-
/// We can't use `XCTAssertEqual(request.httpBody, try multipart.encode)`
41-
/// The `encode` method is executed to fast and rase and error
42-
var body = Data()
43-
body.append(Boundary.data(for: .initial, boundary: boundary))
44-
body.append(
45-
Data((
46-
"Content-Disposition: form-data; name=\"\(name)\"; filename=\"swift.png\"\(crlf)"
47-
+ "Content-Type: image/png\(crlf)\(crlf)"
48-
).utf8)
49-
)
50-
body.append(try Data(contentsOf: url))
51-
body.append(Boundary.data(for: .final, boundary: boundary))
52-
XCTAssertEqual(request.httpBody, body)
48+
49+
/// We can't use `XCTAssertEqual(request.httpBody, try multipart.encode)`
50+
/// The `encode` method is executed to fast and rase and error
51+
var body = Data()
52+
body.append(Boundary.data(for: .initial, boundary: boundary))
53+
body.append(
54+
Data((
55+
"Content-Disposition: form-data; name=\"\(name)\"; filename=\"swift.png\"\(crlf)"
56+
+ "Content-Type: image/png\(crlf)\(crlf)"
57+
).utf8)
58+
)
59+
body.append(try Data(contentsOf: url))
60+
body.append(Boundary.data(for: .final, boundary: boundary))
61+
XCTAssertEqual(request.httpBody, body)
5362
}
54-
55-
func test_toURLRequest_bodyIsEncodable_FillDefaultHeaders() throws {
56-
let request = try Request<Void>.post(.test, body: .encodable(Body()))
63+
64+
func test_toURLRequest_bodyIsEncodable_fillContentTypeHeader() throws {
65+
let request = try Request<Void>.post(.test, body: BodyMock())
5766
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
58-
59-
XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], "application/json")
67+
68+
XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], "application/json")
6069
}
61-
62-
func test_toURLRequest_bodyIsMultipart_itFillDefaultHeaders() throws {
63-
let boundary = "boundary"
64-
var multipart = MultipartFormData(boundary: boundary)
65-
let url = try url(forResource: "swift", withExtension: "png")
66-
let name = "swift"
67-
try multipart.add(url: url, name: name)
68-
69-
let request = try Request<Void>.post(.test, body: .multipart(multipart))
70+
71+
func test_toURLRequest_bodyIsMultipart_fillContentTypeHeader() throws {
72+
let boundary = "boundary"
73+
var multipart = MultipartFormData(boundary: boundary)
74+
let url = try url(forResource: "swift", withExtension: "png")
75+
let name = "swift"
76+
try multipart.add(url: url, name: name)
77+
78+
let request = try Request<Void>.post(.test, body: multipart)
7079
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
71-
72-
XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], HTTPContentType.multipart(boundary: multipart.boundary).value)
80+
81+
XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], HTTPContentType.multipart(boundary: multipart.boundary).value)
7382
}
7483

7584
}
7685

77-
private struct Body: Encodable {
86+
private struct BodyMock: Encodable {
7887

7988
}

0 commit comments

Comments
 (0)