Skip to content

Commit

Permalink
add support for kotlinx serialization SerialName
Browse files Browse the repository at this point in the history
  • Loading branch information
tabilzad committed Aug 20, 2024
1 parent df39cde commit 4643405
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 62 deletions.
1 change: 1 addition & 0 deletions create-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
implementation(libs.bundles.jackson)
implementation(libs.kotlinReflect)
implementation(libs.moshi)
implementation(libs.serialization)

testImplementation(libs.classGraph)
testImplementation(libs.compilerTest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.github.tabilzad.ktor.OpenApiSpec.ObjectType
import io.github.tabilzad.ktor.PluginConfiguration
import io.github.tabilzad.ktor.annotations.KtorDescription
import io.github.tabilzad.ktor.annotations.KtorFieldDescription
import io.github.tabilzad.ktor.k2.MoshiJsonNameResolver.getMoshiJsonName
import io.github.tabilzad.ktor.k2.JsonNameResolver.getCustomNameFromAnnotation
import io.github.tabilzad.ktor.names
import io.github.tabilzad.ktor.visitors.KtorDescriptionBag
import io.github.tabilzad.ktor.visitors.toSwaggerType
Expand Down Expand Up @@ -340,7 +340,7 @@ internal class ClassDescriptorVisitorK2(
}

private fun FirProperty.findName(): String {
return getMoshiJsonName(this, session) ?: name.asString()
return getCustomNameFromAnnotation(this, session) ?: name.asString()
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ object ClassIds {
val KTOR_DSL_ANNOTATION = ClassId(FqName("io.ktor.util"), FqName("KtorDsl"), false)
val TRANSIENT_ANNOTATION = ClassId(FqName("kotlin.jvm"), FqName("Transient"), false)

val MOSHI_JSON_ANNOTATION_FQ_NAME = FqName("com.squareup.moshi.Json")
val MOSHI_JSON_ANNOTATION_NAME_ARGUMENT_IDENTIFIER: Name = Name.identifier("name")
}

enum class SerializationFramework(val fqName: FqName, val identifier: Name) {
MOSHI(FqName("com.squareup.moshi.Json"), Name.identifier("name")),
KOTLINX(FqName("kotlinx.serialization.SerialName"), Name.identifier("value"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.github.tabilzad.ktor.k2

import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.getStringArgument
import org.jetbrains.kotlin.fir.declarations.primaryConstructorIfAny
import org.jetbrains.kotlin.fir.declarations.utils.isData
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.resolve.fqName
import org.jetbrains.kotlin.fir.resolve.getContainingClass

object JsonNameResolver {

fun getCustomNameFromAnnotation(
property: FirProperty,
session: FirSession,
): String? = getCustomNameFromPropertyAnnotation(property, session)
?: getMoshiNameFromDataClassConstructorParamAnnotation(property, session)

private fun getCustomNameFromPropertyAnnotation(
property: FirProperty,
session: FirSession,
): String? = property.annotations.getCustomNameFromAnnotation(session)

private fun List<FirAnnotation>?.getCustomNameFromAnnotation(
session: FirSession
): String? = this?.let { annotations ->
SerializationFramework.entries.mapNotNull {
annotations
.find { annotation -> annotation.fqName(session) == it.fqName }
?.getStringArgument(it.identifier, session)
}
}?.firstOrNull()

private fun getMoshiNameFromDataClassConstructorParamAnnotation(
property: FirProperty,
session: FirSession,
): String? = property.getContainingClass(session)
?.takeIf { it.isData }
?.primaryConstructorIfAny(session)
?.valueParameterSymbols
?.find { it.name == property.name }
?.annotations
?.getCustomNameFromAnnotation(session)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ class K2StabilityTest {
result.assertWith(expected)
}

@Test
fun `should handle kotlinx serialization annotated properties and data class constructor parameters`() {
val (source, expected) = loadSourceAndExpected("SerializationAnnotated")
generateCompilerTest(testFile, source, hideTransient = false, hidePrivate = false)
val result = testFile.readText()
result.assertWith(expected)
}

@Test
fun `should resolve request body schema directly from http method parameter if it's not a resource`() {
val (source, expected) = loadSourceAndExpected("RequestBodyParam")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,5 @@ private val deps = arrayOf(
"ktor-http:2.2.4",
"kotlinx-coroutines-core:1.6.4",
"moshi:1.14.0",
"kotlinx-serialization-core:1.7.1"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"openapi" : "3.1.0",
"info" : {
"title" : "Open API Specification",
"description" : "test",
"version" : "1.0.0"
},
"paths" : {
"/v1/action" : {
"post" : {
"requestBody" : {
"required" : true,
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/sources.SerialNameAnnotated"
}
}
}
}
}
}
},
"components" : {
"schemas" : {
"sources.SerialNameAnnotated" : {
"type" : "object",
"properties" : {
"notAnnotated" : {
"type" : "string"
},
"serial_annotated_constructor_derived_property" : {
"type" : "string"
},
"serial_annotated_constructor_parameter" : {
"type" : "string"
},
"serial_annotated_lateinit_var" : {
"type" : "string"
},
"serial_annotated_mutable_property" : {
"type" : "string"
}
},
"required" : [ "serial_annotated_constructor_parameter", "notAnnotated", "serial_annotated_constructor_derived_property", "serial_annotated_lateinit_var" ]
}
}
}
}
35 changes: 35 additions & 0 deletions create-plugin/src/test/resources/sources/SerializationAnnotated.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package sources

import io.github.tabilzad.ktor.annotations.GenerateOpenApi
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.routing.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class SerialNameAnnotated(
@SerialName("serial_annotated_constructor_parameter")
val serialNameAnnotatedConstructorParameter: String,
val notAnnotated: String,
) {
@SerialName("serial_annotated_constructor_derived_property")
val serialNameAnnotatedConstructorDerivedProperty = notAnnotated + ""

@SerialName("serial_annotated_mutable_property")
var serialNameAnnotatedProperty: String? = null

@SerialName("serial_annotated_lateinit_var")
lateinit var serialNameAnnotatedLateinitVar: String
}

@GenerateOpenApi
fun Application.moduleSerialization() {
routing {
route("/v1") {
post("/action") {
call.receive<SerialNameAnnotated>()
}
}
}
}
19 changes: 10 additions & 9 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,24 @@ mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.28.0" }
[bundles]
jackson = ["jacksonKotlin", "jacksonYaml"]
ktor = ["ktor", "ktorNetty", "ktorResources"]
kotlinGradle = ["kotlinGradleApi","kotlinGradle"]
kotlinGradle = ["kotlinGradleApi", "kotlinGradle"]

[libraries]
kotlinCompiler = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlinVersion" }
assertJ = {module = "org.assertj:assertj-core", version.ref = "assertJVerison"}
classGraph = {module="io.github.classgraph:classgraph", version.ref="classgraph"}
junit = {module = "org.junit:junit-bom", version = "5.10.0"}
junitJupiter = {module = "org.junit.jupiter:junit-jupiter"}
compilerTest = {module = "dev.zacsweers.kctfork:core", version.ref = "compilerTestingVersion"}
assertJ = { module = "org.assertj:assertj-core", version.ref = "assertJVerison" }
classGraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" }
junit = { module = "org.junit:junit-bom", version = "5.10.0" }
junitJupiter = { module = "org.junit.jupiter:junit-jupiter" }
compilerTest = { module = "dev.zacsweers.kctfork:core", version.ref = "compilerTestingVersion" }
jacksonKotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jacksonVersion" }
jacksonYaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jacksonVersion" }
ktor = { module = "io.ktor:ktor", version.ref = "ktorVersion" }
ktorNetty = { module = "io.ktor:ktor-server-netty", version.ref = "ktorVersion" }
ktorResources = { module = "io.ktor:ktor-server-resources", version.ref = "ktorVersion" }
kotlinGradleApi = {module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlinVersion"}
kotlinGradle = {module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinVersion"}
kotlinGradleApi = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlinVersion" }
kotlinGradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinVersion" }
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlinVersion" }
moshi = { module = "com.squareup.moshi:moshi", version = "1.14.0"}
moshi = { module = "com.squareup.moshi:moshi", version = "1.14.0" }
serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version = "1.7.1" }


0 comments on commit 4643405

Please sign in to comment.