@@ -9,128 +9,159 @@ import Typhoon
99
1010// MARK: - RequestProcessor
1111
12- /// An object that handles request processing.
12+ /// An actor responsible for executing network requests in a thread-safe manner.
13+ ///
14+ /// `RequestProcessor` manages the entire lifecycle of a request, including construction,
15+ /// authentication adaptation, execution, credential refreshing, and retry logic.
1316actor RequestProcessor {
14- // MARK: Properties
17+ // MARK: - Properties
1518
16- /// The network layer's configuration.
19+ /// The network layer's configuration containing session settings and decoders .
1720 private let configuration : Configuration
18- /// The object that coordinates a group of related, network data transfer tasks.
21+
22+ /// The underlying `URLSession` used to manage data transfer tasks.
1923 private let session : URLSession
20- /// The data request handler.
24+
25+ /// The handler responsible for managing the state and events of a specific data task.
2126 private let dataRequestHandler : any IDataRequestHandler
22- /// The request builder.
27+
28+ /// The component used to transform `IRequest` models into `URLRequest` objects.
2329 private let requestBuilder : IRequestBuilder
24- /// The retry policy service.
25- private let retryPolicyService : IRetryPolicyService
26- /// The authenticator interceptor.
30+
31+ /// An optional service that handles request retries based on specific strategies.
32+ private let retryPolicyService : IRetryPolicyService ?
33+
34+ /// An optional interceptor for modifying requests and handling authentication challenges.
2735 private let interceptor : IAuthenticationInterceptor ?
28- /// The delegate.
36+
37+ /// A thread-safe delegate for observing and validating request processor events.
2938 private var delegate : SafeRequestProcessorDelegate ?
3039
31- // MARK: Initialization
40+ /// A global evaluator to determine if a retry should be attempted based on the error.
41+ /// This applies to all requests processed by this instance.
42+ private let retryEvaluator : ( @Sendable ( Error ) -> Bool ) ?
43+
44+ // MARK: - Initialization
3245
3346 /// Creates a new `RequestProcessor` instance.
3447 ///
3548 /// - Parameters:
36- /// - configure : The network layer's configuration.
49+ /// - configuration : The network layer's configuration.
3750 /// - requestBuilder: The request builder.
3851 /// - dataRequestHandler: The data request handler.
3952 /// - retryPolicyService: The retry policy service.
53+ /// - delegate: A thread-safe delegate for processor events.
54+ /// - interceptor: An authenticator interceptor.
4055 init (
4156 configuration: Configuration ,
4257 requestBuilder: IRequestBuilder ,
4358 dataRequestHandler: any IDataRequestHandler ,
44- retryPolicyService: IRetryPolicyService ,
59+ retryPolicyService: IRetryPolicyService ? ,
4560 delegate: SafeRequestProcessorDelegate ? ,
46- interceptor: IAuthenticationInterceptor ?
61+ interceptor: IAuthenticationInterceptor ? ,
62+ retryEvaluator: ( @Sendable ( Error ) -> Bool ) ?
4763 ) {
4864 self . configuration = configuration
4965 self . requestBuilder = requestBuilder
5066 self . dataRequestHandler = dataRequestHandler
5167 self . retryPolicyService = retryPolicyService
5268 self . delegate = delegate
5369 self . interceptor = interceptor
70+ self . retryEvaluator = retryEvaluator
71+
5472 self . dataRequestHandler. urlSessionDelegate = configuration. sessionDelegate
73+
5574 session = URLSession (
5675 configuration: configuration. sessionConfiguration,
5776 delegate: dataRequestHandler,
5877 delegateQueue: configuration. sessionDelegateQueue
5978 )
6079 }
6180
62- // MARK: Private
81+ // MARK: - Private Methods
6382
64- /// Performs a network request.
83+ // swiftlint:disable function_body_length
84+ /// Orchestrates the execution of a network request, including building, adaptation, and error handling.
6585 ///
6686 /// - Parameters:
67- /// - request: The network request.
68- /// - strategy: The retry policy strategy.
69- /// - delegate: A protocol that defines methods that URL session instances call on their delegates
70- /// to handle session-level events, like session life cycle changes.
71- /// - configure: A closure to configure the URLRequest.
72- ///
73- /// - Returns: The response from the network request.
87+ /// - request: The network request model.
88+ /// - strategy: An optional override for the retry policy strategy.
89+ /// - delegate: A delegate to handle session-level events.
90+ /// - configure: A closure for final modifications to the `URLRequest`.
91+ /// - Returns: A `Response` object containing the raw `Data`.
7492 private func performRequest(
7593 _ request: some IRequest ,
7694 strategy: RetryPolicyStrategy ? = nil ,
7795 delegate: URLSessionDelegate ? ,
78- configure: ( @Sendable ( inout URLRequest ) throws -> Void ) ?
96+ configure: ( @Sendable ( inout URLRequest ) throws -> Void ) ? ,
97+ shouldRetry: ( @Sendable ( Error ) -> Bool ) ?
7998 ) async throws -> Response < Data > {
80- try await performRequest ( strategy : strategy ) { [ weak self ] in
81- guard let self , var urlRequest = try requestBuilder . build ( request , configure ) else {
82- throw NetworkLayerError . badURL
83- }
99+ try await performRequest (
100+ strategy : strategy ,
101+ send : { [ weak self ] in
102+ guard let self else { throw NetworkLayerError . badURL }
84103
85- try await adapt ( request, urlRequest : & urlRequest , session : session )
104+ var urlRequest = try requestBuilder . build ( request, configure ) ?? { throw NetworkLayerError . badURL } ( )
86105
87- try await self . delegate ? . wrappedValue ? . requestProcessor ( self , willSendRequest : urlRequest)
106+ try await adapt ( request , urlRequest : & urlRequest, session : session )
88107
89- let task = session . dataTask ( with : urlRequest)
108+ try await self . delegate ? . wrappedValue ? . requestProcessor ( self , willSendRequest : urlRequest)
90109
91- do {
92- let response = try await dataRequestHandler. startDataTask ( task, delegate: delegate)
110+ let task = session. dataTask ( with: urlRequest)
93111
94- if request. requiresAuthentication {
95- let isRefreshedCredential = try await refresh (
96- urlRequest: urlRequest,
97- response: response,
98- session: session
99- )
112+ do {
113+ let response = try await dataRequestHandler. startDataTask ( task, delegate: delegate)
100114
101- if isRefreshedCredential {
102- throw AuthenticatorInterceptorError . missingCredential
115+ if request. requiresAuthentication {
116+ let isRefreshedCredential = try await refresh (
117+ urlRequest: urlRequest,
118+ response: response,
119+ session: session
120+ )
121+
122+ if isRefreshedCredential {
123+ throw AuthenticatorInterceptorError . missingCredential
124+ }
103125 }
126+
127+ try await validate ( response)
128+
129+ return response
130+ } catch {
131+ throw error
104132 }
133+ } , shouldRetry: { [ weak self] error in
134+ guard let self else { return false }
105135
106- try await validate ( response )
136+ let globalResult = retryEvaluator ? ( error ) ?? true
107137
108- return response
109- } catch {
110- throw error
138+ let localResult = shouldRetry ? ( error ) ?? true
139+
140+ return globalResult && localResult
111141 }
112- }
142+ )
113143 }
114144
115- /// Adapts an initial request.
145+ // swiftlint:enable function_body_length
146+
147+ /// Modifies the `URLRequest` to include authentication credentials if required.
116148 ///
117149 /// - Parameters:
118- /// - request: The request model.
119- /// - urlRequest: The request that needs to be authenticated .
120- /// - session: The URLSession for which the request is being refreshed .
150+ /// - request: The initial request model.
151+ /// - urlRequest: The `URLRequest` being prepared for transport .
152+ /// - session: The current `URLSession` .
121153 private func adapt( _ request: some IRequest , urlRequest: inout URLRequest , session: URLSession ) async throws {
122154 guard request. requiresAuthentication else { return }
123155 try await interceptor? . adapt ( request: & urlRequest, for: session)
124156 }
125157
126- /// Refreshes credential.
158+ /// Checks if a request requires a credential refresh and performs it if necessary .
127159 ///
128160 /// - Parameters:
129- /// - urlRequest: The request that needs to be authenticated.
130- /// - response: The metadata associated with the response to an HTTP protocol URL load request.
131- /// - session: The URLSession for which the request is being refreshed.
132- ///
133- /// - Returns: `true` if the request's token is refreshed, false otherwise.
161+ /// - urlRequest: The failed or unauthorized request.
162+ /// - response: The received network response.
163+ /// - session: The current `URLSession`.
164+ /// - Returns: `true` if a refresh was triggered, `false` otherwise.
134165 private func refresh(
135166 urlRequest: URLRequest ,
136167 response: Response < some Any > ,
@@ -146,24 +177,28 @@ actor RequestProcessor {
146177 return false
147178 }
148179
149- /// Performs a request with a retry policy .
180+ /// Wraps a request operation with retry logic provided by the `retryPolicyService` .
150181 ///
151182 /// - Parameters:
152- /// - strategy: The strategy for retrying the request .
153- /// - send: The closure that sends the request.
154- ///
155- /// - Returns: The response from the network request.
183+ /// - strategy: The strategy to apply for retries .
184+ /// - send: An asynchronous closure that executes the request logic .
185+ /// - shouldRetry: A closure to decide if a retry should occur based on the error.
186+ /// - Returns: The result of the request if successful .
156187 private func performRequest< T: Sendable > (
157188 strategy: RetryPolicyStrategy ? = nil ,
158- _ send: @Sendable ( ) async throws -> T
189+ send: @Sendable ( ) async throws -> T ,
190+ shouldRetry: @Sendable @escaping ( Error ) -> Bool
159191 ) async throws -> T {
160- do {
161- return try await send ( )
162- } catch {
163- return try await retryPolicyService . retry ( strategy : strategy , send)
192+ if let retryPolicyService {
193+ try await retryPolicyService . retry ( strategy : strategy , onFailure : shouldRetry , send )
194+ } else {
195+ try await send ( )
164196 }
165197 }
166198
199+ /// Triggers the delegate's validation logic for the received HTTP response.
200+ ///
201+ /// - Parameter response: The response object to validate.
167202 private func validate( _ response: Response < Data > ) throws {
168203 guard let urlResponse = response. response as? HTTPURLResponse else { return }
169204 try delegate? . wrappedValue? . requestProcessor (
@@ -178,13 +213,32 @@ actor RequestProcessor {
178213// MARK: IRequestProcessor
179214
180215extension RequestProcessor : IRequestProcessor {
216+ /// Sends a network request and decodes the response into a specified type.
217+ ///
218+ /// - Parameters:
219+ /// - request: The request model.
220+ /// - strategy: Optional retry strategy override.
221+ /// - delegate: Optional session delegate.
222+ /// - configure: Optional closure to modify the `URLRequest`.
223+ /// - shouldRetry: Optional closure to handle specific error filtering.
224+ /// - Returns: A `Response` object containing the decoded model of type `M`.
181225 func send< M: Decodable & Sendable > (
182226 _ request: some IRequest ,
183227 strategy: RetryPolicyStrategy ? = nil ,
184228 delegate: URLSessionDelegate ? = nil ,
185- configure: ( @Sendable ( inout URLRequest ) throws -> Void ) ? = nil
229+ configure: ( @Sendable ( inout URLRequest ) throws -> Void ) ? = nil ,
230+ shouldRetry: ( @Sendable ( Error ) -> Bool ) ? = nil
186231 ) async throws -> Response < M > {
187- let response = try await performRequest ( request, strategy: strategy, delegate: delegate, configure: configure)
188- return try response. map { data in try self . configuration. jsonDecoder. decode ( M . self, from: data) }
232+ let response = try await performRequest (
233+ request,
234+ strategy: strategy,
235+ delegate: delegate,
236+ configure: configure,
237+ shouldRetry: shouldRetry
238+ )
239+
240+ return try response. map { data in
241+ try self . configuration. jsonDecoder. decode ( M . self, from: data)
242+ }
189243 }
190244}
0 commit comments