// // Copyright (c) 2022 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import TINetworking import TISwiftUtils import TIFoundationUtils import Alamofire open class DefaultRecoverableJsonNetworkService: DefaultJsonNetworkService { public typealias EndpointResponse = EndpointRecoverableRequestResult public typealias ErrorType = EndpointErrorResult public typealias RecoverableErrorType = ErrorCollection public typealias RequestRetrier = AnyEndpointRequestRetrier public private(set) var defaultRequestRetriers: [RequestRetrier] = [] open func process(recoverableRequest: EndpointRequest, prependRequestRetriers: [RequestRetrier] = [], appendRequestRetriers: [RequestRetrier] = [], completion: @escaping ParameterClosure>) -> Cancellable { process(recoverableRequest: recoverableRequest, errorHandlers: prependRequestRetriers + defaultRequestRetriers + appendRequestRetriers, completion: completion) } @available(iOS 13.0.0, *) open func process(recoverableRequest: EndpointRequest, prependRequestRetriers: [RequestRetrier] = [], appendRequestRetriers: [RequestRetrier] = []) async -> EndpointResponse { await withTaskCancellableClosure { process(recoverableRequest: recoverableRequest, prependRequestRetriers: prependRequestRetriers, appendRequestRetriers: appendRequestRetriers, completion: $0) } } open func process(recoverableRequest: EndpointRequest, errorHandlers: [RequestRetrier], completion: @escaping ParameterClosure>) -> Cancellable { Cancellables.scoped { cancellableBag in process(request: recoverableRequest) { [weak self] in self?.handle(recoverableResponse: $0, request: recoverableRequest, errorHandlers: errorHandlers, cancellableBag: cancellableBag, completion: completion) } } } @available(iOS 13.0.0, *) open func process(recoverableRequest: EndpointRequest, errorHandlers: [RequestRetrier]) async -> EndpointResponse { await withTaskCancellableClosure { process(recoverableRequest: recoverableRequest, errorHandlers: errorHandlers, completion: $0) } } open func handle(recoverableResponse: RequestResult, request: EndpointRequest, errorHandlers: [RequestRetrier], cancellableBag: BaseCancellableBag, completion: @escaping ParameterClosure>) { if case let .failure(errorResponse) = recoverableResponse { guard !cancellableBag.isCancelled else { return } Self.validateAndRepair(request: request, errors: [errorResponse], retriers: errorHandlers, cancellableBag: cancellableBag) { switch $0 { case .retry, .retryWithDelay: self.process(request: request) { completion($0.mapError { .init(failures: [$0]) }) } .add(to: cancellableBag) case .doNotRetry, .doNotRetryWithError: completion(recoverableResponse.mapError { .init(failures: [$0]) }) } } } else { completion(recoverableResponse.mapError { .init(failures: [$0]) }) } } public func register(defaultRequestRetrier: RequestRetrier) where RequestRetrier.ErrorResult == ErrorType { defaultRequestRetriers.append(defaultRequestRetrier.asAnyEndpointRequestRetrier()) } public func set(defaultRequestRetriers: RequestRetrier...) where RequestRetrier.ErrorResult == ErrorType { self.defaultRequestRetriers = defaultRequestRetriers.map { $0.asAnyEndpointRequestRetrier() } } private static func validateAndRepair(request: EndpointRequest, errors: [ErrorType], retriers: R, cancellableBag: BaseCancellableBag, completion: @escaping ParameterClosure) where R.Element == RequestRetrier { guard let retrier = retriers.first, !cancellableBag.isCancelled else { completion(.doNotRetry) return } retrier.validateAndRepair(errorResults: errors) { handlerResult in switch handlerResult { case let .success(retryResult): switch retryResult { case .retry, .retryWithDelay: completion(.retry) case .doNotRetry, .doNotRetryWithError: validateAndRepair(request: request, errors: errors, retriers: retriers.dropFirst(), cancellableBag: cancellableBag, completion: completion) } case let .failure(error): validateAndRepair(request: request, errors: errors + [error], retriers: retriers.dropFirst(), cancellableBag: cancellableBag, completion: completion) } } .add(to: cancellableBag) } }