Skip to content

Commit 09b0ee2

Browse files
authored
Merge pull request #93 from Marcodeg/feature/HTTPAltTaskValidator-implementation
Add HTTPAltTaskValidator with .afterTask Retry Strategy
2 parents c0ffb26 + d432524 commit 09b0ee2

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// RealHTTP
3+
// Lightweight Async/Await Network Layer/Stubber for Swift
4+
//
5+
// Created & Maintained by Mobile Platforms Team @ ImmobiliareLabs.it
6+
// Email: mobile@immobiliare.it
7+
// Web: http://labs.immobiliare.it
8+
//
9+
// Created by: Daniele Margutti <hello@danielemargutti.com>
10+
11+
// CONTRIBUTORS:
12+
// Thank you to all the contributors who made this project better:
13+
// <https://github.com/immobiliare/RealHTTP/graphs/contributors>
14+
//
15+
// Copyright ©2022 Immobiliare.it SpA.
16+
// Licensed under MIT License.
17+
//
18+
19+
import Foundation
20+
21+
/// This validator can be used to provide an alternate Task to execute before
22+
/// retry the initial request. It may be used to provide silent login operation when you receive
23+
/// and authorized/forbidden errors.
24+
///
25+
/// It's triggered by the `statusCodes` which by default is set `.unathorized, .forbidden`.
26+
open class HTTPAltTaskValidator: HTTPValidator {
27+
public typealias RetryRequestProvider = ((_ request: HTTPRequest, _ response: HTTPResponse) -> HTTPRequest?)
28+
29+
// MARK: - Public Properties (Configuration Options)
30+
31+
/// HTTP status codes which trigger the validator callback.
32+
///
33+
/// NOTE:
34+
/// If you want to trigger the no-response (ie. network failure) you should
35+
/// add `none` in triggered status code response.
36+
open var statusCodes: Set<HTTPStatusCode>
37+
38+
/// This is the delay to retry the initial request after the alt request
39+
/// has been executed. By default is 0s.
40+
open var retryDelay: TimeInterval = 0
41+
42+
/// Number of alternate calls to execute.
43+
/// By default is set to `nil`.
44+
/// It means the first alternate call which fails on a certain
45+
/// request will fails any other request in the same session.
46+
open var maxAltRequests: Int?
47+
48+
/// The task to execute as an alternate operation.
49+
///
50+
/// This task is expected to perform any action that would allow the request to be retried,
51+
/// such as re-authentication. This task must be asynchronous and may throw errors.
52+
open var alternativeTask: HTTPRetryStrategy.RetryTask?
53+
54+
/// A closure that is invoked to handle errors encountered during the execution of the retry task.
55+
///
56+
/// This closure allows the application to handle errors (such as login failure) before retrying the original request.
57+
open var taskErrorCatcher: HTTPRetryStrategy.RetryTaskErrorCatcher?
58+
59+
// MARK: - Private Properties
60+
61+
/// Number of executed alternate request.
62+
open var numberOfAltRequestExecuted = 0
63+
64+
// MARK: - Initialization
65+
66+
/// Initialize to provide a new request when one of `triggeredCodes` arrives.
67+
///
68+
/// - Parameters:
69+
/// - statusCodes: array of `HTTPStatusCode` which trigger the validator (default is `[.unauthorized, .forbidden]`)
70+
/// - alternativeTask: The task to execute as an alternative operation (e.g., silent login).
71+
/// - taskErrorCatcher: A closure to handle any errors that occur during the retry task.
72+
public init(statusCodes: Set<HTTPStatusCode> = [.unauthorized, .forbidden],
73+
alternativeTask: HTTPRetryStrategy.RetryTask? = nil,
74+
taskErrorCatcher: HTTPRetryStrategy.RetryTaskErrorCatcher? = nil) {
75+
self.statusCodes = statusCodes
76+
self.alternativeTask = alternativeTask
77+
self.taskErrorCatcher = taskErrorCatcher
78+
}
79+
80+
// MARK: - Protocol Conformance
81+
82+
open func validate(response: HTTPResponse, forRequest request: HTTPRequest) -> HTTPResponseValidatorResult {
83+
guard statusCodes.contains(response.statusCode) else {
84+
// if received status code for this request is not inside the triggerable status codes we'll skip the validator.
85+
return .nextValidator
86+
}
87+
88+
if let maxAltRequests = maxAltRequests, numberOfAltRequestExecuted >= maxAltRequests {
89+
// If we reached the maximum number of alternate calls to execute we want to cancel any other attempt.
90+
return .failChain(HTTPError(.retryAttemptsReached))
91+
}
92+
93+
// if no retry operation is provided we'll skip and mark the validation as passed
94+
guard let alternativeTask = alternativeTask else {
95+
return .nextValidator
96+
}
97+
98+
// Perform the alt request strategy.
99+
numberOfAltRequestExecuted += 1
100+
return .retry(.afterTask(retryDelay, alternativeTask, taskErrorCatcher))
101+
}
102+
103+
}

0 commit comments

Comments
 (0)