99package dev.restate.sdk.kotlin
1010
1111import dev.restate.sdk.common.DurablePromiseKey
12+ import dev.restate.sdk.common.RichSerde
1213import dev.restate.sdk.common.Serde
1314import dev.restate.sdk.common.StateKey
1415import java.nio.ByteBuffer
1516import java.nio.charset.StandardCharsets
1617import kotlin.reflect.typeOf
18+ import kotlinx.serialization.ExperimentalSerializationApi
1719import kotlinx.serialization.KSerializer
20+ import kotlinx.serialization.Serializable
21+ import kotlinx.serialization.builtins.ListSerializer
22+ import kotlinx.serialization.builtins.serializer
23+ import kotlinx.serialization.descriptors.PrimitiveKind
24+ import kotlinx.serialization.descriptors.SerialDescriptor
25+ import kotlinx.serialization.descriptors.StructureKind
26+ import kotlinx.serialization.encodeToString
1827import kotlinx.serialization.json.Json
28+ import kotlinx.serialization.json.JsonArray
29+ import kotlinx.serialization.json.JsonElement
1930import kotlinx.serialization.json.JsonNull
31+ import kotlinx.serialization.json.JsonTransformingSerializer
2032import kotlinx.serialization.serializer
2133
2234object KtStateKey {
@@ -70,12 +82,13 @@ object KtSerdes {
7082 }
7183
7284 /* * Creates a [Serde] implementation using the `kotlinx.serialization` json module. */
73- fun <T : Any ?> json (serializer : KSerializer <T >): Serde <T > {
74- return object : Serde <T > {
85+ inline fun <reified T : Any ? > json (serializer : KSerializer <T >): Serde <T > {
86+ return object : RichSerde <T > {
7587 override fun serialize (value : T ? ): ByteArray {
7688 if (value == null ) {
7789 return Json .encodeToString(JsonNull .serializer(), JsonNull ).encodeToByteArray()
7890 }
91+
7992 return Json .encodeToString(serializer, value).encodeToByteArray()
8093 }
8194
@@ -86,6 +99,76 @@ object KtSerdes {
8699 override fun contentType (): String {
87100 return " application/json"
88101 }
102+
103+ override fun jsonSchema (): String {
104+ val schema: JsonSchema = serializer.descriptor.jsonSchema()
105+ return Json .encodeToString(schema)
106+ }
89107 }
90108 }
109+
110+ @Serializable
111+ @PublishedApi
112+ internal data class JsonSchema (
113+ @Serializable(with = StringListSerializer ::class ) val type : List <String >? = null ,
114+ val format : String? = null ,
115+ ) {
116+ companion object {
117+ val INT = JsonSchema (type = listOf (" number" ), format = " int32" )
118+
119+ val LONG = JsonSchema (type = listOf (" number" ), format = " int64" )
120+
121+ val DOUBLE = JsonSchema (type = listOf (" number" ), format = " double" )
122+
123+ val FLOAT = JsonSchema (type = listOf (" number" ), format = " float" )
124+
125+ val STRING = JsonSchema (type = listOf (" string" ))
126+
127+ val BOOLEAN = JsonSchema (type = listOf (" boolean" ))
128+
129+ val OBJECT = JsonSchema (type = listOf (" object" ))
130+
131+ val LIST = JsonSchema (type = listOf (" array" ))
132+
133+ val ANY = JsonSchema ()
134+ }
135+ }
136+
137+ object StringListSerializer :
138+ JsonTransformingSerializer <List <String >>(ListSerializer (String .Companion .serializer())) {
139+ override fun transformSerialize (element : JsonElement ): JsonElement {
140+ require(element is JsonArray )
141+ return element.singleOrNull() ? : element
142+ }
143+ }
144+
145+ /* *
146+ * Super simplistic json schema generation. We should replace this with an appropriate library.
147+ */
148+ @OptIn(ExperimentalSerializationApi ::class )
149+ @PublishedApi
150+ internal fun SerialDescriptor.jsonSchema (): JsonSchema {
151+ var schema =
152+ when (this .kind) {
153+ PrimitiveKind .BOOLEAN -> JsonSchema .BOOLEAN
154+ PrimitiveKind .BYTE -> JsonSchema .INT
155+ PrimitiveKind .CHAR -> JsonSchema .STRING
156+ PrimitiveKind .DOUBLE -> JsonSchema .DOUBLE
157+ PrimitiveKind .FLOAT -> JsonSchema .FLOAT
158+ PrimitiveKind .INT -> JsonSchema .INT
159+ PrimitiveKind .LONG -> JsonSchema .LONG
160+ PrimitiveKind .SHORT -> JsonSchema .INT
161+ PrimitiveKind .STRING -> JsonSchema .STRING
162+ StructureKind .LIST -> JsonSchema .LIST
163+ StructureKind .MAP -> JsonSchema .OBJECT
164+ else -> JsonSchema .ANY
165+ }
166+
167+ // Add nullability constraint
168+ if (this .isNullable && schema.type != null ) {
169+ schema = schema.copy(type = schema.type.plus(" null" ))
170+ }
171+
172+ return schema
173+ }
91174}
0 commit comments