Merge pull request #313 from TouchInstinct/fix/DefaultTokenInterceptor_adapt_check
feat: Asynchronous request preprocessing
This commit is contained in:
commit
7e319dcb03
|
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
### 1.22.0
|
||||
|
||||
- **Update**: Asynchronous request preprocessing
|
||||
|
||||
### 1.21.0
|
||||
|
||||
- **Update**: `AsyncEventHandler` was replaced with `EndpointRequestRetrier`
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = "LeadKit"
|
||||
s.version = "1.21.0"
|
||||
s.version = "1.22.0"
|
||||
s.summary = "iOS framework with a bunch of tools for rapid development"
|
||||
s.homepage = "https://github.com/TouchInstinct/LeadKit"
|
||||
s.license = "Apache License, Version 2.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIAppleMapUtils'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting using Apple MapKit.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIAuth'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Login, registration, confirmation and other related actions'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -29,3 +29,20 @@ public struct Cancellables {
|
|||
ScopeCancellable(scopeCancellableClosure: scopeCancellableClosure)
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0.0, *)
|
||||
public func withTaskCancellableClosure<T>(closure: (@escaping (T) -> Void) -> Cancellable) async -> T {
|
||||
let cancellableBag = BaseCancellableBag()
|
||||
|
||||
return await withTaskCancellationHandler(handler: {
|
||||
cancellableBag.cancel()
|
||||
}, operation: {
|
||||
await withCheckedContinuation { continuation in
|
||||
closure {
|
||||
continuation.resume(returning: $0)
|
||||
}
|
||||
.add(to: cancellableBag)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIFoundationUtils'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Set of helpers for Foundation framework classes.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIGoogleMapUtils'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting using Google Maps SDK.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIKeychainUtils'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Set of helpers for Keychain classes.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMapUtils'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -75,33 +75,40 @@ open class DefaultJsonNetworkService: ApiInteractor {
|
|||
completion: @escaping ParameterClosure<R>) -> TIFoundationUtils.Cancellable {
|
||||
|
||||
ScopeCancellable { [serializationQueue, callbackQueue, preprocessors] scope in
|
||||
let workItem = DispatchWorkItem {
|
||||
guard !scope.isCancelled else {
|
||||
return
|
||||
}
|
||||
Self.preprocess(request: request,
|
||||
preprocessors: preprocessors) {
|
||||
switch $0 {
|
||||
case let .success(preprocessedRequest):
|
||||
let workItem = DispatchWorkItem {
|
||||
guard !scope.isCancelled else {
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let preprocessedRequest = try preprocessors.reduce(request) {
|
||||
try $1.preprocess(request: $0)
|
||||
do {
|
||||
let serializedRequest = try self.serialize(request: preprocessedRequest)
|
||||
|
||||
self.process(request: serializedRequest,
|
||||
mapSuccess: mapSuccess,
|
||||
mapFailure: mapFailure,
|
||||
mapNetworkError: mapNetworkError,
|
||||
completion: completion)
|
||||
.add(to: scope)
|
||||
} catch {
|
||||
callbackQueue.async {
|
||||
completion(mapNetworkError(.encodableMapping(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let serializedRequest = try self.serialize(request: preprocessedRequest)
|
||||
workItem.add(to: scope)
|
||||
|
||||
scope.add(cancellable: self.process(request: serializedRequest,
|
||||
mapSuccess: mapSuccess,
|
||||
mapFailure: mapFailure,
|
||||
mapNetworkError: mapNetworkError,
|
||||
completion: completion))
|
||||
} catch {
|
||||
serializationQueue.async(execute: workItem)
|
||||
case let .failure(error):
|
||||
callbackQueue.async {
|
||||
completion(mapNetworkError(.encodableMapping(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serializationQueue.async(execute: workItem)
|
||||
|
||||
return workItem
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,4 +188,29 @@ open class DefaultJsonNetworkService: ApiInteractor {
|
|||
|
||||
preprocessors.append(securityPreprocessor)
|
||||
}
|
||||
|
||||
private static func preprocess<B,S,P: Collection>(request: EndpointRequest<B,S>,
|
||||
preprocessors: P,
|
||||
completion: @escaping (Result<EndpointRequest<B,S>, Error>) -> Void) -> TIFoundationUtils.Cancellable
|
||||
where P.Element == EndpointRequestPreprocessor {
|
||||
|
||||
guard let preprocessor = preprocessors.first else {
|
||||
completion(.success(request))
|
||||
return Cancellables.nonCancellable()
|
||||
}
|
||||
|
||||
return ScopeCancellable { scope in
|
||||
preprocessor.preprocess(request: request) {
|
||||
switch $0 {
|
||||
case let .success(modifiedRequest):
|
||||
preprocess(request: modifiedRequest,
|
||||
preprocessors: preprocessors.dropFirst(),
|
||||
completion: completion)
|
||||
.add(to: scope)
|
||||
case let .failure(error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIMoyaNetworking'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Moya + Swagger network service.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -49,21 +49,13 @@ public extension ApiInteractor {
|
|||
mapFailure: @escaping Closure<F, R>,
|
||||
mapNetworkError: @escaping Closure<NetworkError, R>) async -> R {
|
||||
|
||||
let cancellableBag = BaseCancellableBag()
|
||||
|
||||
return await withTaskCancellationHandler(handler: {
|
||||
cancellableBag.cancel()
|
||||
}, operation: {
|
||||
await withCheckedContinuation { continuation in
|
||||
process(request: request,
|
||||
mapSuccess: mapSuccess,
|
||||
mapFailure: mapFailure,
|
||||
mapNetworkError: mapNetworkError) {
|
||||
|
||||
continuation.resume(returning: $0)
|
||||
}
|
||||
.add(to: cancellableBag)
|
||||
await withTaskCancellableClosure { completion in
|
||||
process(request: request,
|
||||
mapSuccess: mapSuccess,
|
||||
mapFailure: mapFailure,
|
||||
mapNetworkError: mapNetworkError) {
|
||||
completion($0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,24 +25,24 @@ import Foundation
|
|||
import TIFoundationUtils
|
||||
|
||||
open class DefaultTokenInterceptor<RefreshError: Error>: RequestInterceptor {
|
||||
public typealias IsTokenInvalidClosure = (HTTPURLResponse?, Error?) -> Bool
|
||||
public typealias ShouldRefreshTokenClosure = (URLRequest?, HTTPURLResponse?, Error?) -> Bool
|
||||
public typealias RefreshTokenClosure = (@escaping (RefreshError?) -> Void) -> Cancellable
|
||||
|
||||
public typealias RequestModificationClosure = (URLRequest) -> URLRequest
|
||||
|
||||
let refreshLock = NSLock()
|
||||
|
||||
let isTokenInvalidClosure: IsTokenInvalidClosure
|
||||
let shouldRefreshToken: ShouldRefreshTokenClosure
|
||||
let refreshTokenClosure: RefreshTokenClosure
|
||||
|
||||
public var defaultRetryStrategy: RetryResult = .doNotRetry
|
||||
public var requestModificationClosure: RequestModificationClosure?
|
||||
|
||||
public init(isTokenInvalidClosure: @escaping IsTokenInvalidClosure,
|
||||
public init(isTokenInvalidClosure: @escaping ShouldRefreshTokenClosure,
|
||||
refreshTokenClosure: @escaping RefreshTokenClosure,
|
||||
requestModificationClosure: RequestModificationClosure? = nil) {
|
||||
|
||||
self.isTokenInvalidClosure = isTokenInvalidClosure
|
||||
self.shouldRefreshToken = isTokenInvalidClosure
|
||||
self.refreshTokenClosure = refreshTokenClosure
|
||||
self.requestModificationClosure = requestModificationClosure
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ open class DefaultTokenInterceptor<RefreshError: Error>: RequestInterceptor {
|
|||
|
||||
let modifiedRequest = requestModificationClosure?(urlRequest) ?? urlRequest
|
||||
|
||||
validateAndRepair(validationClosure: { true },
|
||||
validateAndRepair(validationClosure: { shouldRefreshToken(urlRequest, nil, nil) },
|
||||
completion: adaptCompletion,
|
||||
defaultCompletionResult: modifiedRequest,
|
||||
recoveredCompletionResult: modifiedRequest)
|
||||
|
|
@ -88,7 +88,7 @@ open class DefaultTokenInterceptor<RefreshError: Error>: RequestInterceptor {
|
|||
}
|
||||
}
|
||||
|
||||
validateAndRepair(validationClosure: { isTokenInvalidClosure(request.response, error) },
|
||||
validateAndRepair(validationClosure: { shouldRefreshToken(request.request, request.response, error) },
|
||||
completion: retryCompletion,
|
||||
defaultCompletionResult: defaultRetryStrategy,
|
||||
recoveredCompletionResult: .retry)
|
||||
|
|
|
|||
|
|
@ -35,17 +35,10 @@ public protocol EndpointRequestRetrier {
|
|||
@available(iOS 13.0.0, *)
|
||||
public extension EndpointRequestRetrier {
|
||||
func validateAndRepair(errorResults: [ErrorResult]) async -> EndpointRetryResult {
|
||||
let cancellableBag = BaseCancellableBag()
|
||||
|
||||
return await withTaskCancellationHandler(handler: {
|
||||
cancellableBag.cancel()
|
||||
}, operation: {
|
||||
await withCheckedContinuation { continuation in
|
||||
validateAndRepair(errorResults: errorResults) {
|
||||
continuation.resume(returning: $0)
|
||||
}
|
||||
.add(to: cancellableBag)
|
||||
await withTaskCancellableClosure { completion in
|
||||
validateAndRepair(errorResults: errorResults) {
|
||||
completion($0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ open class EndpointResponseTokenInterceptor<AE, NE>: DefaultTokenInterceptor<End
|
|||
|
||||
private let isTokenInvalidErrorResultClosure: IsTokenInvalidErrorResultClosure
|
||||
|
||||
public init(isTokenInvalidClosure: @escaping IsTokenInvalidClosure,
|
||||
public init(isTokenInvalidClosure: @escaping ShouldRefreshTokenClosure,
|
||||
refreshTokenClosure: @escaping RefreshTokenClosure,
|
||||
isTokenInvalidErrorResultClosure: @escaping IsTokenInvalidErrorResultClosure,
|
||||
requestModificationClosure: RequestModificationClosure? = nil) {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,12 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIFoundationUtils
|
||||
|
||||
open class DefaultSecuritySchemePreprocessor: SecuritySchemePreprocessor {
|
||||
struct ValueNotProvidedError: Error {}
|
||||
|
||||
public typealias ValueProvider = () -> String?
|
||||
public typealias ValueProvider = (@escaping (String?) -> Void) -> Cancellable
|
||||
|
||||
private let valueProvider: ValueProvider
|
||||
|
||||
|
|
@ -32,43 +34,52 @@ open class DefaultSecuritySchemePreprocessor: SecuritySchemePreprocessor {
|
|||
}
|
||||
|
||||
public init(staticValue: String?) {
|
||||
self.valueProvider = { staticValue }
|
||||
self.valueProvider = {
|
||||
$0(staticValue)
|
||||
return Cancellables.nonCancellable()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - EndpointSecurityRequestPreprocessor
|
||||
|
||||
public func preprocess<B, S>(request: EndpointRequest<B, S>, using security: SecurityScheme) throws -> EndpointRequest<B, S> {
|
||||
public func preprocess<B,S>(request: EndpointRequest<B,S>,
|
||||
using security: SecurityScheme,
|
||||
completion: @escaping (Result<EndpointRequest<B,S>, Error>) -> Void) -> Cancellable {
|
||||
|
||||
var modifiedRequest = request
|
||||
|
||||
guard let value = valueProvider() else {
|
||||
throw ValueNotProvidedError()
|
||||
}
|
||||
return valueProvider {
|
||||
guard let value = $0 else {
|
||||
completion(.failure(ValueNotProvidedError()))
|
||||
return
|
||||
}
|
||||
|
||||
switch security {
|
||||
case let .http(authenticationScheme):
|
||||
let headerValue = "\(authenticationScheme.rawValue) \(value)"
|
||||
var headerParameters = modifiedRequest.headerParameters ?? [:]
|
||||
headerParameters.updateValue(.init(value: headerValue),
|
||||
forKey: "Authorization")
|
||||
|
||||
modifiedRequest.headerParameters = headerParameters
|
||||
case let .apiKey(parameterLocation, parameterName):
|
||||
switch parameterLocation {
|
||||
case .header:
|
||||
switch security {
|
||||
case let .http(authenticationScheme):
|
||||
let headerValue = "\(authenticationScheme.rawValue) \(value)"
|
||||
var headerParameters = modifiedRequest.headerParameters ?? [:]
|
||||
headerParameters.updateValue(.init(value: value),
|
||||
forKey: parameterName)
|
||||
headerParameters.updateValue(.init(value: headerValue),
|
||||
forKey: "Authorization")
|
||||
|
||||
modifiedRequest.headerParameters = headerParameters
|
||||
case .query:
|
||||
modifiedRequest.queryParameters.updateValue(.init(value: value),
|
||||
forKey: parameterName)
|
||||
case .cookie:
|
||||
modifiedRequest.cookieParameters.updateValue(.init(value: value),
|
||||
forKey: parameterName)
|
||||
}
|
||||
}
|
||||
case let .apiKey(parameterLocation, parameterName):
|
||||
switch parameterLocation {
|
||||
case .header:
|
||||
var headerParameters = modifiedRequest.headerParameters ?? [:]
|
||||
headerParameters.updateValue(.init(value: value),
|
||||
forKey: parameterName)
|
||||
|
||||
return modifiedRequest
|
||||
modifiedRequest.headerParameters = headerParameters
|
||||
case .query:
|
||||
modifiedRequest.queryParameters.updateValue(.init(value: value),
|
||||
forKey: parameterName)
|
||||
case .cookie:
|
||||
modifiedRequest.cookieParameters.updateValue(.init(value: value),
|
||||
forKey: parameterName)
|
||||
}
|
||||
}
|
||||
|
||||
completion(.success(modifiedRequest))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIFoundationUtils
|
||||
|
||||
open class DefaultEndpointSecurityPreprocessor: EndpointRequestPreprocessor {
|
||||
enum PreprocessError: Error {
|
||||
case missingSecurityScheme(String, [String: SecurityScheme])
|
||||
|
|
@ -36,46 +38,120 @@ open class DefaultEndpointSecurityPreprocessor: EndpointRequestPreprocessor {
|
|||
self.schemePreprocessors = schemePreprocessors
|
||||
}
|
||||
|
||||
public func preprocess<B, S>(request: EndpointRequest<B, S>) throws -> EndpointRequest<B, S> {
|
||||
public func preprocess<B,S>(request: EndpointRequest<B,S>,
|
||||
completion: @escaping (Result<EndpointRequest<B,S>, Error>) -> Void) -> Cancellable {
|
||||
|
||||
guard !request.security.compactMap({ $0 }).isEmpty else {
|
||||
return request
|
||||
completion(.success(request))
|
||||
return Cancellables.nonCancellable()
|
||||
}
|
||||
|
||||
let endpointSchemes: [[KeyValueTuple<String, SecurityScheme>]] = try request.security.map {
|
||||
try $0.map {
|
||||
guard let securityScheme = openApi.security[$0] else {
|
||||
throw PreprocessError.missingSecurityScheme($0, openApi.security)
|
||||
}
|
||||
do {
|
||||
let endpointSchemes: [[KeyValueTuple<String, SecurityScheme>]] = try request.security.map {
|
||||
try $0.map {
|
||||
guard let securityScheme = openApi.security[$0] else {
|
||||
throw PreprocessError.missingSecurityScheme($0, openApi.security)
|
||||
}
|
||||
|
||||
return ($0, securityScheme)
|
||||
return ($0, securityScheme)
|
||||
}
|
||||
}
|
||||
|
||||
return Self.preprocess(request: request,
|
||||
using: endpointSchemes,
|
||||
schemePreprocessors: schemePreprocessors) { [schemePreprocessors] in
|
||||
switch $0 {
|
||||
case let .success(modifiedRequest):
|
||||
completion(.success(modifiedRequest))
|
||||
case let .failure(error):
|
||||
completion(.failure(PreprocessError.unableToSatisfyRequirements(anyOfRequired: request.security,
|
||||
registeredPreprocessors: schemePreprocessors)))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
return Cancellables.nonCancellable()
|
||||
}
|
||||
|
||||
for schemeGroup in endpointSchemes {
|
||||
let preprocessorsGroup: [KeyValueTuple<SecurityScheme, SecuritySchemePreprocessor>] = schemeGroup.compactMap {
|
||||
guard let preprocessor = schemePreprocessors[$0.key] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ($0.value, preprocessor)
|
||||
}
|
||||
|
||||
guard preprocessorsGroup.count == schemeGroup.count else {
|
||||
continue // unable to satisfy group requirement
|
||||
}
|
||||
|
||||
do {
|
||||
return try preprocessorsGroup.reduce(request) {
|
||||
try $1.value.preprocess(request: $0, using: $1.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw PreprocessError.unableToSatisfyRequirements(anyOfRequired: request.security,
|
||||
registeredPreprocessors: schemePreprocessors)
|
||||
}
|
||||
|
||||
public func register(preprocessor: SecuritySchemePreprocessor, for scheme: String) {
|
||||
schemePreprocessors[scheme] = preprocessor
|
||||
}
|
||||
|
||||
private static func preprocess<B,S,SC: Collection>(request: EndpointRequest<B,S>,
|
||||
using schemes: SC,
|
||||
schemePreprocessors: [String: SecuritySchemePreprocessor],
|
||||
completion: @escaping (Result<EndpointRequest<B,S>, Error>) -> Void) -> Cancellable
|
||||
where SC.Element == [KeyValueTuple<String, SecurityScheme>] {
|
||||
|
||||
guard let schemeGroup = schemes.first else {
|
||||
completion(.success(request))
|
||||
return Cancellables.nonCancellable()
|
||||
}
|
||||
|
||||
let preprocessorsGroup: [KeyValueTuple<SecurityScheme, SecuritySchemePreprocessor>] = schemeGroup.compactMap {
|
||||
guard let preprocessor = schemePreprocessors[$0.key] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ($0.value, preprocessor)
|
||||
}
|
||||
|
||||
guard preprocessorsGroup.count == schemeGroup.count else {
|
||||
// unable to satisfy group requirement
|
||||
// try next scheme group
|
||||
return preprocess(request: request,
|
||||
using: schemes.dropFirst(),
|
||||
schemePreprocessors: schemePreprocessors,
|
||||
completion: completion)
|
||||
}
|
||||
|
||||
return ScopeCancellable { scope in
|
||||
preprocess(request: request,
|
||||
with: preprocessorsGroup) {
|
||||
|
||||
switch $0 {
|
||||
case let .success(modifiedRequest):
|
||||
completion(.success(modifiedRequest))
|
||||
case let .failure(error):
|
||||
guard !schemes.isEmpty else {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
preprocess(request: request,
|
||||
using: schemes.dropFirst(),
|
||||
schemePreprocessors: schemePreprocessors,
|
||||
completion: completion)
|
||||
.add(to: scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func preprocess<B,S,G: Collection>(request: EndpointRequest<B,S>,
|
||||
with groups: G,
|
||||
completion: @escaping (Result<EndpointRequest<B,S>, Error>) -> Void) -> Cancellable
|
||||
where G.Element == KeyValueTuple<SecurityScheme, SecuritySchemePreprocessor> {
|
||||
|
||||
guard let group = groups.first else {
|
||||
completion(.success(request))
|
||||
return Cancellables.nonCancellable()
|
||||
}
|
||||
|
||||
return ScopeCancellable { scope in
|
||||
group.value.preprocess(request: request,
|
||||
using: group.key) {
|
||||
switch $0 {
|
||||
case let .success(modifiedRequest):
|
||||
preprocess(request: modifiedRequest,
|
||||
with: groups.dropFirst(),
|
||||
completion: completion)
|
||||
.add(to: scope)
|
||||
case let .failure(error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,20 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIFoundationUtils
|
||||
|
||||
public protocol EndpointRequestPreprocessor {
|
||||
func preprocess<B,S>(request: EndpointRequest<B,S>) throws -> EndpointRequest<B,S>
|
||||
func preprocess<B,S>(request: EndpointRequest<B,S>,
|
||||
completion: @escaping (Result<EndpointRequest<B,S>, Error>) -> Void) -> Cancellable
|
||||
}
|
||||
|
||||
@available(iOS 13.0.0, *)
|
||||
public extension EndpointRequestPreprocessor {
|
||||
func preprocess<B,S>(request: EndpointRequest<B,S>) async -> Result<EndpointRequest<B,S>, Error> {
|
||||
await withTaskCancellableClosure { completion in
|
||||
preprocess(request: request) {
|
||||
completion($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,21 @@
|
|||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import TIFoundationUtils
|
||||
|
||||
public protocol SecuritySchemePreprocessor {
|
||||
func preprocess<B,S>(request: EndpointRequest<B,S>, using security: SecurityScheme) throws -> EndpointRequest<B,S>
|
||||
func preprocess<B,S>(request: EndpointRequest<B,S>,
|
||||
using security: SecurityScheme,
|
||||
completion: @escaping (Result<EndpointRequest<B,S>, Error>) -> Void) -> Cancellable
|
||||
}
|
||||
|
||||
|
||||
@available(iOS 13.0.0, *)
|
||||
public extension SecuritySchemePreprocessor {
|
||||
func preprocess<B,S>(request: EndpointRequest<B,S>, using security: SecurityScheme) async -> Result<EndpointRequest<B,S>, Error> {
|
||||
await withTaskCancellableClosure { completion in
|
||||
preprocess(request: request, using: security) {
|
||||
completion($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TINetworking'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Swagger-frendly networking layer helpers.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TINetworkingCache'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Caching results of EndpointRequests.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIPagination'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Generic pagination component.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TISwiftUICore'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Core UI elements: protocols, views and helpers..'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TISwiftUtils'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Bunch of useful helpers for Swift development.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TITableKitUtils'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Set of helpers for TableKit classes.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TITransitions'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Set of custom transitions to present controller. '
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIElements'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Bunch of useful protocols and views.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIUIKitCore'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Core UI elements: protocols, views and helpers.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Pod::Spec.new do |s|
|
||||
s.name = 'TIYandexMapUtils'
|
||||
s.version = '1.21.0'
|
||||
s.version = '1.22.0'
|
||||
s.summary = 'Set of helpers for map objects clustering and interacting using Yandex Maps SDK.'
|
||||
s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
|
|
|
|||
Loading…
Reference in New Issue