forked from loafle/openapi-generator-original
[swift6] urlsession interceptor (#19797)
* [swift6] alamofire interceptor * [swift6] alamofire interceptor * [swift6] urlsession interceptor * [swift6] urlsession interceptor * [swift6] urlsession interceptor
This commit is contained in:
parent
4c81563708
commit
9a0fc5900f
@ -30,8 +30,12 @@ import Alamofire{{/useAlamofire}}
|
||||
/// Configures the range of HTTP status codes that will result in a successful response
|
||||
///
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var successfulStatusCodeRange: Range<Int>{{#useAlamofire}}
|
||||
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var successfulStatusCodeRange: Range<Int>{{#useURLSession}}
|
||||
|
||||
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var interceptor: OpenAPIInterceptor{{/useURLSession}}{{#useAlamofire}}
|
||||
|
||||
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var interceptor: RequestInterceptor?
|
||||
|
||||
/// ResponseSerializer that will be used by the generator for `Data` responses
|
||||
///
|
||||
/// If unchanged, Alamofires default `DataResponseSerializer` will be used.
|
||||
@ -52,7 +56,8 @@ import Alamofire{{/useAlamofire}}
|
||||
requestBuilderFactory: RequestBuilderFactory = {{#useAlamofire}}AlamofireRequestBuilderFactory(){{/useAlamofire}}{{#useURLSession}}URLSessionRequestBuilderFactory(){{/useURLSession}},
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300{{#useAlamofire}},
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300{{#useURLSession}},
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor(){{/useURLSession}}{{#useAlamofire}},
|
||||
interceptor: RequestInterceptor? = nil,
|
||||
dataResponseSerializer: AnyResponseSerializer<Data> = AnyResponseSerializer(DataResponseSerializer()),
|
||||
stringResponseSerializer: AnyResponseSerializer<String> = AnyResponseSerializer(StringResponseSerializer()){{/useAlamofire}}{{/useVapor}}
|
||||
@ -67,7 +72,8 @@ import Alamofire{{/useAlamofire}}
|
||||
self.requestBuilderFactory = requestBuilderFactory
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange{{#useAlamofire}}
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange{{#useURLSession}}
|
||||
self.interceptor = interceptor{{/useURLSession}}{{#useAlamofire}}
|
||||
self.interceptor = interceptor
|
||||
self.dataResponseSerializer = dataResponseSerializer
|
||||
self.stringResponseSerializer = stringResponseSerializer{{/useAlamofire}}{{/useVapor}}
|
||||
|
@ -25,7 +25,7 @@ import UniformTypeIdentifiers
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol URLSessionProtocol {
|
||||
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
///
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: RequestInterceptor?
|
||||
|
||||
/// ResponseSerializer that will be used by the generator for `Data` responses
|
||||
///
|
||||
/// If unchanged, Alamofires default `DataResponseSerializer` will be used.
|
||||
|
@ -22,7 +22,9 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
///
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: RequestInterceptor?
|
||||
|
||||
/// ResponseSerializer that will be used by the generator for `Data` responses
|
||||
///
|
||||
/// If unchanged, Alamofires default `DataResponseSerializer` will be used.
|
||||
|
@ -22,6 +22,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://petstore.swagger.io:80/v2",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://petstore.swagger.io:80/v2",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://petstore.swagger.io:80/v2",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://petstore.swagger.io:80/v2",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://petstore.swagger.io:80/v2",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://localhost",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://petstore.swagger.io:80/v2",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ internal class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
internal var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
internal var interceptor: OpenAPIInterceptor
|
||||
|
||||
internal init(
|
||||
basePath: String = "http://petstore.swagger.io:80/v2",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ internal class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ internal class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
internal static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ internal protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
internal protocol URLSessionProtocol {
|
||||
internal protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ internal class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendab
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
internal enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
internal protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
internal class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://petstore.swagger.io:80/v2",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://petstore.swagger.io:80/v2",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -31,7 +33,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -40,6 +43,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */; };
|
||||
6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */; };
|
||||
6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */; };
|
||||
A5465867259E09C600C3929B /* BearerDecodableRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5465866259E09C600C3929B /* BearerDecodableRequestBuilder.swift */; };
|
||||
A5465867259E09C600C3929B /* BearerTokenHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5465866259E09C600C3929B /* BearerTokenHandler.swift */; };
|
||||
A5782C772664FBA800CAA106 /* PetstoreClient in Frameworks */ = {isa = PBXBuildFile; productRef = A5782C762664FBA800CAA106 /* PetstoreClient */; };
|
||||
A5EA12642419439700E30FC3 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12622419439700E30FC3 /* FileUtils.swift */; };
|
||||
A5EA12652419439700E30FC3 /* UIImage+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12632419439700E30FC3 /* UIImage+Extras.swift */; };
|
||||
@ -46,7 +46,7 @@
|
||||
6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PetAPITests.swift; sourceTree = "<group>"; };
|
||||
6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreAPITests.swift; sourceTree = "<group>"; };
|
||||
6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAPITests.swift; sourceTree = "<group>"; };
|
||||
A5465866259E09C600C3929B /* BearerDecodableRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BearerDecodableRequestBuilder.swift; sourceTree = "<group>"; };
|
||||
A5465866259E09C600C3929B /* BearerTokenHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BearerTokenHandler.swift; sourceTree = "<group>"; };
|
||||
A5EA12622419439700E30FC3 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
|
||||
A5EA12632419439700E30FC3 /* UIImage+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extras.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@ -101,7 +101,7 @@
|
||||
children = (
|
||||
6D4EFB941C692C6300B96B06 /* AppDelegate.swift */,
|
||||
6D4EFB961C692C6300B96B06 /* ViewController.swift */,
|
||||
A5465866259E09C600C3929B /* BearerDecodableRequestBuilder.swift */,
|
||||
A5465866259E09C600C3929B /* BearerTokenHandler.swift */,
|
||||
6D4EFB981C692C6300B96B06 /* Main.storyboard */,
|
||||
6D4EFB9B1C692C6300B96B06 /* Assets.xcassets */,
|
||||
6D4EFB9D1C692C6300B96B06 /* LaunchScreen.storyboard */,
|
||||
@ -231,7 +231,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6D4EFB971C692C6300B96B06 /* ViewController.swift in Sources */,
|
||||
A5465867259E09C600C3929B /* BearerDecodableRequestBuilder.swift in Sources */,
|
||||
A5465867259E09C600C3929B /* BearerTokenHandler.swift in Sources */,
|
||||
6D4EFB951C692C6300B96B06 /* AppDelegate.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -17,8 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
|
||||
// Customize requestBuilderFactory
|
||||
OpenAPIClient.shared.requestBuilderFactory = BearerRequestBuilderFactory()
|
||||
OpenAPIClient.shared.interceptor = BearerOpenAPIInterceptor()
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -1,171 +0,0 @@
|
||||
//
|
||||
// BearerDecodableRequestBuilder.swift
|
||||
// SwaggerClient
|
||||
//
|
||||
// Created by Bruno Coelho on 31/12/2020.
|
||||
// Copyright © 2020 Swagger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PetstoreClient
|
||||
|
||||
class BearerRequestBuilderFactory: RequestBuilderFactory {
|
||||
func getNonDecodableBuilder<T>() -> RequestBuilder<T>.Type {
|
||||
BearerRequestBuilder<T>.self
|
||||
}
|
||||
|
||||
func getBuilder<T: Decodable>() -> RequestBuilder<T>.Type {
|
||||
BearerDecodableRequestBuilder<T>.self
|
||||
}
|
||||
}
|
||||
|
||||
class BearerRequestBuilder<T>: URLSessionRequestBuilder<T>, @unchecked Sendable {
|
||||
|
||||
@discardableResult
|
||||
override func execute(completion: @Sendable @escaping (Result<Response<T>, ErrorResponse>) -> Void) -> RequestTask {
|
||||
|
||||
guard self.requiresAuthentication else {
|
||||
return super.execute(completion: completion)
|
||||
}
|
||||
|
||||
// Before making the request, we can validate if we have a bearer token to be able to make a request
|
||||
BearerTokenHandler.shared.refreshTokenIfDoesntExist { token in
|
||||
|
||||
self.addHeaders(["Authorization": "Bearer \(token)"])
|
||||
|
||||
// Here we make the request
|
||||
super.execute { result in
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
// If we got a successful response, we send the response to the completion block
|
||||
completion(result)
|
||||
|
||||
case let .failure(error):
|
||||
|
||||
// If we got a failure response, we will analyse the error to see what we should do with it
|
||||
if case let ErrorResponse.error(_, data, response, error) = error {
|
||||
|
||||
// If the error is an ErrorResponse.error() we will analyse it to see if it's a 401, and if it's a 401, we will refresh the token and retry the request
|
||||
BearerTokenHandler.shared.refreshTokenIfUnauthorizedRequestResponse(
|
||||
data: data,
|
||||
response: response,
|
||||
error: error
|
||||
) { (wasTokenRefreshed, newToken) in
|
||||
|
||||
if wasTokenRefreshed, let newToken = newToken {
|
||||
|
||||
// If the token was refreshed, it's because it was a 401 error, so we refreshed the token, and we are going to retry the request by calling self.execute()
|
||||
self.addHeaders(["Authorization": "Bearer \(newToken)"])
|
||||
self.execute(completion: completion)
|
||||
} else {
|
||||
// If the token was not refreshed, it's because it was not a 401 error, so we send the response to the completion block
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If it's an unknown error, we send the response to the completion block
|
||||
completion(result)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestTask
|
||||
}
|
||||
}
|
||||
|
||||
class BearerDecodableRequestBuilder<T: Decodable>: URLSessionDecodableRequestBuilder<T>, @unchecked Sendable {
|
||||
|
||||
@discardableResult
|
||||
override func execute(completion: @Sendable @escaping (Result<Response<T>, ErrorResponse>) -> Void) -> RequestTask {
|
||||
|
||||
guard self.requiresAuthentication else {
|
||||
return super.execute(completion: completion)
|
||||
}
|
||||
|
||||
// Before making the request, we can validate if we have a bearer token to be able to make a request
|
||||
BearerTokenHandler.shared.refreshTokenIfDoesntExist { token in
|
||||
|
||||
self.addHeaders(["Authorization": "Bearer \(token)"])
|
||||
|
||||
// Here we make the request
|
||||
super.execute { result in
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
// If we got a successful response, we send the response to the completion block
|
||||
completion(result)
|
||||
|
||||
case let .failure(error):
|
||||
|
||||
// If we got a failure response, we will analyse the error to see what we should do with it
|
||||
if case let ErrorResponse.error(_, data, response, error) = error {
|
||||
|
||||
// If the error is an ErrorResponse.error() we will analyse it to see if it's a 401, and if it's a 401, we will refresh the token and retry the request
|
||||
BearerTokenHandler.shared.refreshTokenIfUnauthorizedRequestResponse(
|
||||
data: data,
|
||||
response: response,
|
||||
error: error
|
||||
) { (wasTokenRefreshed, newToken) in
|
||||
|
||||
if wasTokenRefreshed, let newToken = newToken {
|
||||
|
||||
// If the token was refreshed, it's because it was a 401 error, so we refreshed the token, and we are going to retry the request by calling self.execute()
|
||||
self.addHeaders(["Authorization": "Bearer \(newToken)"])
|
||||
self.execute(completion: completion)
|
||||
} else {
|
||||
// If the token was not refreshed, it's because it was not a 401 error, so we send the response to the completion block
|
||||
completion(result)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If it's an unknown error, we send the response to the completion block
|
||||
completion(result)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return requestTask
|
||||
}
|
||||
}
|
||||
|
||||
class BearerTokenHandler: @unchecked Sendable {
|
||||
private init() {}
|
||||
static let shared = BearerTokenHandler()
|
||||
|
||||
|
||||
private var bearerToken: String? = nil
|
||||
|
||||
func refreshTokenIfDoesntExist(completionHandler: @escaping (String) -> Void) {
|
||||
if let bearerToken = bearerToken {
|
||||
completionHandler(bearerToken)
|
||||
} else {
|
||||
startRefreshingToken { token in
|
||||
completionHandler(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshTokenIfUnauthorizedRequestResponse(data: Data?, response: URLResponse?, error: Error?, completionHandler: @escaping (Bool, String?) -> Void) {
|
||||
if let response = response as? HTTPURLResponse, response.statusCode == 401 {
|
||||
startRefreshingToken { token in
|
||||
completionHandler(true, token)
|
||||
}
|
||||
} else {
|
||||
completionHandler(false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func startRefreshingToken(completionHandler: @escaping (String) -> Void) {
|
||||
// Get a bearer token
|
||||
let dummyBearerToken = "..."
|
||||
|
||||
bearerToken = dummyBearerToken
|
||||
|
||||
completionHandler(dummyBearerToken)
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
//
|
||||
// BearerTokenHandler.swift
|
||||
// SwaggerClient
|
||||
//
|
||||
// Created by Bruno Coelho on 31/12/2020.
|
||||
// Copyright © 2020 Swagger. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PetstoreClient
|
||||
|
||||
public class BearerOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
BearerTokenHandler.shared.refreshTokenIfDoesntExist { token in
|
||||
|
||||
// Change the current url request
|
||||
var newUrlRequest = urlRequest
|
||||
newUrlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
||||
|
||||
// Change the global headers
|
||||
openAPIClient.customHeaders["Authorization"] = "Bearer \(token)"
|
||||
|
||||
completion(.success(newUrlRequest))
|
||||
}
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
// We will analyse the response to see if it's a 401, and if it's a 401, we will refresh the token and retry the request
|
||||
BearerTokenHandler.shared.refreshTokenIfUnauthorizedRequestResponse(
|
||||
data: data,
|
||||
response: response,
|
||||
error: error
|
||||
) { (wasTokenRefreshed, newToken) in
|
||||
|
||||
if wasTokenRefreshed, let newToken = newToken {
|
||||
|
||||
// Change the global headers
|
||||
openAPIClient.customHeaders["Authorization"] = "Bearer \(newToken)"
|
||||
|
||||
completion(.retry)
|
||||
} else {
|
||||
// If the token was not refreshed, it's because it was not a 401 error, so we send the response to the completion block
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BearerTokenHandler: @unchecked Sendable {
|
||||
private init() {}
|
||||
static let shared = BearerTokenHandler()
|
||||
|
||||
|
||||
private var bearerToken: String? = nil
|
||||
|
||||
func refreshTokenIfDoesntExist(completionHandler: @escaping (String) -> Void) {
|
||||
if let bearerToken = bearerToken {
|
||||
completionHandler(bearerToken)
|
||||
} else {
|
||||
startRefreshingToken { token in
|
||||
completionHandler(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshTokenIfUnauthorizedRequestResponse(data: Data?, response: URLResponse, error: Error, completionHandler: @escaping (Bool, String?) -> Void) {
|
||||
if let response = response as? HTTPURLResponse, response.statusCode == 401 {
|
||||
startRefreshingToken { token in
|
||||
completionHandler(true, token)
|
||||
}
|
||||
} else {
|
||||
completionHandler(false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func startRefreshingToken(completionHandler: @escaping (String) -> Void) {
|
||||
// Get a bearer token
|
||||
let dummyBearerToken = "..."
|
||||
|
||||
bearerToken = dummyBearerToken
|
||||
|
||||
completionHandler(dummyBearerToken)
|
||||
}
|
||||
}
|
@ -22,6 +22,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
/// If a HTTP status code is outside of this range the response will be interpreted as failed.
|
||||
public var successfulStatusCodeRange: Range<Int>
|
||||
|
||||
public var interceptor: OpenAPIInterceptor
|
||||
|
||||
public init(
|
||||
basePath: String = "http://localhost",
|
||||
customHeaders: [String: String] = [:],
|
||||
@ -29,7 +31,8 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(),
|
||||
apiResponseQueue: DispatchQueue = .main,
|
||||
codableHelper: CodableHelper = CodableHelper(),
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300
|
||||
successfulStatusCodeRange: Range<Int> = 200..<300,
|
||||
interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor()
|
||||
) {
|
||||
self.basePath = basePath
|
||||
self.customHeaders = customHeaders
|
||||
@ -38,6 +41,7 @@ open class OpenAPIClient: @unchecked Sendable {
|
||||
self.apiResponseQueue = apiResponseQueue
|
||||
self.codableHelper = codableHelper
|
||||
self.successfulStatusCodeRange = successfulStatusCodeRange
|
||||
self.interceptor = interceptor
|
||||
}
|
||||
|
||||
public static let shared = OpenAPIClient()
|
||||
|
@ -25,7 +25,7 @@ public protocol URLSessionDataTaskProtocol {
|
||||
}
|
||||
|
||||
// Protocol allowing implementations to alter what is returned or to test their implementations.
|
||||
public protocol URLSessionProtocol {
|
||||
public protocol URLSessionProtocol: Sendable {
|
||||
// Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request
|
||||
// is sent off when `.resume()` is called.
|
||||
func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol
|
||||
@ -160,21 +160,46 @@ open class URLSessionRequestBuilder<T>: RequestBuilder<T>, @unchecked Sendable {
|
||||
do {
|
||||
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
|
||||
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
openAPIClient.interceptor.intercept(urlRequest: request, urlSession: urlSession, openAPIClient: openAPIClient) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let modifiedRequest):
|
||||
let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in
|
||||
self.cleanupRequest()
|
||||
if let response, let error {
|
||||
self.openAPIClient.interceptor.retry(urlRequest: modifiedRequest, urlSession: urlSession, openAPIClient: self.openAPIClient, data: data, response: response, error: error) { retry in
|
||||
switch retry {
|
||||
case .retry:
|
||||
self.execute(completion: completion)
|
||||
|
||||
case .dontRetry:
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = self.taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential
|
||||
|
||||
self.requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
|
||||
case .failure(let error):
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onProgressReady?(dataTask.progress)
|
||||
|
||||
URLSessionRequestBuilderConfiguration.shared.challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge
|
||||
URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = credential
|
||||
|
||||
requestTask.set(task: dataTask)
|
||||
|
||||
dataTask.resume()
|
||||
} catch {
|
||||
self.openAPIClient.apiResponseQueue.async {
|
||||
completion(.failure(ErrorResponse.error(415, nil, nil, error)))
|
||||
@ -676,3 +701,26 @@ private extension Optional where Wrapped == Data {
|
||||
}
|
||||
|
||||
extension JSONDataEncoding: ParameterEncoding {}
|
||||
|
||||
public enum OpenAPIInterceptorRetry {
|
||||
case retry
|
||||
case dontRetry
|
||||
}
|
||||
|
||||
public protocol OpenAPIInterceptor {
|
||||
func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, Error>) -> Void)
|
||||
|
||||
func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
|
||||
}
|
||||
|
||||
public class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
|
||||
public init() {}
|
||||
|
||||
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
|
||||
completion(.success(urlRequest))
|
||||
}
|
||||
|
||||
public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
|
||||
completion(.dontRetry)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user