Skip to content

Commit d5cbca6

Browse files
authored
feat(request): [#11] Introduce Endpoint instead of Path (#13)
1 parent cd9957b commit d5cbca6

File tree

8 files changed

+93
-47
lines changed

8 files changed

+93
-47
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,16 @@ extension Request {
2222

2323
And... voila! We defined a `login(_:)` request which will request login endpoint by sending a `UserBody` and waiting for a `UserResponse`. Now it's time to use it.
2424

25-
You can also use an enum to define your Request path:
25+
You can declare constant endpoints if needed (refer to Endpoint documentation to see more):
2626

2727
```swift
28-
enum MyAppEndpoint: String, Path {
29-
case login
28+
extension Endpoint {
29+
static let login: Endpoint = "login"
3030
}
3131

3232
extension Request {
3333
static let func login(_ body: UserBody) -> Self where Output == UserResponse {
34-
.post(MyAppEndpoint.login, body: body)
34+
.post(.login, body: body)
3535
}
3636
}
3737
```

Sources/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Packages
2+
3+
The project is split in two packages:
4+
5+
- SimpleHTTPFoundation
6+
- SimpleFoundation
7+
8+
## SimpleHTTPFoundation
9+
10+
This package contain extensions on Foundation objects to bring some convenience methods to Foundation that could be used by any network package or project.
11+
12+
Related article: [Designing a lightweight HTTP framework: foundation](https://swiftunwrap.com/article/designing-http-framework-foundation/).
13+
14+
## SimpleHTTP
15+
16+
It contain package true functionalities like Request or Session objects. When building a functionality in this package try to do it in 3 steps:
17+
18+
- Design the new objects
19+
- Make them interact with Foundation. For instance `Request+URLRequest.swift` bridge from `Request` to `URLRequest`
20+
- Add pieces to `Session`
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Foundation
2+
3+
/// A endpoint represents a path a request can query to
4+
///
5+
/// You can create endpoints using plain String, for instance:
6+
/// ```swift
7+
/// extension Endpoint {
8+
/// static let user = "v1/users"
9+
/// }
10+
///
11+
/// If you want to regroup a set of endpoints you can use your own "namespace" and add a forward declaration in `Endpoint`.
12+
/// Adding a declaration provide autocompletion when using it in `Request`.
13+
/// ```swift
14+
/// enum MyEndpoints {
15+
/// static let user: Endpoint = "v1/users"
16+
/// }
17+
///
18+
/// extension Endpoint {
19+
/// static let myEndpoints = MyEndpoints.self
20+
/// }
21+
///
22+
/// let user: Endpoint = .myEndpoints.user
23+
/// ```
24+
public struct Endpoint: Equatable, ExpressibleByStringLiteral {
25+
/// relative path
26+
let path: String
27+
28+
init(path: String) {
29+
self.path = path
30+
}
31+
32+
public init(stringLiteral value: StringLiteralType) {
33+
self.init(path: value)
34+
}
35+
36+
public static func ==(lhs: Endpoint, rhs: String) -> Bool {
37+
lhs.path == rhs
38+
}
39+
}
40+

Sources/SimpleHTTP/Request/Path.swift

Lines changed: 0 additions & 14 deletions
This file was deleted.

Sources/SimpleHTTP/Request/Request.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,33 @@ public enum Body {
1717
/// Highly inspired by https://swiftwithmajid.com/2021/02/10/building-type-safe-networking-in-swift/
1818
public struct Request<Output> {
1919

20-
/// request relative path
21-
public let path: String
20+
/// request relative endpoint
21+
public let endpoint: Endpoint
2222
public let method: Method
2323
public let body: Body?
2424
public let query: [String: QueryParam]
2525
public private(set) var headers: HTTPHeaderFields = [:]
2626

27-
public static func get(_ path: Path, query: [String: QueryParam] = [:]) -> Self {
28-
self.init(path: path, method: .get, query: query, body: nil)
27+
public static func get(_ endpoint: Endpoint, query: [String: QueryParam] = [:]) -> Self {
28+
self.init(endpoint: endpoint, method: .get, query: query, body: nil)
2929
}
3030

31-
public static func post(_ path: Path, body: Body?, query: [String: QueryParam] = [:])
31+
public static func post(_ endpoint: Endpoint, body: Body?, query: [String: QueryParam] = [:])
3232
-> Self {
33-
self.init(path: path, method: .post, query: query, body: body)
33+
self.init(endpoint: endpoint, method: .post, query: query, body: body)
3434
}
3535

36-
public static func put(_ path: Path, body: Body, query: [String: QueryParam] = [:])
36+
public static func put(_ endpoint: Endpoint, body: Body, query: [String: QueryParam] = [:])
3737
-> Self {
38-
self.init(path: path, method: .put, query: query, body: body)
38+
self.init(endpoint: endpoint, method: .put, query: query, body: body)
3939
}
4040

41-
public static func delete(_ path: Path, query: [String: QueryParam] = [:]) -> Self {
42-
self.init(path: path, method: .delete, query: query, body: nil)
41+
public static func delete(_ endpoint: Endpoint, query: [String: QueryParam] = [:]) -> Self {
42+
self.init(endpoint: endpoint, method: .delete, query: query, body: nil)
4343
}
4444

45-
private init(path: Path, method: Method, query: [String: QueryParam], body: Body?) {
46-
self.path = path.path
45+
private init(endpoint: Endpoint, method: Method, query: [String: QueryParam], body: Body?) {
46+
self.endpoint = endpoint
4747
self.method = method
4848
self.body = body
4949
self.query = query

Sources/SimpleHTTP/Request/URL+Request.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import FoundationNetworking
66

77
extension URL {
88
init<Output>(from request: Request<Output>) throws {
9-
guard var components = URLComponents(string: request.path) else {
10-
throw URLComponents.Error.invalid(path: request.path)
9+
guard var components = URLComponents(string: request.endpoint.path) else {
10+
throw URLComponents.Error.invalid(endpoint: request.endpoint)
1111
}
1212

1313
let queryItems = (components.queryItems ?? []) + request.query.queryItems
@@ -24,7 +24,7 @@ extension URL {
2424

2525
extension URLComponents {
2626
public enum Error: Swift.Error {
27-
case invalid(path: String)
27+
case invalid(endpoint: Endpoint)
2828
case cannotGenerateURL(components: URLComponents)
2929
}
3030
}

Tests/SimpleHTTPTests/Request/RequestTests.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
import XCTest
22
@testable import SimpleHTTP
33

4+
extension Endpoint {
5+
fileprivate static let test: Endpoint = "test"
6+
}
7+
48
class RequestTests: XCTestCase {
5-
enum TestEndpoint: String, Path {
6-
case test
7-
}
8-
99
let baseURL = URL(string: "https://google.fr")!
1010

1111
func test_init_withPathAsString() {
12-
XCTAssertEqual(Request<Void>.get("hello_world").path, "hello_world")
12+
XCTAssertEqual(Request<Void>.get("hello_world").endpoint, "hello_world")
1313
}
1414

1515
func test_toURLRequest_setHttpMethod() throws {
16-
let request = try Request<Void>.post(TestEndpoint.test, body: nil)
16+
let request = try Request<Void>.post(.test, body: nil)
1717
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
1818

1919
XCTAssertEqual(request.httpMethod, "POST")
2020
}
2121

2222
func test_toURLRequest_EncodeBody() throws {
23-
let request = try Request<Void>.post(TestEndpoint.test, body: .encodable(Body()))
23+
let request = try Request<Void>.post(.test, body: .encodable(Body()))
2424
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
2525

2626
XCTAssertEqual(request.httpBody, try JSONEncoder().encode(Body()))
@@ -34,7 +34,7 @@ class RequestTests: XCTestCase {
3434
let name = "swift"
3535
try multipart.add(url: url, name: name)
3636

37-
let request = try Request<Void>.post(TestEndpoint.test, body: .multipart(multipart))
37+
let request = try Request<Void>.post(.test, body: .multipart(multipart))
3838
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
3939

4040
/// We can't use `XCTAssertEqual(request.httpBody, try multipart.encode)`
@@ -53,7 +53,7 @@ class RequestTests: XCTestCase {
5353
}
5454

5555
func test_toURLRequest_bodyIsEncodable_FillDefaultHeaders() throws {
56-
let request = try Request<Void>.post(TestEndpoint.test, body: .encodable(Body()))
56+
let request = try Request<Void>.post(.test, body: .encodable(Body()))
5757
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
5858

5959
XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], "application/json")
@@ -66,7 +66,7 @@ class RequestTests: XCTestCase {
6666
let name = "swift"
6767
try multipart.add(url: url, name: name)
6868

69-
let request = try Request<Void>.post(TestEndpoint.test, body: .multipart(multipart))
69+
let request = try Request<Void>.post(.test, body: .multipart(multipart))
7070
.toURLRequest(encoder: JSONEncoder(), relativeTo: baseURL)
7171

7272
XCTAssertEqual(request.allHTTPHeaderFields?["Content-Type"], HTTPContentType.multipart(boundary: multipart.boundary).value)

Tests/SimpleHTTPTests/Session/SessionTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ class SessionAsyncTests: XCTestCase {
9797
}
9898
}
9999

100-
private enum Endpoint: String, Path {
101-
case test
100+
private extension Endpoint {
101+
static let test: Endpoint = "test"
102102
}
103103

104104
private struct Content: Codable, Equatable {
@@ -111,11 +111,11 @@ private struct CustomError: Error, Codable, Equatable {
111111

112112
private extension Request {
113113
static func test() -> Self where Output == Content {
114-
.get(Endpoint.test)
114+
.get(.test)
115115
}
116116

117117
static func void() -> Self where Output == Void {
118-
.get(Endpoint.test)
118+
.get(.test)
119119
}
120120
}
121121

0 commit comments

Comments
 (0)