Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//
2
//  RequestInterceptor.swift
3
//
4
//  Copyright (c) 2019 Alamofire Software Foundation (http://alamofire.org/)
5
//
6
//  Permission is hereby granted, free of charge, to any person obtaining a copy
7
//  of this software and associated documentation files (the "Software"), to deal
8
//  in the Software without restriction, including without limitation the rights
9
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
//  copies of the Software, and to permit persons to whom the Software is
11
//  furnished to do so, subject to the following conditions:
12
//
13
//  The above copyright notice and this permission notice shall be included in
14
//  all copies or substantial portions of the Software.
15
//
16
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
//  THE SOFTWARE.
23
//
24
 
25
import Foundation
26
 
27
/// Stores all state associated with a `URLRequest` being adapted.
28
public struct RequestAdapterState {
29
    /// The `UUID` of the `Request` associated with the `URLRequest` to adapt.
30
    public let requestID: UUID
31
 
32
    /// The `Session` associated with the `URLRequest` to adapt.
33
    public let session: Session
34
}
35
 
36
// MARK: -
37
 
38
/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
39
public protocol RequestAdapter {
40
    /// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result.
41
    ///
42
    /// - Parameters:
43
    ///   - urlRequest: The `URLRequest` to adapt.
44
    ///   - session:    The `Session` that will execute the `URLRequest`.
45
    ///   - completion: The completion handler that must be called when adaptation is complete.
46
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void)
47
 
48
    /// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result.
49
    ///
50
    /// - Parameters:
51
    ///   - urlRequest: The `URLRequest` to adapt.
52
    ///   - state:      The `RequestAdapterState` associated with the `URLRequest`.
53
    ///   - completion: The completion handler that must be called when adaptation is complete.
54
    func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void)
55
}
56
 
57
extension RequestAdapter {
58
    public func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void) {
59
        adapt(urlRequest, for: state.session, completion: completion)
60
    }
61
}
62
 
63
// MARK: -
64
 
65
/// Outcome of determination whether retry is necessary.
66
public enum RetryResult {
67
    /// Retry should be attempted immediately.
68
    case retry
69
    /// Retry should be attempted after the associated `TimeInterval`.
70
    case retryWithDelay(TimeInterval)
71
    /// Do not retry.
72
    case doNotRetry
73
    /// Do not retry due to the associated `Error`.
74
    case doNotRetryWithError(Error)
75
}
76
 
77
extension RetryResult {
78
    var retryRequired: Bool {
79
        switch self {
80
        case .retry, .retryWithDelay: return true
81
        default: return false
82
        }
83
    }
84
 
85
    var delay: TimeInterval? {
86
        switch self {
87
        case let .retryWithDelay(delay): return delay
88
        default: return nil
89
        }
90
    }
91
 
92
    var error: Error? {
93
        guard case let .doNotRetryWithError(error) = self else { return nil }
94
        return error
95
    }
96
}
97
 
98
/// A type that determines whether a request should be retried after being executed by the specified session manager
99
/// and encountering an error.
100
public protocol RequestRetrier {
101
    /// Determines whether the `Request` should be retried by calling the `completion` closure.
102
    ///
103
    /// This operation is fully asynchronous. Any amount of time can be taken to determine whether the request needs
104
    /// to be retried. The one requirement is that the completion closure is called to ensure the request is properly
105
    /// cleaned up after.
106
    ///
107
    /// - Parameters:
108
    ///   - request:    `Request` that failed due to the provided `Error`.
109
    ///   - session:    `Session` that produced the `Request`.
110
    ///   - error:      `Error` encountered while executing the `Request`.
111
    ///   - completion: Completion closure to be executed when a retry decision has been determined.
112
    func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void)
113
}
114
 
115
// MARK: -
116
 
117
/// Type that provides both `RequestAdapter` and `RequestRetrier` functionality.
118
public protocol RequestInterceptor: RequestAdapter, RequestRetrier {}
119
 
120
extension RequestInterceptor {
121
    public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
122
        completion(.success(urlRequest))
123
    }
124
 
125
    public func retry(_ request: Request,
126
                      for session: Session,
127
                      dueTo error: Error,
128
                      completion: @escaping (RetryResult) -> Void) {
129
        completion(.doNotRetry)
130
    }
131
}
132
 
133
/// `RequestAdapter` closure definition.
134
public typealias AdaptHandler = (URLRequest, Session, _ completion: @escaping (Result<URLRequest, Error>) -> Void) -> Void
135
/// `RequestRetrier` closure definition.
136
public typealias RetryHandler = (Request, Session, Error, _ completion: @escaping (RetryResult) -> Void) -> Void
137
 
138
// MARK: -
139
 
140
/// Closure-based `RequestAdapter`.
141
open class Adapter: RequestInterceptor {
142
    private let adaptHandler: AdaptHandler
143
 
144
    /// Creates an instance using the provided closure.
145
    ///
146
    /// - Parameter adaptHandler: `AdaptHandler` closure to be executed when handling request adaptation.
147
    public init(_ adaptHandler: @escaping AdaptHandler) {
148
        self.adaptHandler = adaptHandler
149
    }
150
 
151
    open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
152
        adaptHandler(urlRequest, session, completion)
153
    }
154
 
155
    open func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void) {
156
        adaptHandler(urlRequest, state.session, completion)
157
    }
158
}
159
 
160
#if swift(>=5.5)
161
extension RequestAdapter where Self == Adapter {
162
    /// Creates an `Adapter` using the provided `AdaptHandler` closure.
163
    ///
164
    /// - Parameter closure: `AdaptHandler` to use to adapt the request.
165
    /// - Returns:           The `Adapter`.
166
    public static func adapter(using closure: @escaping AdaptHandler) -> Adapter {
167
        Adapter(closure)
168
    }
169
}
170
#endif
171
 
172
// MARK: -
173
 
174
/// Closure-based `RequestRetrier`.
175
open class Retrier: RequestInterceptor {
176
    private let retryHandler: RetryHandler
177
 
178
    /// Creates an instance using the provided closure.
179
    ///
180
    /// - Parameter retryHandler: `RetryHandler` closure to be executed when handling request retry.
181
    public init(_ retryHandler: @escaping RetryHandler) {
182
        self.retryHandler = retryHandler
183
    }
184
 
185
    open func retry(_ request: Request,
186
                    for session: Session,
187
                    dueTo error: Error,
188
                    completion: @escaping (RetryResult) -> Void) {
189
        retryHandler(request, session, error, completion)
190
    }
191
}
192
 
193
#if swift(>=5.5)
194
extension RequestRetrier where Self == Retrier {
195
    /// Creates a `Retrier` using the provided `RetryHandler` closure.
196
    ///
197
    /// - Parameter closure: `RetryHandler` to use to retry the request.
198
    /// - Returns:           The `Retrier`.
199
    public static func retrier(using closure: @escaping RetryHandler) -> Retrier {
200
        Retrier(closure)
201
    }
202
}
203
#endif
204
 
205
// MARK: -
206
 
207
/// `RequestInterceptor` which can use multiple `RequestAdapter` and `RequestRetrier` values.
208
open class Interceptor: RequestInterceptor {
209
    /// All `RequestAdapter`s associated with the instance. These adapters will be run until one fails.
210
    public let adapters: [RequestAdapter]
211
    /// All `RequestRetrier`s associated with the instance. These retriers will be run one at a time until one triggers retry.
212
    public let retriers: [RequestRetrier]
213
 
214
    /// Creates an instance from `AdaptHandler` and `RetryHandler` closures.
215
    ///
216
    /// - Parameters:
217
    ///   - adaptHandler: `AdaptHandler` closure to be used.
218
    ///   - retryHandler: `RetryHandler` closure to be used.
219
    public init(adaptHandler: @escaping AdaptHandler, retryHandler: @escaping RetryHandler) {
220
        adapters = [Adapter(adaptHandler)]
221
        retriers = [Retrier(retryHandler)]
222
    }
223
 
224
    /// Creates an instance from `RequestAdapter` and `RequestRetrier` values.
225
    ///
226
    /// - Parameters:
227
    ///   - adapter: `RequestAdapter` value to be used.
228
    ///   - retrier: `RequestRetrier` value to be used.
229
    public init(adapter: RequestAdapter, retrier: RequestRetrier) {
230
        adapters = [adapter]
231
        retriers = [retrier]
232
    }
233
 
234
    /// Creates an instance from the arrays of `RequestAdapter` and `RequestRetrier` values.
235
    ///
236
    /// - Parameters:
237
    ///   - adapters:     `RequestAdapter` values to be used.
238
    ///   - retriers:     `RequestRetrier` values to be used.
239
    ///   - interceptors: `RequestInterceptor`s to be used.
240
    public init(adapters: [RequestAdapter] = [], retriers: [RequestRetrier] = [], interceptors: [RequestInterceptor] = []) {
241
        self.adapters = adapters + interceptors
242
        self.retriers = retriers + interceptors
243
    }
244
 
245
    open func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
246
        adapt(urlRequest, for: session, using: adapters, completion: completion)
247
    }
248
 
249
    private func adapt(_ urlRequest: URLRequest,
250
                       for session: Session,
251
                       using adapters: [RequestAdapter],
252
                       completion: @escaping (Result<URLRequest, Error>) -> Void) {
253
        var pendingAdapters = adapters
254
 
255
        guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return }
256
 
257
        let adapter = pendingAdapters.removeFirst()
258
 
259
        adapter.adapt(urlRequest, for: session) { result in
260
            switch result {
261
            case let .success(urlRequest):
262
                self.adapt(urlRequest, for: session, using: pendingAdapters, completion: completion)
263
            case .failure:
264
                completion(result)
265
            }
266
        }
267
    }
268
 
269
    open func adapt(_ urlRequest: URLRequest, using state: RequestAdapterState, completion: @escaping (Result<URLRequest, Error>) -> Void) {
270
        adapt(urlRequest, using: state, adapters: adapters, completion: completion)
271
    }
272
 
273
    private func adapt(_ urlRequest: URLRequest,
274
                       using state: RequestAdapterState,
275
                       adapters: [RequestAdapter],
276
                       completion: @escaping (Result<URLRequest, Error>) -> Void) {
277
        var pendingAdapters = adapters
278
 
279
        guard !pendingAdapters.isEmpty else { completion(.success(urlRequest)); return }
280
 
281
        let adapter = pendingAdapters.removeFirst()
282
 
283
        adapter.adapt(urlRequest, using: state) { result in
284
            switch result {
285
            case let .success(urlRequest):
286
                self.adapt(urlRequest, using: state, adapters: pendingAdapters, completion: completion)
287
            case .failure:
288
                completion(result)
289
            }
290
        }
291
    }
292
 
293
    open func retry(_ request: Request,
294
                    for session: Session,
295
                    dueTo error: Error,
296
                    completion: @escaping (RetryResult) -> Void) {
297
        retry(request, for: session, dueTo: error, using: retriers, completion: completion)
298
    }
299
 
300
    private func retry(_ request: Request,
301
                       for session: Session,
302
                       dueTo error: Error,
303
                       using retriers: [RequestRetrier],
304
                       completion: @escaping (RetryResult) -> Void) {
305
        var pendingRetriers = retriers
306
 
307
        guard !pendingRetriers.isEmpty else { completion(.doNotRetry); return }
308
 
309
        let retrier = pendingRetriers.removeFirst()
310
 
311
        retrier.retry(request, for: session, dueTo: error) { result in
312
            switch result {
313
            case .retry, .retryWithDelay, .doNotRetryWithError:
314
                completion(result)
315
            case .doNotRetry:
316
                // Only continue to the next retrier if retry was not triggered and no error was encountered
317
                self.retry(request, for: session, dueTo: error, using: pendingRetriers, completion: completion)
318
            }
319
        }
320
    }
321
}
322
 
323
#if swift(>=5.5)
324
extension RequestInterceptor where Self == Interceptor {
325
    /// Creates an `Interceptor` using the provided `AdaptHandler` and `RetryHandler` closures.
326
    ///
327
    /// - Parameters:
328
    ///   - adapter: `AdapterHandler`to use to adapt the request.
329
    ///   - retrier: `RetryHandler` to use to retry the request.
330
    /// - Returns:   The `Interceptor`.
331
    public static func interceptor(adapter: @escaping AdaptHandler, retrier: @escaping RetryHandler) -> Interceptor {
332
        Interceptor(adaptHandler: adapter, retryHandler: retrier)
333
    }
334
 
335
    /// Creates an `Interceptor` using the provided `RequestAdapter` and `RequestRetrier` instances.
336
    /// - Parameters:
337
    ///   - adapter: `RequestAdapter` to use to adapt the request
338
    ///   - retrier: `RequestRetrier` to use to retry the request.
339
    /// - Returns:   The `Interceptor`.
340
    public static func interceptor(adapter: RequestAdapter, retrier: RequestRetrier) -> Interceptor {
341
        Interceptor(adapter: adapter, retrier: retrier)
342
    }
343
 
344
    /// Creates an `Interceptor` using the provided `RequestAdapter`s, `RequestRetrier`s, and `RequestInterceptor`s.
345
    /// - Parameters:
346
    ///   - adapters:     `RequestAdapter`s to use to adapt the request. These adapters will be run until one fails.
347
    ///   - retriers:     `RequestRetrier`s to use to retry the request. These retriers will be run one at a time until
348
    ///                   a retry is triggered.
349
    ///   - interceptors: `RequestInterceptor`s to use to intercept the request.
350
    /// - Returns:        The `Interceptor`.
351
    public static func interceptor(adapters: [RequestAdapter] = [],
352
                                   retriers: [RequestRetrier] = [],
353
                                   interceptors: [RequestInterceptor] = []) -> Interceptor {
354
        Interceptor(adapters: adapters, retriers: retriers, interceptors: interceptors)
355
    }
356
}
357
#endif