Skip to content

Commit

Permalink
kotlin
Browse files Browse the repository at this point in the history
  • Loading branch information
fcarrero committed Aug 27, 2024
1 parent f4be860 commit c3c91e4
Show file tree
Hide file tree
Showing 272 changed files with 8,811 additions and 12,237 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
port: 3000
- name: Build and test
run: |
./gradlew clean build
./gradlew clean build test
- name: Archive artifacts
uses: actions/upload-artifact@v2
Expand Down
446 changes: 234 additions & 212 deletions .openapi-generator/FILES

Large diffs are not rendered by default.

168 changes: 146 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,156 @@
# org.openapitools.client - Kotlin client library for Conekta API

Conekta sdk

## Overview
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate an API client.
A kotlin client for Android using the currently recommended http client, Volley. See https://developer.android.com/training/volley

- API version: 2.1.0
- Package version:
- Generator version: 7.6.0
- Build package: org.openapitools.codegen.languages.KotlinClientCodegen
For more information, please visit [https://github.com/conekta/openapi/issues](https://github.com/conekta/openapi/issues)
- 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
```class SomeApi (
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 overridable request queue - which in turn can have a custom http url stack passed to it
- An overridable 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.7.21
* Gradle 7.5
* Kotlin 1.4.30
* Gradle 6.8.3

## Build

Expand All @@ -32,13 +168,6 @@ Then, run:

This runs all tests and packages the library.

## Features/Implementation Notes

* Supports JSON inputs/outputs, File inputs, and Form inputs.
* Supports collection formats for query parameters: csv, tsv, ssv, pipes.
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in OpenAPI definitions.
* Implementation of ApiClient is intended to reduce method counts, specifically to benefit Android targets.

<a id="documentation-for-api-endpoints"></a>
## Documentation for API Endpoints

Expand Down Expand Up @@ -333,8 +462,3 @@ Authentication schemes defined for the API:

- **Type**: HTTP Bearer Token authentication



## Author

[email protected]
107 changes: 68 additions & 39 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,60 +1,89 @@
group 'io.conekta'
version '6.0.0-beta'

wrapper {
gradleVersion = '8.7'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}

buildscript {
ext.kotlin_version = '1.9.23'
ext.spotless_version = "6.25.0"

ext.kotlin_version = '1.5.20'
ext.swagger_annotations_version = "1.6.2"
ext.gson_version = "2.8.6"
ext.volley_version = "1.2.0"
ext.junit_version = "4.13.2"
ext.robolectric_version = "4.5.1"
ext.concurrent_unit_version = "0.4.6"

repositories {
maven { url "https://repo1.maven.org/maven2" }
mavenLocal()
google()
maven {
url 'https://dl.google.com/dl/android/maven2'
}
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.diffplug.spotless:spotless-plugin-gradle:$spotless_version"
classpath 'com.android.tools.build:gradle:7.4.2'
}
}

apply plugin: 'kotlin'
apply plugin: 'maven-publish'
apply plugin: 'com.diffplug.spotless'

repositories {
maven { url "https://repo1.maven.org/maven2" }
allprojects {
repositories {
google()
mavenCentral()
}
}

// Use spotless plugin to automatically format code, remove unused import, etc
// To apply changes directly to the file, run `gradlew spotlessApply`
// Ref: https://github.com/diffplug/spotless/tree/main/plugin-gradle
spotless {
// comment out below to run spotless as part of the `check` task
enforceCheck false
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

format 'misc', {
// define the files (e.g. '*.gradle', '*.md') to apply `misc` to
target '.gitignore'

// define the steps to apply to those files
trimTrailingWhitespace()
indentWithSpaces() // Takes an integer argument if you don't like 4
endWithNewline()
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
}
kotlin {
ktfmt()
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
abortOnError false
}

// Rename the aar correctly
libraryVariants.all { variant ->
variant.outputs.all { output ->
if (outputFile != null && outputFileName.endsWith('.aar')) {
outputFileName = "${archivesBaseName}-${version}.aar"
}
}
}
}

test {
useJUnitPlatform()
testOptions {
unitTests.returnDefaultValues = true
}
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "com.google.code.gson:gson:2.10.1"
implementation "com.squareup.okhttp3:okhttp:4.12.0"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "io.swagger:swagger-annotations:$swagger_annotations_version"
implementation "com.google.code.gson:gson:$gson_version"
implementation "com.android.volley:volley:${volley_version}"
testImplementation "junit:junit:$junit_version"
testImplementation "org.robolectric:robolectric:${robolectric_version}"
testImplementation "net.jodah:concurrentunit:${concurrent_unit_version}"
testImplementation "io.kotlintest:kotlintest-runner-junit5:3.4.2"
testImplementation("io.mockk:mockk:1.12.0")
}

afterEvaluate {
android.libraryVariants.all { variant ->
def task = project.tasks.create "jar${variant.name.capitalize()}", Jar
task.description = "Create jar artifact for ${variant.name}"
task.dependsOn variant.javaCompile
task.from variant.javaCompile.destinationDirectory
task.destinationDirectory = project.file("${project.buildDir}/outputs/jar")
task.archiveFileName = "${project.name}-${variant.baseName}-${version}.jar"
artifacts.add('archives', task);
}
}

2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"scmConnection": "scm:git:[email protected]:conekta/conekta-android..git",
"scmDeveloperConnection": "scm:git:[email protected]:conekta/conekta-android..git",
"scmUrl": "https://github.com/conekta/conekta-android./tree/main",
"library": "jvm-okhttp4",
"library": "jvm-volley",
"swagger1AnnotationLibrary": false,
"javaxPackage": "javax",
"disallowAdditionalPropertiesIfNotPresent": true,
Expand Down
Loading

0 comments on commit c3c91e4

Please sign in to comment.