alisters 0de482da2b
Kotlin client: add volley library support (#10253)
* Add basic jvm-volley folder to enable it as a library

* Add JVM_VOLLEY to the KotlinClientCodegen as a library option (using Retrofit2 processing for now)

* Temporary checkin of generated code and kotlinfied version for use in new template

* Added Kotlin-ified api invoker and request objects, update Kotlin client codgen for volley

* Add Android specific build.gradle mustache file to jvm-volley library

* Hardcode SDK version and build tools version in build.gradle template, add extra repository for Android Gradle build tools

* Add Android manifest to generated code

* Add Kotlin dependencies and plugins to build gradle template

* WIP: Create basic API templating for jvm-volley

* Add ApiException and parameter validation, create path variable using ApiInvoker

* Build queryParams and headerParams

* Add VolleyRequest template

* WIP: Injecting context and default API invoker into APIs (non compiling)

* Add DefaultInvoker stub and update API to inject context

* Add request queue generation to the DefaultInvoker

* Fix up compile errors in the invoker

* Cleanup unrequired templates

* Update templates

* Add constructor overloads to inject stack or network into request queue

* Fix compile errors with request queue generation

* Fix compile errors

* Al'll fix it for you.....

* WIP compile fixes

* More compile fixes

* Generate to java directory and kotlin-ify auth code

* More syntax fixes in templates

* Almost left it in a working state, fixing that .... now...

* Switch builder method based on model existence constraints - body and response

* Add coroutine logic to APIs and pass through listeners to the requests, various other fixes.

* Use reflection and type tokens to work around clazz issues on generics

* Add POST, PATCH and PUT to RequestFactory

* More templating magic

* Fix Steve, the human compiler's errors again !

* Add CLI option for generating room models

* Configure the room model package

* Add initial room model templating and generation

* Add room model generation implementation

* Implement toRoom function on models to convert model to room model

* Bug fixes, transformers to and from room models

* Add query parameters to URL generation

* Fix issues with gson type conversion, add type adapters to gson instance

* Fix issues with older API versions and Java8 libraries,

* Add request factory interface

* API template tidy up

* Update IRequestFactory to include companion object, minor tidy ups

* Remove @Keep annotations from room templates

* Rename toRoomModel and toApiModel functions

* Add empty companion object to generated room model

* Add ITransformStorage interface to allow polymorphic transforms to room models

* Add content type into GsonRequest

* Move gson serialization of request body into GsonRequest

* Update request factory to take header factories

* Remove the generated comparision code

* Move the generateRoomModels switch into the KotlinClientCodegen class

* Move room model generation out of default generator

* Updates for auth

* Finalise removal of kotlin elements from default generator

* Hoist room model logic out of abstractKotlin into kotlin client codegen

* Revert AbstractKotlinCodegen

* Revert Codegen constants to remove base generator changes out of our new library

* Revert data class template changes, add data class body check to Kotlin Client codegen

* Add sample generation yaml file for jvm-volley library

* Update JVM-Volley readme for generateRoomModels flag

* Remove unused template files, get auth compiling but non functional, clean build of warnings

* Generate sample generated code

* Add not implemented method for oauth

* Add unit test for KotlinClientCodegen generateRoomModel flag

* Remove accidental hard coding of src/main/java source folder

* Push changed generated sample files

* Move and rename IStorable inside the volley library

* Inject retry policy into API definition, re-run sample and doc scripts

* Add generic post processors

* Update samples after generator changes

* Fix some compile errors with the pet store sample

* Fix duplicate auth companion object and import generation

* Reinstate query and form parameter code generation

* Add check for unsupported serialization libraries

* Fix broken unit tests

* Regenerate samples

* AN-233 Update request factory to allow custom gsonadapters

* update `GsonRequest.mustache` and `RequestFactoy.mustache` to use `Map<Type, Any>` instead of `Map<Type, Object>` to better fit kotlin conventions

* Update readme with better examples and design notes

* Update readme with info about gson serializers and adapters for polymorphic types

* Updated samples

* Merge from upstream

* Address review comments

* Update samples

* Samples

* Update docs

* Remove DateAdapter generated file, template and it's inclusion as a supporting file in favour of localDateTime

* Review comment cleanup for initial PR #10253 - cleaner auth key in parameter string handling

* Review comment - add a kotlin version parameter to the build scripts

* Updated samples

* Missing changes from build.mustache

* Regenerate samples for build.gradle changes

* Merge from master and generate samples

* Remove serializer as a supporting file from jvm-volley - it's serialisation is not a singleton and configured differently via gson request and dependency injection

* Remove singleton serializer from jvm-volley generation as it's not used

Co-authored-by: Alister Shipman <alister.shipman@greater.com.au>
Co-authored-by: Steve Telford <steven.telford@greater.com.au>
Co-authored-by: Leigh Cooper <leigh.cooper@greater.com.au>
Co-authored-by: Michael Hewett <y2trooper@gmail.com>
2021-12-20 14:59:11 +08:00

10 KiB

org.openapitools.client - Kotlin client library for OpenAPI Petstore

A kotlin client for Android using the currently recommended http client, Volley. See https://developer.android.com/training/volley

  • Currently sends GsonRequests
  • Currently only supports Gson as a serializer - will throw an exception if a different serializer is chosen
  • Defaults the source location to src/main/java as per standard Android builds

Design

Volley is a queue/request based layer on top of http url stack specific to Android. Android favours dependency injection and a layered architecture, and IO performed off the main thread to maintain UI responsiveness, with a preferred technique of kotlin co-routines. The code gen library reflects these factors.

  • Api calls use co-routines, and execute them using volley callbacks to avoid tying up a thread.

  • Facilitate dependency injection, with default implementations available.

  • Generate a requestFactory that can be overridden

  • Allow the passing of the RequestFactory per tag (api client) or per operation (an extra parameter is created on operations with non-global security), with per operation auth overriding global security.

  • DI scoping of the Request Factory and pre-generated auth header factories allow for thread safe and secure setting of credentials.

  • Lazy header factories allow for refreshing tokens etc

  • Factoring of header factories to the Request Factory allow ambient provision of credentials. Code gen library is credential storage agnostic.

  • Header factories allow the merging of generated headers from open api spec with dynamically added headers

  • Injection of http url stack to allow custom http stacks. Default implementation is best practice singleton

  • Data classes used for serialisation to reflect volley's preference - an immutable request that once queued can't be tampered with.

  • Reuse model class and other jvm common infrastructure

  • Optional generation of room database models, and transform methods to these from open api models

  • Room and api models can be extended with additional extension properties.

Future improvements

  • Option to generate image requests on certain conditionals e.g content-type gif etc
  • Support for kotlin serialization.
  • Multi part form parameters and support for file inputs

Usage

Hilt Dependency injection example - with default values for parameters overridden.

 @Provides
    internal fun provideSomeApi(
        context: Context,
        restService: IRestService,
        configurationService: IConfigurationService,
        sessionService: ISessionService
    ): SomeApi {
        return SomeApi(
            context = context,
            requestQueue = restService.getRequestQueue(),
            requestFactory = RequestFactory(listOf(createSessionHeaderFactory(sessionService), createTraceHeaderFactory()),
                postProcessors = listOf(retryPolicySetter)),
            basePath = configurationService.getBaseUrl()
        )
    }

Here is the constructor so you can see the defaults

val context: Context,
val requestQueue: Lazy<RequestQueue> = lazy(initializer = {
    Volley.newRequestQueue(context.applicationContext)
    }),
    val requestFactory: IRequestFactory = RequestFactory(),
    val basePath: String = "https://yourbasepath.from_input_parameter.com/api",
    private val postProcessors :List <(Request<*>) -> Unit> = listOf()) {

Overriding defaults

The above constructor for each api allows the following to be customized

  • A custom context, so either a singleton request queue or different scope can be created - see https://developer.android.com/training/volley/requestqueue#singleton
  • An overrideable request queue - which in turn can have a custom http url stack passed to it
  • An overrideable request factory constructor call, or a request factory that can be overridden by a custom template, with custom header factory, request post processors and custom gson adapters injected.

Overriding request generation

Request generation can be overridden by

  • Overriding the entire request factory template
  • Supplying custom header factories - methods that take any possible parameters but return a map of headers
  • Supplying custom request post processors - methods that take and return the request object

Header factory examples can be found in the auth section, as these are implemented as header factories. eg

val basicAuthHeaderFactoryBuilder = { username: String?, password: String? ->
{ mapOf("Authorization" to "Basic " + Base64.encodeToString("${username ?: ""}:${password ?: ""}".toByteArray(), Base64.DEFAULT))}
}

In this case it's a lambda function (a factory method) that takes an username and password, and returns a map of headers. Other generated code will supply the username and password. In this case it results in a map of just one key/value pair, but it could be multiple. The important part is it's returning a map - and that the surrounding code will can bind the inputs to it at some point.

Here is a different example that supplies tracing header values

/**
 * Create a lambda of tracing headers to be injected into an API's [RequestFactory].
 */
private fun createTraceHeaderFactory(): () -> Map<String, String> = {
    mapOf(
        HttpHeaderType.b3_traceId.rawValue to UUIDExtensions.asTraceId(UUID.randomUUID()),
        HttpHeaderType.b3_spanId.rawValue to UUIDExtensions.asSpanId(UUID.randomUUID()),
        HttpHeaderType.b3_sampled.rawValue to "1"
    )
}

Finally a post processor example

 /**
     * Configure a [DefaultRetryPolicy] to be injected into the [RequestFactory] with a maximum number of retries of zero.
     */
    private val retryPolicySetter = { request: Request<*> ->
        Unit.apply {
            request.setRetryPolicy(
                DefaultRetryPolicy(
                    RestService.DEFAULT_TIMEOUT_MS,
                    0,
                    DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
                )
            )
        }
    }

Serialization

Gson and Polymorphic types

The GsonRequest object can be passed custom type adapters

class GsonRequest<T>(
    method: Int,
    url: String,
    private val body: Any?,
    private val headers: Map<String, String>?,
    private val params: MutableMap<String, String>?,
    private val contentTypeForBody: String?,
    private val encodingForParams: String?,
    private val gsonAdapters: Map<Type, Object>?,
    private val type: Type,
    private val listener: Response.Listener<T>,
    errorListener: Response.ErrorListener
) : Request<T>(method, url, errorListener) {

    val gsonBuilder: GsonBuilder = GsonBuilder()
        .registerTypeAdapter(OffsetDateTime::class.java, OffsetDateTimeAdapter())
        .registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeAdapter())
        .registerTypeAdapter(LocalDate::class.java, LocalDateAdapter())
        .registerTypeAdapter(ByteArray::class.java, ByteArrayAdapter())

Requires

  • Kotlin 1.4.30
  • Gradle 6.8.3

Build

First, create the gradle wrapper script:

gradle wrapper

Then, run:

./gradlew check assemble

This runs all tests and packages the library.

Documentation for API Endpoints

All URIs are relative to http://petstore.swagger.io/v2

Class Method HTTP request Description
PetApi addPet POST /pet Add a new pet to the store
PetApi deletePet DELETE /pet/{petId} Deletes a pet
PetApi findPetsByStatus GET /pet/findByStatus Finds Pets by status
PetApi findPetsByTags GET /pet/findByTags Finds Pets by tags
PetApi getPetById GET /pet/{petId} Find pet by ID
PetApi updatePet PUT /pet Update an existing pet
PetApi updatePetWithForm POST /pet/{petId} Updates a pet in the store with form data
PetApi uploadFile POST /pet/{petId}/uploadImage uploads an image
StoreApi deleteOrder DELETE /store/order/{orderId} Delete purchase order by ID
StoreApi getInventory GET /store/inventory Returns pet inventories by status
StoreApi getOrderById GET /store/order/{orderId} Find purchase order by ID
StoreApi placeOrder POST /store/order Place an order for a pet
UserApi createUser POST /user Create user
UserApi createUsersWithArrayInput POST /user/createWithArray Creates list of users with given input array
UserApi createUsersWithListInput POST /user/createWithList Creates list of users with given input array
UserApi deleteUser DELETE /user/{username} Delete user
UserApi getUserByName GET /user/{username} Get user by user name
UserApi loginUser GET /user/login Logs user into the system
UserApi logoutUser GET /user/logout Logs out current logged in user session
UserApi updateUser PUT /user/{username} Updated user

Documentation for Models

Documentation for Authorization

api_key

  • Type: API key
  • API key parameter name: api_key
  • Location: HTTP header

petstore_auth