Skip to content

Commit

Permalink
[swift6] improve retry interceptor (OpenAPITools#19988)
Browse files Browse the repository at this point in the history
* [swift6] improve retry interceptor

* [swift6] improve retry interceptor

* [swift6] improve retry interceptor
  • Loading branch information
4brunu authored Oct 30, 2024
1 parent 9452873 commit e9ea12f
Show file tree
Hide file tree
Showing 14 changed files with 734 additions and 631 deletions.
17 changes: 12 additions & 5 deletions docs/faq-generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,21 +350,28 @@ First you implement the `OpenAPIInterceptor` protocol.
public class BearerOpenAPIInterceptor: OpenAPIInterceptor {
public init() {}
public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, openAPIClient: OpenAPIClient, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
public func intercept<T>(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder<T>, completion: @escaping (Result<URLRequest, any Error>) -> Void) {
guard requestBuilder.requiresAuthentication else {
// no authentication required
completion(.success(urlRequest))
return
}
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)"
requestBuilder.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) {
public func retry<T>(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder<T>, 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
refreshTokenIfUnauthorizedRequestResponse(
data: data,
Expand All @@ -375,7 +382,7 @@ public class BearerOpenAPIInterceptor: OpenAPIInterceptor {
if wasTokenRefreshed, let newToken = newToken {
// Change the global headers
openAPIClient.customHeaders["Authorization"] = "Bearer \(newToken)"
requestBuilder.openAPIClient.customHeaders["Authorization"] = "Bearer \(newToken)"
completion(.retry)
} else {
Expand All @@ -397,7 +404,7 @@ public class BearerOpenAPIInterceptor: OpenAPIInterceptor {
}
}
func refreshTokenIfUnauthorizedRequestResponse(data: Data?, response: URLResponse, error: Error, completionHandler: @escaping (Bool, String?) -> Void) {
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,24 +155,48 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable {
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, requestBuilder: self, 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.cleanupRequest()
if let error = error {
self.retryRequest(
urlRequest: modifiedRequest,
urlSession: urlSession,
statusCode: -1,
data: data,
response: response,
error: error,
completion: completion
)
return
}

guard let httpResponse = response as? HTTPURLResponse else {
self.retryRequest(
urlRequest: modifiedRequest,
urlSession: urlSession,
statusCode: -2,
data: data,
response: response,
error: DecodableRequestBuilderError.nilHTTPResponse,
completion: completion
)
return
}

guard self.openAPIClient.successfulStatusCodeRange.contains(httpResponse.statusCode) else {
self.retryRequest(
urlRequest: modifiedRequest,
urlSession: urlSession,
statusCode: httpResponse.statusCode,
data: data,
response: httpResponse,
error: DecodableRequestBuilderError.unsuccessfulHTTPStatusCode,
completion: completion
)
return
}

self.processRequestResponse(urlRequest: request, data: data, httpResponse: httpResponse, error: error, completion: completion)
}

self.onProgressReady?(dataTask.progress)
Expand Down Expand Up @@ -204,22 +228,21 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable {
}
}

fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, response: URLResponse?, error: Error?, completion: @escaping (_ result: Swift.Result<Response<T>, ErrorResponse>) -> Void) {
private func retryRequest(urlRequest: URLRequest, urlSession: URLSessionProtocol, statusCode: Int, data: Data?, response: URLResponse?, error: Error, completion: @Sendable @escaping (_ result: Swift.Result<Response<T>, ErrorResponse>) -> Void) {
self.openAPIClient.interceptor.retry(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: response, error: error) { retry in
switch retry {
case .retry:
self.execute(completion: completion)
if let error = error {
completion(.failure(ErrorResponse.error(-1, data, response, error)))
return
}

guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(ErrorResponse.error(-2, data, response, DecodableRequestBuilderError.nilHTTPResponse)))
return
case .dontRetry:
self.openAPIClient.apiResponseQueue.async {
completion(.failure(ErrorResponse.error(statusCode, data, response, error)))
}
}
}
}

guard openAPIClient.successfulStatusCodeRange.contains(httpResponse.statusCode) else {
completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, response, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode)))
return
}
fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, httpResponse: HTTPURLResponse, error: Error?, completion: @escaping (_ result: Swift.Result<Response<T>, ErrorResponse>) -> Void) {
switch T.self {
case is Void.Type:
Expand Down Expand Up @@ -297,22 +320,7 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable {
}

{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionDecodableRequestBuilder<T: Decodable>: URLSessionRequestBuilder<T>, @unchecked Sendable {
override fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, response: URLResponse?, error: Error?, completion: @escaping (_ result: Swift.Result<Response<T>, ErrorResponse>) -> Void) {
if let error = error {
completion(.failure(ErrorResponse.error(-1, data, response, error)))
return
}

guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(ErrorResponse.error(-2, data, response, DecodableRequestBuilderError.nilHTTPResponse)))
return
}

guard openAPIClient.successfulStatusCodeRange.contains(httpResponse.statusCode) else {
completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, response, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode)))
return
}
override fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, httpResponse: HTTPURLResponse, error: Error?, completion: @escaping (_ result: Swift.Result<Response<T>, ErrorResponse>) -> Void) {
switch T.self {
case is String.Type:
Expand Down Expand Up @@ -353,9 +361,9 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable {
completion(.success(Response(response: httpResponse, body: filePath as! T, bodyData: data)))

} catch let requestParserError as DownloadException {
completion(.failure(ErrorResponse.error(400, data, response, requestParserError)))
completion(.failure(ErrorResponse.error(400, data, httpResponse, requestParserError)))
} catch {
completion(.failure(ErrorResponse.error(400, data, response, error)))
completion(.failure(ErrorResponse.error(400, data, httpResponse, error)))
}

case is Void.Type:
Expand All @@ -372,7 +380,7 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable {
if let expressibleByNilLiteralType = T.self as? ExpressibleByNilLiteral.Type {
completion(.success(Response(response: httpResponse, body: expressibleByNilLiteralType.init(nilLiteral: ()) as! T, bodyData: data)))
} else {
completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, response, DecodableRequestBuilderError.emptyDataResponse)))
completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, httpResponse, DecodableRequestBuilderError.emptyDataResponse)))
}
return
}
Expand All @@ -383,7 +391,7 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable {
case let .success(decodableObj):
completion(.success(Response(response: httpResponse, body: decodableObj, bodyData: unwrappedData)))
case let .failure(error):
completion(.failure(ErrorResponse.error(httpResponse.statusCode, unwrappedData, response, error)))
completion(.failure(ErrorResponse.error(httpResponse.statusCode, unwrappedData, httpResponse, error)))
}
}
}
Expand Down Expand Up @@ -694,7 +702,7 @@ extension JSONDataEncoding: ParameterEncoding {}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol OpenAPIInterceptor {
func intercept<T>(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder<T>, completion: @escaping (Result<URLRequest, Error>) -> Void)
func retry<T>(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder<T>, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
func retry<T>(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder<T>, data: Data?, response: URLResponse?, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void)
}

{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class DefaultOpenAPIInterceptor: OpenAPIInterceptor {
Expand All @@ -704,7 +712,7 @@ extension JSONDataEncoding: ParameterEncoding {}
completion(.success(urlRequest))
}

public func retry<T>(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder<T>, data: Data?, response: URLResponse, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
public func retry<T>(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder<T>, data: Data?, response: URLResponse?, error: Error, completion: @escaping (OpenAPIInterceptorRetry) -> Void) {
completion(.dontRetry)
}
}
Loading

0 comments on commit e9ea12f

Please sign in to comment.