Skip to content

Commit

Permalink
feat: protobuf java converters (#382)
Browse files Browse the repository at this point in the history
  • Loading branch information
jvgelder authored Nov 15, 2022
1 parent 7b39e44 commit 20f8a4f
Show file tree
Hide file tree
Showing 30 changed files with 8,384 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Added Support for Custom objects from Java generated protobuf classes

### Added

### Changed
Expand Down
2 changes: 2 additions & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Summary

* [Introduction](index.md)
* [Helpers](helpers/index.md)
* [Protobuf java converter](helpers/protobuf_java_converter.md)
* [Plugins](plugins/index.md)
* [Notarized Application](plugins/notarized_application.md)
* [Notarized Route](plugins/notarized_route.md)
Expand Down
7 changes: 7 additions & 0 deletions docs/helpers/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Helper modules help you to interact with Kompendium.

Some functionality is not possible or difficult to do with Kompendium by default. Modules in this folder help you to get
functionality that would otherwise be difficult.

The first one of which is [Protobuf java converter](protobuf_java_converter.md) which translates java protobuf classes
to `customTypes` entries.
153 changes: 153 additions & 0 deletions docs/helpers/protobuf_java_converter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
The `Protobuf java converter` functions allow you to generate documentation from your generated Java classes.
Since Kompendium relies a lot on `KProperties` we have yet to find a way to connect this with our Java.
For now the documentation is generated for the `customTypes` in `NotarizedApplication`.

## Usage with Kotlinx

setup:
```kotlin
install(ContentNegotiation) {
json(Json {
encodeDefaults = false
// Combine the kompendium serializers with your custom java proto serializers
serializersModule =
KompendiumSerializersModule.module + SerializersModule { serializersModule = yourCustomProtoSerializers }
})
}
```

For one message and all its nested sub messages:
```kotlin
private fun Application.mainModule() {
// ...
install(NotarizedApplication()) {
spec = baseSpec
customTypes = MyJavaProto.getDefaultInstance().createCustomTypesForTypeAndSubTypes().toMap()
}
}
```

For multiple messages and their submesages:
```kotlin
private fun Application.mainModule() {
// ...
install(NotarizedApplication()) {
spec = baseSpec
customTypes = MyJavaProto.getDefaultInstance().createCustomTypesForTypeAndSubTypes()
.plus(AnotherJavaProto.getDefaultInstance().createCustomTypesForTypeAndSubTypes()).toMap()
}
}
```

### Example User

The protobuf that is used on our endpoint
```proto
message User {
string id = 1;
string email = 2;
string mobile_phone = 3;
string name = 4;
}
```

A custom serializer deserializer:
```kotlin
@OptIn(ExperimentalSerializationApi::class)
object UserSerializer : KSerializer<User> {

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("User") {
element("id", serialDescriptor<String>())
element("email", serialDescriptor<String>())
element("mobile_phone", serialDescriptor<String>())
element("name", serialDescriptor<String>())
}

override fun deserialize(decoder: Decoder): User {
return decoder.decodeStructure(descriptor) {
var id: String? = null
var email: String? = null
var mobilePhone: String? = null
var name: String? = null

loop@ while (true) {
when (val index = decodeElementIndex(descriptor)) {
CompositeDecoder.DECODE_DONE -> break@loop
0 -> id = decodeStringElement(descriptor, index)
1 -> email = decodeStringElement(descriptor, index)
2 -> mobilePhone = decodeStringElement(descriptor, index)
3 -> name = decodeStringElement(descriptor, index)
else -> throw RuntimeException(
"Unexpected index field ${descriptor.getElementName(index)}"
)
}
}
// building the protobuf object
val user = User.newBuilder().apply {
id?.let { v -> this.id = v }
email?.let { v -> this.email = v }
mobilePhone?.let { v -> this.mobilePhone = v }
name?.let { v -> this.name = v }
}.build()
user
}
}

override fun serialize(encoder: Encoder, value: User) {
encoder.encodeStructure(descriptor) {
encodeStringElement(descriptor, 0, value.id)
encodeStringElement(descriptor, 1, value.email)
encodeStringElement(descriptor, 2, value.mobilePhone)
encodeStringElement(descriptor, 3, value.name)
}
}
}
```
Setting the content type:
```kotlin
install(ContentNegotiation) {
json(Json {
encodeDefaults = false
// Combine the kompendium serializers with your custom java proto serializers
serializersModule =
KompendiumSerializersModule.module + SerializersModule {
serializersModule = SerializersModule {
contextual(UserSerializer)
}
}
})
}
```
The installation of the noterized application:
```kotlin
install(NotarizedApplication()) {
spec = baseSpec
customTypes = User.getDefaultInstance().createCustomTypesForTypeAndSubTypes().toMap()
}
```
Route configuration as you normally would with one exception which is `createType()` to create kotlin type from a javaClass.

```kotlin
private fun Route.userDocumentation() {
install(NotarizedRoute()) {
post = PostInfo.builder {
summary("My User API")
description("Create a user")
request {
requestType(User::class.createType())
description("My user creation object")
}
response {
responseCode(HttpStatusCode.OK)
responseType(CreateUserResponse::class.createType())
description("Returns simulation object")
}
canRespond {
responseCode(HttpStatusCode.NotFound)
responseType<String>()
description("Indicates that the user could not be found")
}
}
}
}
```
1 change: 1 addition & 0 deletions playground/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
implementation(projects.kompendiumCore)
implementation(projects.kompendiumLocations)
implementation(projects.kompendiumResources)
implementation(projects.kompendiumProtobufJavaConverter)

// Ktor
val ktorVersion: String by project
Expand Down
43 changes: 43 additions & 0 deletions protobuf-java-converter/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
id("io.bkbn.sourdough.library.jvm")
id("io.gitlab.arturbosch.detekt")
id("com.adarshr.test-logger")
id("maven-publish")
id("java-library")
id("signing")
id("org.jetbrains.kotlinx.kover")
}

sourdoughLibrary {
libraryName.set("Kompendium Protobuf java converter")
libraryDescription.set("Converts Java protobuf generated classes to custom type maps")
compilerArgs.set(listOf("-opt-in=kotlin.RequiresOptIn"))
}

dependencies {
// Versions
val detektVersion: String by project


implementation(projects.kompendiumJsonSchema)
implementation("com.google.protobuf:protobuf-java:3.21.9")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.21")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")

// Formatting
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion")
testImplementation(testFixtures(projects.kompendiumCore))
}


testing {

suites {
named("test", JvmTestSuite::class) {
useJUnitJupiter()
}
}
}

Loading

0 comments on commit 20f8a4f

Please sign in to comment.