Skip to content

Commit

Permalink
[swift6] promote to beta and improve documentation (OpenAPITools#19856)
Browse files Browse the repository at this point in the history
* [swift6] promote to beta

* [swift6] format code

* [swift] authentication docs

* [swift] update docs

* [swift] update docs

* [swift] update docs

* [swift] update docs

* [swift] update docs

* [swift] update docs

* [swift] update docs

* [swift] update docs

* [swift] update docs

* [swift] update docs
  • Loading branch information
4brunu authored Oct 14, 2024
1 parent b5b760a commit 2354d40
Show file tree
Hide file tree
Showing 103 changed files with 3,863 additions and 812 deletions.
404 changes: 395 additions & 9 deletions docs/faq-generators.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ The following generators are available:
* [scalaz](generators/scalaz.md)
* [swift-combine](generators/swift-combine.md)
* [swift5](generators/swift5.md)
* [swift6 (experimental)](generators/swift6.md)
* [swift6 (beta)](generators/swift6.md)
* [typescript (experimental)](generators/typescript.md)
* [typescript-angular](generators/typescript-angular.md)
* [typescript-aurelia](generators/typescript-aurelia.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/swift6.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ title: Documentation for the swift6 Generator
| Property | Value | Notes |
| -------- | ----- | ----- |
| generator name | swift6 | pass this to the generate command after -g |
| generator stability | EXPERIMENTAL | |
| generator stability | BETA | |
| generator type | CLIENT | |
| generator language | Swift | |
| generator default templating engine | mustache | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,11 @@ public void postProcess() {
System.out.println("# #");
System.out.println("# swift5 generator is contributed by Bruno Coelho (https://github.com/4brunu). #");
System.out.println("# Please support his work directly via https://paypal.com/paypalme/4brunu \uD83D\uDE4F #");
System.out.println("# #");
System.out.println("# There is a new `swift6` generator, that is currently in beta. #");
System.out.println("# Try it and give us your feedback. #");
System.out.println("# https://openapi-generator.tech/docs/generators/swift6 #");
System.out.println("# https://openapi-generator.tech/docs/faq-generators/#how-do-i-migrate-from-the-swift-5-generator-to-the-swift-6-generator");
System.out.println("################################################################################");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public Swift6ClientCodegen() {
this.useOneOfInterfaces = true;

generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
.stability(Stability.EXPERIMENTAL)
.stability(Stability.BETA)
.build();

outputFolder = "generated-code" + File.separator + "swift";
Expand Down
246 changes: 246 additions & 0 deletions modules/openapi-generator/src/main/resources/swift5/README.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,252 @@ Class | Method | HTTP request | Description

{{/authMethods}}

{{#useURLSession}}
### How do I implement bearer token authentication with URLSession on the Swift 5 API client?

First you subclass RequestBuilderFactory
```
class BearerRequestBuilderFactory: RequestBuilderFactory {
func getNonDecodableBuilder<T>() -> RequestBuilder<T>.Type {
BearerRequestBuilder<T>.self
}

func getBuilder<T: Decodable>() -> RequestBuilder<T>.Type {
BearerDecodableRequestBuilder<T>.self
}
}
```

Then you subclass URLSessionRequestBuilder and URLSessionDecodableRequestBuilder
```
class BearerRequestBuilder<T>: URLSessionRequestBuilder<T> {
@discardableResult
override func execute(_ apiResponseQueue: DispatchQueue = PetstoreClientAPI.apiResponseQueue, _ completion: @escaping (Result<Response<T>, ErrorResponse>) -> Void) -> RequestTask {
// Before making the request, we can validate if we have a bearer token to be able to make a request
BearerTokenHandler.refreshTokenIfDoesntExist {
// Here we make the request
super.execute(apiResponseQueue) { 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.refreshTokenIfUnauthorizedRequestResponse(
data: data,
response: response,
error: error
) { wasTokenRefreshed in
if wasTokenRefreshed {
// 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.execute(apiResponseQueue, 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> {
@discardableResult
override func execute(_ apiResponseQueue: DispatchQueue = PetstoreClientAPI.apiResponseQueue, _ completion: @escaping (Result<Response<T>, ErrorResponse>) -> Void) -> RequestTask {
// Before making the request, we can validate if we have a bearer token to be able to make a request
BearerTokenHandler.refreshTokenIfDoesntExist {
// Here we make the request
super.execute(apiResponseQueue) { 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.refreshTokenIfUnauthorizedRequestResponse(
data: data,
response: response,
error: error
) { wasTokenRefreshed in
if wasTokenRefreshed {
// 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.execute(apiResponseQueue, 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 {
private static var bearerToken: String? = nil
static func refreshTokenIfDoesntExist(completionHandler: @escaping () -> Void) {
if bearerToken != nil {
completionHandler()
} else {
startRefreshingToken {
completionHandler()
}
}
}

static func refreshTokenIfUnauthorizedRequestResponse(data: Data?, response: URLResponse?, error: Error?, completionHandler: @escaping (Bool) -> Void) {
if let response = response as? HTTPURLResponse, response.statusCode == 401 {
startRefreshingToken {
completionHandler(true)
}
} else {
completionHandler(false)
}
}

private static func startRefreshingToken(completionHandler: @escaping () -> Void) {
// Get a bearer token
let dummyBearerToken = "..."
bearerToken = dummyBearerToken
PetstoreClientAPI.customHeaders["Authorization"] = "Bearer \(dummyBearerToken)"
completionHandler()
}
}
```

Then you assign the `BearerRequestBuilderFactory` to the property `requestBuilderFactory`.

`PetstoreClientAPI.requestBuilderFactory = BearerRequestBuilderFactory()`

The name `PetstoreClientAPI.requestBuilderFactory` will change depending on your project name.

Here is a working sample that put's together all of this.
[AppDelegate.swift](https://github.com/OpenAPITools/openapi-generator/blob/master/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClient/AppDelegate.swift)
[BearerDecodableRequestBuilder.swift](https://github.com/OpenAPITools/openapi-generator/blob/master/samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClient/BearerDecodableRequestBuilder.swift)
{{/useURLSession}}
{{#useAlamofire}}
### How do I implement bearer token authentication with Alamofire on the Swift 5 API client?

First you subclass RequestBuilderFactory
```
class BearerRequestBuilderFactory: RequestBuilderFactory {
func getNonDecodableBuilder<T>() -> RequestBuilder<T>.Type {
BearerRequestBuilder<T>.self
}

func getBuilder<T: Decodable>() -> RequestBuilder<T>.Type {
BearerDecodableRequestBuilder<T>.self
}
}
```

Then you subclass AlamofireRequestBuilder and AlamofireDecodableRequestBuilder
```
class BearerRequestBuilder<T>: AlamofireRequestBuilder<T> {
override func createSessionManager() -> SessionManager {
let sessionManager = super.createSessionManager()
let bearerTokenHandler = BearerTokenHandler()
sessionManager.adapter = bearerTokenHandler
sessionManager.retrier = bearerTokenHandler
return sessionManager
}
}

class BearerDecodableRequestBuilder<T: Decodable>: AlamofireDecodableRequestBuilder<T> {
override func createSessionManager() -> SessionManager {
let sessionManager = super.createSessionManager()
let bearerTokenHandler = BearerTokenHandler()
sessionManager.adapter = bearerTokenHandler
sessionManager.retrier = bearerTokenHandler
return sessionManager
}
}

class BearerTokenHandler: RequestAdapter, RequestRetrier {
private static var bearerToken: String? = nil
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
if let bearerToken = Self.bearerToken {
var urlRequest = urlRequest
urlRequest.setValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization")
return urlRequest
}

return urlRequest
}

func should(_: SessionManager, retry request: Request, with _: Error, completion: @escaping RequestRetryCompletion) {
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
Self.startRefreshingToken { isTokenRefreshed in
completion(isTokenRefreshed, 0.0)
}
} else {
completion(false, 0.0)
}
}

private static func startRefreshingToken(completionHandler: @escaping (Bool) -> Void) {
// Get a bearer token
let dummyBearerToken = "..."
bearerToken = dummyBearerToken
PetstoreClientAPI.customHeaders["Authorization"] = "Bearer \(dummyBearerToken)"
completionHandler(true)
}
}
```

Then you assign the `BearerRequestBuilderFactory` to the property `requestBuilderFactory`.

`PetstoreClientAPI.requestBuilderFactory = BearerRequestBuilderFactory()`

The name `PetstoreClientAPI.requestBuilderFactory` will change depending on your project name.

Here is a working sample that put's together all of this.
[AppDelegate.swift](https://github.com/OpenAPITools/openapi-generator/blob/master/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClient/AppDelegate.swift)
[BearerTokenHandler.swift](https://github.com/OpenAPITools/openapi-generator/blob/master/samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClient/BearerDecodableRequestBuilder.swift)
{{/useAlamofire}}

## Author

{{#apiInfo}}{{#apis}}{{#-last}}{{infoEmail}}
Expand Down
Loading

0 comments on commit 2354d40

Please sign in to comment.