Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update models in kotlin client generator #1373

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cloud-agent/client/kotlin/.openapi-generator-ignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
settings.gradle
build.gradle
docs
src/main/test

# igore broken files
src/main/kotlin/org/hyperledger/identus/client/models/UpdateManagedDIDServiceAction.kt
Expand All @@ -15,3 +16,5 @@ src/main/kotlin/org/hyperledger/identus/client/models/CredentialSubject.kt
src/main/kotlin/org/hyperledger/identus/client/models/DateTimeParameter.kt
src/main/kotlin/org/hyperledger/identus/client/models/DidParameter.kt
src/main/kotlin/org/hyperledger/identus/client/models/VcVerificationParameter.kt

src/main/kotlin/org/hyperledger/identus/client/models/Json.kt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm probably not understanding something, but I thought initially that you will override Service(id, type, serviceEndpoint) type here, should not you also add it in this file as well?

444 changes: 444 additions & 0 deletions cloud-agent/client/kotlin/.openapi-generator/FILES

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cloud-agent/client/kotlin/.openapi-generator/VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.4.0
97 changes: 97 additions & 0 deletions cloud-agent/client/kotlin/CUTOM_TYPES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Custom Types

Right now, the `openapi-generator` tool being used [doesn't cover](https://openapi-generator.tech/docs/generators/kotlin#schema-support-feature)
all the `OAS 3+` features. The goal of this document is to define some processes when we have to manually write the models for it.

- OpenApi Generator for Kotlin: https://openapi-generator.tech/docs/generators/kotlin

## OneOf

- Specification: https://swagger.io/specification/#discriminator-object

1. Generate the models
2. Find the model you want to override
3. Add the file to `.openapi-generator-ignore`
4. Implement the files
1. Model class
2. Interfaces
3. Adapters
5. Add to git
6. Push

### OneOf - File Implementation

The strategy found to implement the `OneOf` was through interfaces.

E.g.
```yml
AnimalResponse:
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Lizard'
```

Generating the models will create the `AnimalResponse` model class file.
In this case `AnimalResponse` can be one of these 3 types

- Cat
- Dog
- Lizard

The next step is to create a new base types (interfaces) in `org.hyperledger.identus.client.custom.types.base` package.

Since we are not going to add a new `model` we just have to create the interfaces in that package.

It's `mandatory` that each interface implements the `BaseType` interface.

#### Interfaces

```kotlin
interface CatType : BaseType {
fun meow(): String {
return convert()
}
}
```

```kotlin
interface DogType : BaseType {
fun bark(): String {
return convert()
}
}
```

```kotlin
interface LizardType : BaseType {
fun chirp(): String {
return convert()
}
}
```

#### Model class

```kotlin
@JsonAdapter(AnimalResponseAdapter::class)
class AnimalResponse(override var value: Any?) : CatType, DogType, LizardType
```

#### Adapter

If there's no `discriminator` you'll have to try to parse each type to retrieve the typed object.

```kotlin
class ServiceTypeAdapter : TypeAdapter<AnimalResponse>() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be class AnimalResponseAdapter? 🤔

override fun write(out: JsonWriter, value: AnimalResponse) = // ...
override fun read(input: JsonReader): AnimalResponse {
// use the generic Extension.read(input) to retrieve the object as `Hashmap`
// and add the cases to deserialize the object.
// this method sets the `value` of the base type
return AnimalResponse
}
}
```

Or add the `GenericObjectType` interface to the type and retrieve using the `.asObject(MyClass::class)` method.
11 changes: 6 additions & 5 deletions cloud-agent/client/kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ wrapper {
}

buildscript {
ext.kotlin_version = '1.7.21'
ext.kotlin_version = '2.0.20'

repositories {
mavenLocal()
Expand All @@ -20,6 +20,7 @@ buildscript {
apply plugin: 'kotlin'
apply plugin: 'maven-publish'
apply plugin: 'java'
apply plugin: 'idea'

repositories {
mavenLocal()
Expand All @@ -31,10 +32,10 @@ test {
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "com.google.code.gson:gson:2.9.0"
implementation "com.squareup.okhttp3:okhttp:4.10.0"
testImplementation "io.kotlintest:kotlintest-runner-junit5:3.4.2"
implementation "com.squareup.okhttp3:okhttp:4.12.0"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlin_version"
}

java {
Expand All @@ -58,4 +59,4 @@ publishing {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.hyperledger.identus.client.custom

import com.google.gson.Gson
import com.google.gson.ToNumberPolicy
import com.google.gson.TypeAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonToken.BEGIN_ARRAY
import com.google.gson.stream.JsonToken.BEGIN_OBJECT
import com.google.gson.stream.JsonToken.BOOLEAN
import com.google.gson.stream.JsonToken.NULL
import com.google.gson.stream.JsonToken.NUMBER
import com.google.gson.stream.JsonToken.STRING
import com.google.gson.stream.JsonWriter
import org.hyperledger.identus.client.custom.adapters.JsonTypeAdapter
import org.hyperledger.identus.client.custom.types.JsonType
import org.hyperledger.identus.client.custom.types.base.BaseType

inline fun <reified T : Any> BaseType.convert(): T? {
if (this.value == null) {
return null
}
if (this.value is T) {
return this.value as T
}
throw IllegalStateException("Requested parameter as [${this.value!!::class.simpleName}] but is [${T::class.simpleName}]")
}

inline fun <reified T: BaseType> TypeAdapter<T>.writeExtension(out: JsonWriter, value: T) {
out.jsonValue(Gson().toJson(value.value))
}

fun JsonTypeAdapter.oleole() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you've been using this while developing and forgot to remove

println("PORRA")
}

inline fun <reified T: BaseType> TypeAdapter<T>.readExtension(input: JsonReader): T {
val constructor = T::class.constructors.first()
when (val parsed = input.parse(input)) {
is List<*> -> {
val json = Gson().toJson(parsed)
val type = object : TypeToken<List<Any?>>() {}.type
val list: List<Any?> = Gson().fromJson(json, type)
return constructor.call(list)
}
null,
is String,
is Number,
is Boolean,
is HashMap<*,*> -> return constructor.call(parsed)
else -> throw IllegalArgumentException("Unsupported type ${parsed?.javaClass}")
}
}

fun JsonReader.parse(input: JsonReader): Any? {
val token: JsonToken = input.peek()
when (token) {
BEGIN_ARRAY -> {
val list = mutableListOf<Any?>()
input.beginArray()

while (input.hasNext()) {
list.add(parse(input))
}

input.endArray()
return list
}
BEGIN_OBJECT -> {
val map = linkedMapOf<Any, Any?>()
input.beginObject()
while (input.hasNext()) {
map[input.nextName()] = parse(input)
}
input.endObject()
return map
}
STRING -> return input.nextString()
NUMBER -> return ToNumberPolicy.LAZILY_PARSED_NUMBER.readNumber(input)
BOOLEAN -> return input.nextBoolean()
NULL -> {
input.nextNull()
return null
}
else -> throw IllegalStateException()
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.hyperledger.identus.client.custom.adapters

import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import org.hyperledger.identus.client.custom.readExtension
import org.hyperledger.identus.client.custom.types.JsonType
import org.hyperledger.identus.client.custom.writeExtension

class JsonTypeAdapter : TypeAdapter<JsonType>() {
override fun write(out: JsonWriter, value: JsonType) = writeExtension(out, value)

override fun read(input: JsonReader): JsonType = readExtension(input)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.hyperledger.identus.client.custom.adapters

import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import org.hyperledger.identus.client.custom.readExtension
import org.hyperledger.identus.client.custom.types.ServiceType
import org.hyperledger.identus.client.custom.writeExtension

class ServiceTypeAdapter : TypeAdapter<ServiceType>() {
override fun write(out: JsonWriter, value: ServiceType) = writeExtension(out, value)
override fun read(input: JsonReader): ServiceType = readExtension(input)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.hyperledger.identus.client.custom.types

import com.google.gson.annotations.JsonAdapter
import org.hyperledger.identus.client.custom.adapters.JsonTypeAdapter
import org.hyperledger.identus.client.custom.types.base.ArrayType
import org.hyperledger.identus.client.custom.types.base.BoolType
import org.hyperledger.identus.client.custom.types.base.GenericObjectType
import org.hyperledger.identus.client.custom.types.base.NullType
import org.hyperledger.identus.client.custom.types.base.NumberType
import org.hyperledger.identus.client.custom.types.base.StringType

@JsonAdapter(JsonTypeAdapter::class)
class JsonType(override var value: Any?) : ArrayType, BoolType, NullType, NumberType, GenericObjectType, StringType
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.hyperledger.identus.client.custom.types

import com.google.gson.annotations.JsonAdapter
import org.hyperledger.identus.client.custom.adapters.ServiceTypeAdapter
import org.hyperledger.identus.client.custom.types.base.ArrayType
import org.hyperledger.identus.client.custom.types.base.StringType

@JsonAdapter(ServiceTypeAdapter::class)
class ServiceType(override var value: Any?) : ArrayType, StringType
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.hyperledger.identus.client.custom.types.base

import org.hyperledger.identus.client.custom.convert

interface ArrayType : BaseType {
fun asArray(): kotlin.collections.List<Any>? {
return convert()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.hyperledger.identus.client.custom.types.base

import com.google.gson.annotations.JsonAdapter
import org.hyperledger.identus.client.custom.adapters.JsonTypeAdapter

@JsonAdapter(JsonTypeAdapter::class)
interface BaseType {
var value: Any?

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.hyperledger.identus.client.custom.types.base

interface BoolType : BaseType {
fun asBool(): Boolean {
return value as Boolean
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.hyperledger.identus.client.custom.types.base

import com.google.gson.Gson
import kotlin.reflect.KClass

interface GenericObjectType : BaseType {
fun <T : Any> asObject(clazz: KClass<T>): T {
val json = Gson().toJson(value)
return Gson().fromJson(json, clazz.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.hyperledger.identus.client.custom.types.base

interface NullType : BaseType {
override var value: Any?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.hyperledger.identus.client.custom.types.base

interface NumberType : BaseType {
fun asNumber(): kotlin.Number {
return value as kotlin.Number
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.hyperledger.identus.client.custom.types.base

import org.hyperledger.identus.client.custom.convert

interface StringType : BaseType {
fun asString(): kotlin.String? {
return convert()
}
}
Loading
Loading