Skip to content

Commit 84a751a

Browse files
Enable Json Schema generation in kotlin
Fix test where a bad type was being used for serialization/deserialization
1 parent c86ae92 commit 84a751a

File tree

6 files changed

+50
-10
lines changed

6 files changed

+50
-10
lines changed

sdk-api-kotlin-gen/src/test/kotlin/dev/restate/sdk/kotlin/CodegenTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ class CodegenTest : TestDefinitions.TestSuite {
5757
@Exclusive
5858
suspend fun complexType(
5959
context: ObjectContext,
60-
request: Map<Output, List<out Input>>
61-
): Map<Input, List<out Output>> {
60+
request: Map<String, List<out Input>>
61+
): Map<String, List<out Output>> {
6262
return mapOf()
6363
}
6464
}

sdk-api-kotlin/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ dependencies {
1313
implementation(kotlinLibs.kotlinx.serialization.core)
1414
implementation(kotlinLibs.kotlinx.serialization.json)
1515

16+
implementation("io.bkbn:kompendium-json-schema:4.0.0-alpha")
17+
1618
implementation(coreLibs.log4j.api)
1719
implementation(platform(coreLibs.opentelemetry.bom))
1820
implementation(coreLibs.opentelemetry.kotlin)

sdk-api-kotlin/src/main/kotlin/dev/restate/sdk/kotlin/KtSerdes.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99
package dev.restate.sdk.kotlin
1010

1111
import dev.restate.sdk.common.DurablePromiseKey
12+
import dev.restate.sdk.common.RichSerde
1213
import dev.restate.sdk.common.Serde
1314
import dev.restate.sdk.common.StateKey
15+
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
16+
import io.bkbn.kompendium.json.schema.SchemaGenerator
17+
import io.bkbn.kompendium.json.schema.definition.JsonSchema
1418
import java.nio.ByteBuffer
1519
import java.nio.charset.StandardCharsets
1620
import kotlin.reflect.typeOf
@@ -70,12 +74,13 @@ object KtSerdes {
7074
}
7175

7276
/** 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> {
77+
inline fun <reified T : Any?> json(serializer: KSerializer<T>): Serde<T> {
78+
return object : RichSerde<T> {
7579
override fun serialize(value: T?): ByteArray {
7680
if (value == null) {
7781
return Json.encodeToString(JsonNull.serializer(), JsonNull).encodeToByteArray()
7882
}
83+
7984
return Json.encodeToString(serializer, value).encodeToByteArray()
8085
}
8186

@@ -86,6 +91,17 @@ object KtSerdes {
8691
override fun contentType(): String {
8792
return "application/json"
8893
}
94+
95+
override fun jsonSchema(): String {
96+
val schema =
97+
SchemaGenerator.fromTypeToSchema(
98+
type = typeOf<T>(),
99+
cache = mutableMapOf(),
100+
schemaConfigurator = KotlinXSchemaConfigurator(),
101+
)
102+
103+
return Json.encodeToString(JsonSchema.serializer(), schema)
104+
}
89105
}
90106
}
91107
}

sdk-common/src/main/java/dev/restate/sdk/common/RichSerde.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414
/**
1515
* Richer version of {@link Serde} containing schema information.
1616
*
17+
* <p>This API should be considered unstable to implement.
18+
*
1719
* <p>You can create one using {@link #withSchema(Object, Serde)}.
1820
*/
1921
public interface RichSerde<T extends @Nullable Object> extends Serde<T> {
2022

2123
/**
22-
* @return a Draft 2020-12 Json Schema
24+
* @return a Draft 2020-12 Json Schema. It should be self-contained, and MUST not contain refs to
25+
* files. If the schema shouldn't be serialized with Jackson, return a {@link String}
2326
*/
2427
Object jsonSchema();
2528

sdk-core/src/main/java/dev/restate/sdk/core/EndpointManifest.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import static dev.restate.sdk.core.ServiceProtocol.*;
1212

13+
import com.fasterxml.jackson.core.JsonProcessingException;
1314
import dev.restate.sdk.common.HandlerType;
1415
import dev.restate.sdk.common.RichSerde;
1516
import dev.restate.sdk.common.ServiceType;
@@ -108,8 +109,17 @@ private static Input convertHandlerInput(HandlerSpecification<?, ?> spec) {
108109
: new Input().withRequired(true).withContentType(acceptContentType);
109110

110111
if (spec.getRequestSerde() instanceof RichSerde) {
111-
input.setJsonSchema(
112-
Objects.requireNonNull(((RichSerde<?>) spec.getRequestSerde()).jsonSchema()));
112+
Object jsonSchema =
113+
Objects.requireNonNull(((RichSerde<?>) spec.getRequestSerde()).jsonSchema());
114+
if (jsonSchema instanceof String) {
115+
// We need to convert it to databind JSON value
116+
try {
117+
jsonSchema = MANIFEST_OBJECT_MAPPER.readTree((String) jsonSchema);
118+
} catch (JsonProcessingException e) {
119+
throw new RuntimeException("The schema generated by RichSerde is not a valid JSON", e);
120+
}
121+
}
122+
input.setJsonSchema(jsonSchema);
113123
}
114124
return input;
115125
}
@@ -123,8 +133,17 @@ private static Output convertHandlerOutput(HandlerSpecification<?, ?> spec) {
123133
.withSetContentTypeIfEmpty(false);
124134

125135
if (spec.getResponseSerde() instanceof RichSerde) {
126-
output.setJsonSchema(
127-
Objects.requireNonNull(((RichSerde<?>) spec.getResponseSerde()).jsonSchema()));
136+
Object jsonSchema =
137+
Objects.requireNonNull(((RichSerde<?>) spec.getResponseSerde()).jsonSchema());
138+
if (jsonSchema instanceof String) {
139+
// We need to convert it to databind JSON value
140+
try {
141+
jsonSchema = MANIFEST_OBJECT_MAPPER.readTree((String) jsonSchema);
142+
} catch (JsonProcessingException e) {
143+
throw new RuntimeException("The schema generated by RichSerde is not a valid JSON", e);
144+
}
145+
}
146+
output.setJsonSchema(jsonSchema);
128147
}
129148

130149
return output;

sdk-core/src/main/java/dev/restate/sdk/core/ServiceProtocol.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ static String serviceDiscoveryProtocolVersionToHeaderValue(
135135
"Service discovery protocol version '%s' has no header value", version.getNumber()));
136136
}
137137

138-
private static final ObjectMapper MANIFEST_OBJECT_MAPPER = new ObjectMapper();
138+
static final ObjectMapper MANIFEST_OBJECT_MAPPER = new ObjectMapper();
139139

140140
@JsonFilter("V2FieldsFilter")
141141
interface V2Mixin {}

0 commit comments

Comments
 (0)