Skip to content

Commit 8d66b1b

Browse files
authored
Merge pull request #81 from pace/improve-401-response-handling
Improve 401 response handling in swift API templates
2 parents 1e58101 + ef68d28 commit 8d66b1b

File tree

7 files changed

+134
-36
lines changed

7 files changed

+134
-36
lines changed

Sources/SwagGenKit/CodeFormatter.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ public class CodeFormatter {
369369
return value == "OAuth2" || value == "OIDC" || value == "keycloak"
370370
}) {
371371
context["authorizationRequired"] = true
372+
} else {
373+
context["authorizationRequired"] = false
372374
}
373375

374376
// Responses

Templates/SwiftPrivateAPIs/Common/APIClient.swift

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,69 @@ public class {{ options.name }}Client {
8080
urlRequest.allHTTPHeaderFields?[key] = value
8181
}
8282

83+
let cancellableRequest = Cancellable{{ options.name }}Request(request: request.asAny())
84+
8385
urlRequest = requestBehaviour.modifyRequest(urlRequest)
8486

85-
let cancellableRequest = Cancellable{{ options.name }}Request(request: request.asAny())
87+
if request.isAuthorizationRequired
88+
&& request.customHeaders[HttpHeaderFields.authorization.rawValue] == nil
89+
&& IDKit.isSessionAvailable {
90+
IDKit.refreshToken { [weak self] result in
91+
guard let self else { return }
92+
guard case let .failure(error) = result else {
93+
guard case let .success(accessToken) = result,
94+
let accessToken else { return }
95+
urlRequest.setValue("Bearer \(accessToken)",
96+
forHTTPHeaderField: HttpHeaderFields.authorization.rawValue)
97+
self.validateNetworkRequest(request: request,
98+
urlRequest: urlRequest,
99+
cancellableRequest: cancellableRequest,
100+
requestBehaviour: requestBehaviour,
101+
currentUnauthorizedRetryCount: currentUnauthorizedRetryCount,
102+
currentRetryCount: currentRetryCount,
103+
completionQueue: completionQueue,
104+
complete: complete)
105+
return
106+
}
107+
108+
if case .failedTokenRefresh = error {
109+
completionQueue.async {
110+
let response = {{ options.name }}Response<T>(request: request,
111+
result: .failure(APIClientError
112+
.unexpectedStatusCode(statusCode: 401,
113+
data: Data("UNAUTHORIZED".utf8))))
114+
complete(response)
115+
}
116+
} else {
117+
completionQueue.async {
118+
let response = {{ options.name }}Response<T>(request: request,
119+
result: .failure(APIClientError.unknownError(error)))
120+
complete(response)
121+
}
122+
}
123+
}
124+
} else {
125+
validateNetworkRequest(request: request,
126+
urlRequest: urlRequest,
127+
cancellableRequest: cancellableRequest,
128+
requestBehaviour: requestBehaviour,
129+
currentUnauthorizedRetryCount: currentUnauthorizedRetryCount,
130+
currentRetryCount: currentRetryCount,
131+
completionQueue: completionQueue,
132+
complete: complete)
133+
}
134+
135+
return cancellableRequest
136+
}
86137

138+
private func validateNetworkRequest<T>(request: {{ options.name }}Request<T>,
139+
urlRequest: URLRequest,
140+
cancellableRequest: Cancellable{{ options.name }}Request,
141+
requestBehaviour: {{ options.name }}RequestBehaviourGroup,
142+
currentUnauthorizedRetryCount: Int,
143+
currentRetryCount: Int,
144+
completionQueue: DispatchQueue,
145+
complete: @escaping ({{ options.name }}Response<T>) -> Void) {
87146
requestBehaviour.validate(urlRequest) { result in
88147
switch result {
89148
case .success(let urlRequest):
@@ -101,7 +160,6 @@ public class {{ options.name }}Client {
101160
complete(response)
102161
}
103162
}
104-
return cancellableRequest
105163
}
106164

107165
private func makeNetworkRequest<T>(request: {{ options.name }}Request<T>,
@@ -267,9 +325,10 @@ public class {{ options.name }}Client {
267325
}
268326

269327
if response.statusCode == HttpStatusCode.unauthorized.rawValue
328+
&& request.customHeaders[HttpHeaderFields.authorization.rawValue] == nil
270329
&& currentUnauthorizedRetryCount < maxUnauthorizedRetryCount
271330
&& IDKit.isSessionAvailable {
272-
IDKit.refreshToken { [weak self] result in
331+
IDKit.refreshToken(force: true) { [weak self] result in
273332
guard case .failure(let error) = result else {
274333
self?.makeRequest(request,
275334
behaviours: requestBehaviour.behaviours,

Templates/SwiftPrivateAPIs/Common/APIRequest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class {{ options.name }}Request<ResponseType: APIResponseValue> {
1212
public var customHeaders: [String: String] = [:]
1313
public var version: String = "{{ info.version }}"
1414
public var contentType: String = "application/json"
15+
public private(set) var isAuthorizationRequired: Bool = false
1516

1617
public var headers: [String: String] {
1718
return headerParameters.merging(customHeaders) { param, custom in return custom }

Templates/SwiftPrivateAPIs/Common/Request.swift

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -158,26 +158,14 @@ extension {{ options.name }}{% if tag %}.{{ options.tagPrefix }}{{ tag|upperCame
158158
headers["{{ param.value }}"] = {% if param.type == "String" %}options.{{ param.encodedValue }}{% else %}String(describing: options.{{ param.encodedValue }}){% endif %}
159159
{% endif %}
160160
{% endfor %}
161-
{% if authorizationRequired %}
162-
if let token = API.accessToken {
163-
headers["Authorization"] = "Bearer \(token)"
164-
}
165-
{% endif %}
166161

167162
return headers
168163
}
169-
{% else %}
170-
{% if authorizationRequired %}
164+
{% endif %}
171165

172-
override var headerParameters: [String: String] {
173-
var headers: [String: String] = [:]
174-
if let token = API.accessToken {
175-
headers["Authorization"] = "Bearer \(token)"
176-
}
177-
return headers
166+
public override var isAuthorizationRequired: Bool {
167+
{% if authorizationRequired == true %}true{% else %}false{% endif %}
178168
}
179-
{% endif %}
180-
{% endif %}
181169
}
182170

183171
public enum Response: APIResponseValue, CustomStringConvertible, CustomDebugStringConvertible {

Templates/SwiftPublicAPIs/Common/APIClient.swift

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,69 @@ public class {{ options.name }}Client {
8080
urlRequest.allHTTPHeaderFields?[key] = value
8181
}
8282

83+
let cancellableRequest = Cancellable{{ options.name }}Request(request: request.asAny())
84+
8385
urlRequest = requestBehaviour.modifyRequest(urlRequest)
8486

85-
let cancellableRequest = Cancellable{{ options.name }}Request(request: request.asAny())
87+
if request.isAuthorizationRequired
88+
&& request.customHeaders[HttpHeaderFields.authorization.rawValue] == nil
89+
&& IDKit.isSessionAvailable {
90+
IDKit.refreshToken { [weak self] result in
91+
guard let self else { return }
92+
guard case let .failure(error) = result else {
93+
guard case let .success(accessToken) = result,
94+
let accessToken else { return }
95+
urlRequest.setValue("Bearer \(accessToken)",
96+
forHTTPHeaderField: HttpHeaderFields.authorization.rawValue)
97+
self.validateNetworkRequest(request: request,
98+
urlRequest: urlRequest,
99+
cancellableRequest: cancellableRequest,
100+
requestBehaviour: requestBehaviour,
101+
currentUnauthorizedRetryCount: currentUnauthorizedRetryCount,
102+
currentRetryCount: currentRetryCount,
103+
completionQueue: completionQueue,
104+
complete: complete)
105+
return
106+
}
107+
108+
if case .failedTokenRefresh = error {
109+
completionQueue.async {
110+
let response = {{ options.name }}Response<T>(request: request,
111+
result: .failure(APIClientError
112+
.unexpectedStatusCode(statusCode: 401,
113+
data: Data("UNAUTHORIZED".utf8))))
114+
complete(response)
115+
}
116+
} else {
117+
completionQueue.async {
118+
let response = {{ options.name }}Response<T>(request: request,
119+
result: .failure(APIClientError.unknownError(error)))
120+
complete(response)
121+
}
122+
}
123+
}
124+
} else {
125+
validateNetworkRequest(request: request,
126+
urlRequest: urlRequest,
127+
cancellableRequest: cancellableRequest,
128+
requestBehaviour: requestBehaviour,
129+
currentUnauthorizedRetryCount: currentUnauthorizedRetryCount,
130+
currentRetryCount: currentRetryCount,
131+
completionQueue: completionQueue,
132+
complete: complete)
133+
}
134+
135+
return cancellableRequest
136+
}
86137

138+
private func validateNetworkRequest<T>(request: {{ options.name }}Request<T>,
139+
urlRequest: URLRequest,
140+
cancellableRequest: Cancellable{{ options.name }}Request,
141+
requestBehaviour: {{ options.name }}RequestBehaviourGroup,
142+
currentUnauthorizedRetryCount: Int,
143+
currentRetryCount: Int,
144+
completionQueue: DispatchQueue,
145+
complete: @escaping ({{ options.name }}Response<T>) -> Void) {
87146
requestBehaviour.validate(urlRequest) { result in
88147
switch result {
89148
case .success(let urlRequest):
@@ -101,7 +160,6 @@ public class {{ options.name }}Client {
101160
complete(response)
102161
}
103162
}
104-
return cancellableRequest
105163
}
106164

107165
private func makeNetworkRequest<T>(request: {{ options.name }}Request<T>,
@@ -267,9 +325,10 @@ public class {{ options.name }}Client {
267325
}
268326

269327
if response.statusCode == HttpStatusCode.unauthorized.rawValue
328+
&& request.customHeaders[HttpHeaderFields.authorization.rawValue] == nil
270329
&& currentUnauthorizedRetryCount < maxUnauthorizedRetryCount
271330
&& IDKit.isSessionAvailable {
272-
IDKit.refreshToken { [weak self] result in
331+
IDKit.refreshToken(force: true) { [weak self] result in
273332
guard case .failure(let error) = result else {
274333
self?.makeRequest(request,
275334
behaviours: requestBehaviour.behaviours,

Templates/SwiftPublicAPIs/Common/APIRequest.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class {{ options.name }}Request<ResponseType: APIResponseValue> {
1212
public var customHeaders: [String: String] = [:]
1313
public var version: String = "{{ info.version }}"
1414
public var contentType: String = "application/json"
15+
public private(set) var isAuthorizationRequired: Bool = false
1516

1617
public var headers: [String: String] {
1718
return headerParameters.merging(customHeaders) { param, custom in return custom }

Templates/SwiftPublicAPIs/Common/Request.swift

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -158,26 +158,14 @@ extension {{ options.name }}{% if tag %}.{{ options.tagPrefix }}{{ tag|upperCame
158158
headers["{{ param.value }}"] = {% if param.type == "String" %}options.{{ param.encodedValue }}{% else %}String(describing: options.{{ param.encodedValue }}){% endif %}
159159
{% endif %}
160160
{% endfor %}
161-
{% if authorizationRequired %}
162-
if let token = API.accessToken {
163-
headers["Authorization"] = "Bearer \(token)"
164-
}
165-
{% endif %}
166161

167162
return headers
168163
}
169-
{% else %}
170-
{% if authorizationRequired %}
164+
{% endif %}
171165

172-
override var headerParameters: [String: String] {
173-
var headers: [String: String] = [:]
174-
if let token = API.accessToken {
175-
headers["Authorization"] = "Bearer \(token)"
176-
}
177-
return headers
166+
public override var isAuthorizationRequired: Bool {
167+
{% if authorizationRequired == true %}true{% else %}false{% endif %}
178168
}
179-
{% endif %}
180-
{% endif %}
181169
}
182170

183171
public enum Response: APIResponseValue, CustomStringConvertible, CustomDebugStringConvertible {

0 commit comments

Comments
 (0)