Skip to content

Commit a57c4f2

Browse files
committed
📚 ♻️ ✨ Adds response to retriers. General documentation.
- Passes a response, if any, to retriers. - Improves documentation of various HTTPNetworking types. - Converts `HTTPMethod` to a struct for external extensibility purposes.
1 parent f2c57a3 commit a57c4f2

File tree

12 files changed

+143
-82
lines changed

12 files changed

+143
-82
lines changed

Sources/HTTPNetworking/HTTPClient.swift

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
/// `HTTPClient` creates and manages requests over the network.
44
///
5-
/// The client also provides common functionality for all ``HTTPRequest`` objects, including encoding and decoding strategies,
5+
/// The client provides common functionality for all ``HTTPRequest`` objects, including encoding and decoding strategies,
66
/// request adaptation, response validation and retry stratagies.
77
///
88
/// Generally an ``HTTPClient`` is used to manage the interaction with a single API service. Most APIs
@@ -12,20 +12,37 @@ import Foundation
1212
/// If your API requires a custom encoder or decoder, you can provide your own custom ones to the client. These encoder and decoders
1313
/// will then be used for all requests made using the client.
1414
///
15+
/// ## Adaptors
16+
///
1517
/// If your API requires some sort of repetitive task applied to each request, rather than manipulating it at each call, you can make use of an
1618
/// ``HTTPRequestAdaptor``. The adaptors that you provide to the client will all be run on a request before it is sent to your API.
1719
/// This provides you with an opportunity to insert various headers or perform asyncrounous tasks before sending the outbound request.
1820
///
21+
/// > Important: All adaptors will always run for every request.
22+
///
23+
/// ## Validators
24+
///
1925
/// Different APIs require different forms of validation that help you determine if a request was successful or not.
2026
/// The default ``HTTPClient`` makes no assumptions about the format and/or success of an API's response. Instead, you can make use of an
2127
/// ``HTTPResponseValidator``. The validators that you provide the client will all be run on the response received by your API.
2228
/// This provides you with an opportunity to determine wether or not the response was a success and failure, and consolidate potentially repeated logic in one place.
2329
///
30+
/// > Important: Validators will be run one by one on a request's response. If any validators determines that the response is invalid,
31+
/// the request will fail and throw the error returned by that validator. No other subsequent validators will be run for that run of a request.
32+
///
33+
/// ## Retriers
34+
///
2435
/// Sometimes you may want to implement logic for handling retries of failed requests. An ``HTTPClient`` can make use of an
2536
/// ``HTTPRequestRetriers``. The retriers that you provide the client will be run when a request fails.
2637
/// This provides you with an opportunity to observe the error and determine wether or not the request should be sent again. You can implement complex retry
2738
/// logic across your entire client without having to repeat yourself.
2839
///
40+
/// > Important: Retriers will be run one by one in the event of a failed request. If any retrier concedes that the request should not be retried,
41+
/// the request will ask the next retrier if the request should be retried. If any retrier determines that a request should be retired, the request will
42+
/// immedietly be retired, without asking any subsequent retriers.
43+
///
44+
/// ## Request Manipulation
45+
///
2946
/// In addition to providing adaptors, validators and retriers to your entire client, you can provide additional configuration to each
3047
/// request using a chaining style syntax. This allows you to add additional configuration to endpoints that you may want to provide further
3148
/// customization to beyond the standard configuration at the client level.
@@ -44,17 +61,7 @@ import Foundation
4461
/// let response = try await request.run()
4562
/// ```
4663
///
47-
/// > Note: All adaptors, validators and retriers at the client level will be run first, after which the
48-
/// request configurations at the request level will be run.
49-
///
50-
/// > Important: All adaptors will always run for every request.
51-
///
52-
/// > Important: Validators will be run one by one on a request's response. If any validators determines that the response is invalid,
53-
/// the request will fail and throw the error returned by that validator. No other subsequent validators will be run for that run of a request.
54-
///
55-
/// > Important: Retriers will be run one by one in the event of a failred request. If any retrier concedes that the request should not be retried,
56-
/// the request will ask the next retrier if the request should be retried. If any retrier determines that a request should be retired, the request will
57-
/// immedietly be retired, without asking any subsequent retriers.
64+
/// > Note: For all adaptors, validators and retriers, the client level plugins will run before the request level plugins
5865
public struct HTTPClient {
5966

6067
// MARK: Properties

Sources/HTTPNetworking/HTTPDispatcher.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public struct HTTPDispatcher {
1818
}
1919

2020
/// Fetches data using the provided request.
21-
func data(for request: URLRequest) async throws -> (Data, HTTPURLResponse) {
21+
func data(for request: URLRequest) async throws -> (data: Data, response: HTTPURLResponse) {
2222
let (data, response) = try await session.data(for: request)
2323
guard let response = response as? HTTPURLResponse else {
2424
throw URLError(.cannotParseResponse)
@@ -46,7 +46,7 @@ extension HTTPDispatcher {
4646
/// A mock implementation of an ``HTTPDispatcher`` that will return the provided
4747
/// response to the corresponding request.
4848
///
49-
/// - Parameter responses: A dictionary of responses keyed by the requests for which they should respond to.
49+
/// - Parameter responses: A dictionary of responses keyed by the urls for which they should respond to.
5050
/// - Returns: An ``HTTPDispatcher``
5151
public static func mock(
5252
responses: [URL: MockResponse]
Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,39 @@
11
import Foundation
22

3-
/// An HTTP method for an HTTP request.
4-
public enum HTTPMethod: String {
5-
/// The GET method requests a representation of the specified resource. Requests using GET should only retrieve data.
6-
case get
7-
/// The HEAD method asks for a response identical to that of a GET request, but without the response body.
8-
case head
9-
/// The POST method is used to submit an entity to the specified resource, often causing a change in state or side effects on the server.
10-
case post
11-
/// The PUT method replaces all current representations of the target resource with the request payload.
12-
case put
13-
/// The DELETE method deletes the specified resource.
14-
case delete
15-
/// The CONNECT method establishes a tunnel to the server identified by the target resource.
16-
case connect
17-
/// The OPTIONS method is used to describe the communication options for the target resource.
18-
case options
19-
/// The TRACE method performs a message loop-back test along the path to the target resource.
20-
case trace
21-
/// The PATCH method is used to apply partial modifications to a resource.
22-
case patch
3+
/// A type representing HTTP methods.
4+
///
5+
/// See [RFC 9110 - Section 9](https://www.rfc-editor.org/rfc/rfc9110.html#name-methods) for more information.
6+
public struct HTTPMethod: RawRepresentable, Equatable, Hashable {
7+
/// The `GET` method requests a representation of the specified resource. Requests using GET should only retrieve data.
8+
public static let get = HTTPMethod(rawValue: "GET")
9+
10+
/// The `HEAD` method asks for a response identical to that of a GET request, but without the response body.
11+
public static let head = HTTPMethod(rawValue: "HEAD")
12+
13+
/// The `POST` method is used to submit an entity to the specified resource, often causing a change in state or side effects on the server.
14+
public static let post = HTTPMethod(rawValue: "POST")
15+
16+
/// The `PUT` method replaces all current representations of the target resource with the request payload.
17+
public static let put = HTTPMethod(rawValue: "PUT")
18+
19+
/// The `DELETE` method deletes the specified resource.
20+
public static let delete = HTTPMethod(rawValue: "DELETE")
21+
22+
/// The `CONNECT` method establishes a tunnel to the server identified by the target resource.
23+
public static let connect = HTTPMethod(rawValue: "CONNECT")
24+
25+
/// The `OPTIONS` method is used to describe the communication options for the target resource.
26+
public static let options = HTTPMethod(rawValue: "OPTIONS")
27+
28+
/// The `TRACE` method performs a message loop-back test along the path to the target resource.
29+
public static let trace = HTTPMethod(rawValue: "TRACE")
30+
31+
/// The `PATCH` method is used to apply partial modifications to a resource.
32+
public static let patch = HTTPMethod(rawValue: "PATCH")
33+
34+
public let rawValue: String
35+
36+
public init(rawValue: String) {
37+
self.rawValue = rawValue.uppercased()
38+
}
2339
}

Sources/HTTPNetworking/HTTPRequest.swift

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ import Foundation
88
///
99
/// To send an ``HTTPRequest`` call ``run()``. This will perform the network request ant return the expected response type.
1010
///
11-
/// You can adapt the `URLRequest`, before it gets sent out, by calling ``adapt(_:)`` or ``adapt(with:)``.
12-
/// By providing an ``AdaptationHandler`` or ``HTTPRequestAdaptor``, you can perform various asyncronous logic before a request gets sent
11+
/// Each ``HTTPRequest`` is setup with concurrency in mind. In-flight requests are periodically checked for `Task` cancellation and will approprietly stop
12+
/// when their parent task is cancelled.
13+
///
14+
/// > Tip: You can adapt the `URLRequest`, before it gets sent out, by calling methods like ``adapt(_:)`` or ``adapt(with:)``.
15+
/// By providing an ``AdaptationHandler`` or any ``HTTPRequestAdaptor``, you can perform various asyncronous logic before a request gets sent
1316
/// Typical use cases include things like adding headers to requests, or managing the lifecycle of a client's authorization status.
1417
///
15-
/// You can validate the `HTTPURLResponse`, before it gets deocded, by calling ``validate(_:)`` or ``validate(with:)``.
16-
/// By providing a ``ValidationHandler`` or ``HTTPResponseValidator``, you can perform various validations before a response is decoded.
18+
/// > Tip: You can validate the `HTTPURLResponse`, before it gets deocded, by calling methods like ``validate(_:)`` or ``validate(with:)``.
19+
/// By providing a ``ValidationHandler`` or any ``HTTPResponseValidator``, you can perform various validations before a response is decoded.
1720
/// Typical use cases include things like validating the status code or headers of a request.
1821
///
19-
/// You can retry the ``HTTPRequest`` if it fails by calling ``retry(_:)`` or ``retry(with:)``.
20-
/// By providing a ``RetryHandler`` or ``HTTPRequestRetrier``, you can determine if a request should be retried if it fails.
22+
/// > Tip: You can retry the ``HTTPRequest`` if it fails by calling retry methods like ``retry(_:)`` or ``retry(with:)``.
23+
/// By providing a ``RetryHandler`` or any ``HTTPRequestRetrier``, you can determine if a request should be retried if it fails.
2124
/// Typical use cases include retrying requests a given number of times in the event of poor network connectivity.
2225
public class HTTPRequest<Value: Decodable> {
2326

@@ -69,6 +72,8 @@ public class HTTPRequest<Value: Decodable> {
6972
/// Executes the request to the dispatcher.
7073
private func execute(previousAttempts: Int) async throws -> Value {
7174
var request = self.request
75+
var response: HTTPURLResponse?
76+
7277
do {
7378

7479
try Task.checkCancellation()
@@ -79,24 +84,26 @@ public class HTTPRequest<Value: Decodable> {
7984
try Task.checkCancellation()
8085

8186
// Dispatch the request and wait for a response.
82-
let (data, response) = try await dispatcher.data(for: request)
87+
let reply = try await dispatcher.data(for: request)
88+
response = reply.response
8389

8490
try Task.checkCancellation()
8591

8692
// Validate the response.
8793
try await ZipValidator(validators)
88-
.validate(response, for: request, with: data)
94+
.validate(reply.response, for: request, with: reply.data)
8995
.get()
9096

9197
try Task.checkCancellation()
9298

9399
// Convert data to the expected type
94-
return try decoder.decode(Value.self, from: data)
100+
return try decoder.decode(Value.self, from: reply.data)
95101
} catch {
96102
let previousAttempts = previousAttempts + 1
97103
let strategy = try await ZipRetrier(retriers).retry(
98104
request,
99105
for: dispatcher.session,
106+
with: response,
100107
dueTo: error,
101108
previousAttempts: previousAttempts
102109
)

Sources/HTTPNetworking/Plugins/Adaptors/Adaptor.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import Foundation
22

3-
public typealias AdaptationHandler = (URLRequest, URLSession) async throws -> URLRequest
3+
public typealias AdaptationHandler = (
4+
_ request: URLRequest,
5+
_ session: URLSession
6+
) async throws -> URLRequest
47

58
/// An ``HTTPRequestAdaptor`` that can be used to manipulate a `URLRequest` before it is sent out over the network.
69
public struct Adaptor: HTTPRequestAdaptor {

Sources/HTTPNetworking/Plugins/HTTPRequestRetrier.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import Foundation
77
/// By conforming to ``HTTPRequestRetrier`` you can implement both simple and complex logic for retrying an ``HTTPRequest`` when
88
/// it fails. Common uses cases include retrying a given amount of times due to a specific error such as poor network connectivity.
99
public protocol HTTPRequestRetrier {
10-
func retry(_ request: URLRequest, for session: URLSession, dueTo error: Error, previousAttempts: Int) async throws -> RetryStrategy
10+
func retry(
11+
_ request: URLRequest,
12+
for session: URLSession,
13+
with response: HTTPURLResponse?,
14+
dueTo error: Error,
15+
previousAttempts: Int
16+
) async throws -> RetryStrategy
1117
}
1218

1319
// MARK: - RetryStrategy

Sources/HTTPNetworking/Plugins/Retriers/Retrier.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import Foundation
22

3-
public typealias RetryHandler = (URLRequest, URLSession, Error, Int) async throws -> RetryStrategy
3+
public typealias RetryHandler = (
4+
_ request: URLRequest,
5+
_ session: URLSession,
6+
_ response: HTTPURLResponse?,
7+
_ error: Error,
8+
_ previousAttempts: Int
9+
) async throws -> RetryStrategy
410

511
/// An ``HTTPRequestRetrier`` that can be used to retry an ``HTTPRequest`` upon failure.
612
public struct Retrier: HTTPRequestRetrier {
@@ -24,10 +30,11 @@ public struct Retrier: HTTPRequestRetrier {
2430
public func retry(
2531
_ request: URLRequest,
2632
for session: URLSession,
33+
with response: HTTPURLResponse?,
2734
dueTo error: Error,
2835
previousAttempts: Int
2936
) async throws -> RetryStrategy {
30-
try await handler(request, session, error, previousAttempts)
37+
try await handler(request, session, response, error, previousAttempts)
3138
}
3239
}
3340

Sources/HTTPNetworking/Plugins/Retriers/ZipRetrier.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public struct ZipRetrier: HTTPRequestRetrier {
2929
public func retry(
3030
_ request: URLRequest,
3131
for session: URLSession,
32+
with response: HTTPURLResponse?,
3233
dueTo error: Error,
3334
previousAttempts: Int
3435
) async throws -> RetryStrategy {
@@ -38,6 +39,7 @@ public struct ZipRetrier: HTTPRequestRetrier {
3839
let strategy = try await retrier.retry(
3940
request,
4041
for: session,
42+
with: response,
4143
dueTo: error,
4244
previousAttempts: previousAttempts
4345
)

Sources/HTTPNetworking/Plugins/Validators/Validator.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import Foundation
22

3-
public typealias ValidationHandler = (HTTPURLResponse, URLRequest, Data) async throws -> ValidationResult
3+
public typealias ValidationHandler = (
4+
_ response: HTTPURLResponse,
5+
_ request: URLRequest,
6+
_ data: Data
7+
) async throws -> ValidationResult
48

59
/// An ``HTTPResponseValidator`` that can be used to validate a response from an ``HTTPRequest``.
610
public struct Validator: HTTPResponseValidator {

0 commit comments

Comments
 (0)