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

[feat] Added Kotlinx Serialization support #656

Merged
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/springwolf-addons.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
addon: [ "common-model-converters", "generic-binding", "json-schema" ]
addon: [ "common-model-converters", "generic-binding", "json-schema", "kotlinx-serialization-model-converter" ]
timeout-minutes: 10

env:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ springwolf-plugins/build/
springwolf-bindings/build/
springwolf-ui/build/
springwolf-add-ons/springwolf-common-model-converters/build/
springwolf-add-ons/springwolf-kotlinx-serialization-model-converter/build/
springwolf-examples/springwolf-amqp-example/build/
springwolf-examples/springwolf-cloud-stream-example/build/
springwolf-examples/springwolf-kafka-example/build/
Expand Down
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,12 @@ More details in the documentation.
| [AWS SQS Binding](https://github.com/springwolf/springwolf-core/tree/master/springwolf-bindings/springwolf-sqs-binding) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-sqs-binding?color=green&label=springwolf-sqs-binding&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-sqs-binding?label=springwolf-sqs-binding&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [Google PubSub Binding](https://github.com/springwolf/springwolf-core/tree/master/springwolf-bindings/springwolf-googlepubsub-binding) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-googlepubsub-binding?color=green&label=springwolf-googlepubsub-binding&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-googlepubsub-binding?label=springwolf-googlepubsub-binding&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |


| Add-on | Current version | SNAPSHOT version |
|-------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Common Model Converter](https://github.com/springwolf/springwolf-core/tree/master/springwolf-add-ons/springwolf-common-model-converters) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-common-model-converters?color=green&label=springwolf-common-model-converters&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-common-model-converters?label=springwolf-common-model-converters&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [Generic Binding](https://github.com/springwolf/springwolf-core/tree/master/springwolf-add-ons/springwolf-generic-binding) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-generic-binding?color=green&label=springwolf-generic-binding&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-generic-binding?label=springwolf-generic-binding&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [Json Schema](https://github.com/springwolf/springwolf-core/tree/master/springwolf-add-ons/springwolf-json-schema) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-json-schema?color=green&label=springwolf-json-schema&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-json-schema?label=springwolf-json-schema&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |

| Add-on | Current version | SNAPSHOT version |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Common Model Converter](https://github.com/springwolf/springwolf-core/tree/master/springwolf-add-ons/springwolf-common-model-converters) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-common-model-converters?color=green&label=springwolf-common-model-converters&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-common-model-converters?label=springwolf-common-model-converters&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [Generic Binding](https://github.com/springwolf/springwolf-core/tree/master/springwolf-add-ons/springwolf-generic-binding) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-generic-binding?color=green&label=springwolf-generic-binding&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-generic-binding?label=springwolf-generic-binding&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [Json Schema](https://github.com/springwolf/springwolf-core/tree/master/springwolf-add-ons/springwolf-json-schema) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-json-schema?color=green&label=springwolf-json-schema&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-json-schema?label=springwolf-json-schema&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
| [Kotlinx Serialization Model Converter](https://github.com/springwolf/springwolf-core/tree/master/springwolf-add-ons/springwolf-kotlinx-serialization-model-converter) | ![Maven Central](https://img.shields.io/maven-central/v/io.github.springwolf/springwolf-kotlinx-serialization-model-converter?color=green&label=springwolf-kotlinx-serialization-model-converter&style=plastic) | ![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/io.github.springwolf/springwolf-kotlinx-serialization-model-converter?label=springwolf-kotlinx-serialization-model-converter&server=https%3A%2F%2Fs01.oss.sonatype.org&style=plastic) |
</details>

### 🚀 Who's Using Springwolf
Expand Down
3 changes: 2 additions & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ include(
'springwolf-ui',
'springwolf-add-ons:springwolf-common-model-converters',
'springwolf-add-ons:springwolf-generic-binding',
'springwolf-add-ons:springwolf-json-schema'
'springwolf-add-ons:springwolf-json-schema',
'springwolf-add-ons:springwolf-kotlinx-serialization-model-converter',
)

project(':springwolf-plugins:springwolf-amqp-plugin').name = 'springwolf-amqp'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

### About

This module implements common model converters to be used with Sringwolf when needed.
This module implements common model converters to be used with Springwolf when needed.

Currently, the module includes a model converter that fixes an issue with the `javax.money.MonetaryAmount` type.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Springwolf Kotlinx Serialization Converter Add-on

##### Common `Kotlinx Serialization` beans for springwolf

### Table Of Contents

- [About](#about)
- [Usage](#usage)
- [Dependencies](#dependencies)

### About

This module implements Kotlin Serialization model converter to be used with Springwolf when needed.

Given a Kotlin class like

```kotlin
@Serializable
data class SomeData(
@SerialName("id_value")
val id: Int,
@SerialName("some_name")
val name: String,
@SerialName("color_value")
val color: Color,
)
```

will use the proper values from `@SerialName` to generate the AsyncAPI schema

### Usage

Add the following dependency:

#### Dependencies

```groovy
dependencies {
implementation 'io.github.springwolf:springwolf-kotlinx-serialization-model-converters:<springwolf-version>'
}
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
buildscript {
ext {
kotlinVersion = '1.9.22'
kotlinxSerializationVersion = '1.6.3'
}
}

plugins {
id 'java'

id 'org.springframework.boot'
id 'io.spring.dependency-management'
id 'ca.cutterslade.analyze'

id "org.jetbrains.kotlin.jvm" version "${kotlinVersion}"
id 'org.jetbrains.kotlin.plugin.serialization' version "${kotlinVersion}"
}

dependencies {
implementation "org.springframework:spring-context"
implementation "org.springframework:spring-beans"

implementation "org.slf4j:slf4j-api:${slf4jApiVersion}"

implementation "io.swagger.core.v3:swagger-core-jakarta:${swaggerVersion}"
implementation "io.swagger.core.v3:swagger-models-jakarta:${swaggerVersion}"

implementation "org.apache.commons:commons-lang3:${commonsLang3Version}"

implementation "org.jetbrains:annotations:13.0"

implementation "org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:${kotlinxSerializationVersion}"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:${kotlinxSerializationVersion}"
implementation "org.jetbrains.kotlin:kotlin-reflect"

testRuntimeOnly "org.junit.jupiter:junit-jupiter:${junitJupiterVersion}"
testImplementation "org.assertj:assertj-core:${assertjCoreVersion}"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}"
testImplementation "net.javacrumbs.json-unit:json-unit-assertj:${jsonUnitAssertJVersion}"

}

jar {
enabled = true
archiveClassifier = ''
}
bootJar.enabled = false

java {
withJavadocJar()
withSourcesJar()
}

kotlin {
jvmToolchain(17)
}

publishing {
publications {
mavenJava(MavenPublication) {
pom {
name = 'springwolf-kotlinx-serialization-model-converter'
description = 'Kotlinx serialization model converter beans for Springwolf'
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.addons.kotlinx_serialization_model_converter.configuration;

import io.github.springwolf.addons.kotlinx_serialization_model_converter.converter.KotlinxSerializationModelConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Spring AutoConfiguration adding an {@link KotlinxSerializationModelConverter} Bean to the spring context.
*/
@Configuration(proxyBeanMethods = false)
public class KotlinxSerializationModelConverterAutoConfiguration {

@Bean
public KotlinxSerializationModelConverter kotlinxSerializationTypeConverter(
@Value("${springwolf.use-fqn}") boolean useFqn) {
return new KotlinxSerializationModelConverter(useFqn);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package io.github.springwolf.addons.kotlinx_serialization_model_converter.converter

import io.swagger.v3.core.converter.AnnotatedType
import io.swagger.v3.core.converter.ModelConverter
import io.swagger.v3.core.converter.ModelConverterContext
import io.swagger.v3.core.util.RefUtils
import io.swagger.v3.oas.models.media.ArraySchema
import io.swagger.v3.oas.models.media.MapSchema
import io.swagger.v3.oas.models.media.ObjectSchema
import io.swagger.v3.oas.models.media.Schema
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaType
import kotlin.reflect.jvm.jvmErasure

class KotlinxSerializationModelConverter(private val useFqn: Boolean = false) : ModelConverter {

override fun resolve(
annotatedType: AnnotatedType, context: ModelConverterContext, chain: Iterator<ModelConverter>
): Schema<*>? {
if (annotatedType.type is Class<*>) {
val kClass = (annotatedType.type as Class<*>).kotlin
val isKotlinSerializable = kClass.findAnnotation<Serializable>() != null

if (isKotlinSerializable) {
val schema = ObjectSchema()
schema.nullable = false
schema.name = kClass.simpleName

kClass.memberProperties.forEach { property ->
val propertySchema = getPropertySchema(property, context)

schema.addProperty(propertySchema.name, propertySchema)
if (!propertySchema.nullable) {
schema.addRequiredItem(propertySchema.name)
}
}

return schema
}
}

return if ((chain.hasNext())) chain.next().resolve(annotatedType, context, chain) else null
}

private fun getPropertyName(property: KProperty1<*, *>): String {
val serialNameAnnotation = property.findAnnotation<SerialName>()
if (serialNameAnnotation != null) {
return serialNameAnnotation.value
}
return property.name
}

private fun getPropertySchema(property: KProperty1<*, *>, context: ModelConverterContext): Schema<*> {
val propertySchema: Schema<*>

val name = getPropertyName(property)

val propertyType = property.returnType.jvmErasure

when {
propertyType.isSubclassOf(List::class) -> {
propertySchema = ArraySchema()
val value = (property.returnType.javaType as ParameterizedType).actualTypeArguments[0]
propertySchema.items = resolveRefSchema(value, context)
}

propertyType.isSubclassOf(Set::class) -> {
propertySchema = ArraySchema()
val value = (property.returnType.javaType as ParameterizedType).actualTypeArguments[0]
propertySchema.items = resolveRefSchema(value, context)
propertySchema.uniqueItems = true
}

propertyType.isSubclassOf(Map::class) -> {
val mapType = (property.returnType.javaType as ParameterizedType)
val value = mapType.actualTypeArguments[1]
propertySchema = MapSchema().additionalProperties(context.resolve(AnnotatedType(value)))
}

else -> {
propertySchema = resolveRefSchema(property.returnType.javaType, context)
}
}
propertySchema.nullable = property.returnType.isMarkedNullable
propertySchema.name = name

return propertySchema
}

private fun resolveRefSchema(type: Type, context: ModelConverterContext): Schema<*> {
val typeSchema = context.resolve(AnnotatedType(type))
if (typeSchema.type == "object") {
val name = getClassName(type as Class<*>)
context.defineModel(name, typeSchema)
return Schema<Any>().`$ref`(RefUtils.constructRef(name))
}
return typeSchema
}

private fun getClassName(type: Class<*>): String {
val qualifiedName = type.name
return if (useFqn) {
qualifiedName
} else {
qualifiedName.substringAfterLast(".")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.github.springwolf.addons.kotlinx_serialization_model_converter.configuration.KotlinxSerializationModelConverterAutoConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
package io.github.springwolf.addons.kotlinx_serialization_model_converter.converter;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public final class ClasspathUtil {
private ClasspathUtil() {}

public static String readAsString(String resourceName) throws IOException {
try (InputStream inputStream = ClasspathUtil.class.getResourceAsStream(resourceName)) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
Loading