Kotlin DTO generator for Protobuf Messages

Overview

Kotlin DTO Generator for gRPC

The protobuf plugin generates DTO classes for the corresponding messages in your *.proto files.

The data classes in Kotlin is more convenient then the builders.

The generator makes the next steps:

  1. Create a new DTO class with a simple name <message_class_name>*Dto* inside a package <java_package>*.dto*. Both suffixes for class and package are customizable.
  2. Create to additional extension functions inside each file: toDto and toGrpc

The generator creates new Dto class with simple name <message_class_name>*Dto*. The prefix is customizable. The generator creates two additional extensions functions toDto and toGrpc

Some code taken from - https://github.com/grpc/grpc-kotlin. Remove as soon as possible.

Table of content

  1. How to use
  2. Options
  3. Examples
    1. Primitives
    2. Enum
    3. One Of
    4. Optional
    5. Repeated
    6. Special scenarios

How to use

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:${rootProject.ext["protobufVersion"]}"
    }
    plugins {
        id("grpc") {
            artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}"
        }
        id("grpcdtokt") {
            artifact = "INPROGRESS"
        }
    }
    generateProtoTasks {
        all().forEach {
            if (it.name.startsWith("generateTestProto")) {
                it.dependsOn("jar")
            }

            it.plugins {
                id("grpc")
                id("grpcdtokt") {
                    option("dto_suffix=Dto")
                    option("dto_package_suffix=dto")
                }
            }
        }
    }
}

Options

Current implementation has the next options:

  1. dto_suffix - DTO class suffix. Default - Dto (example: dto_suffix=DTO)
  2. dto_package_suffix - DTO class package suffix. New package suffix adds to default package for proto objects. Default - dto (example: dto_package_suffix=test.example)

Examples

Primitives

All primitives has a default value.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.avlasov.test.primitives";

package test.primitives;

message TestPrimitive {
  string stringValue = 1;
  double doubleValue = 2;
  bool booleanValue = 3;
  fixed32 fixed32Value = 4;
  fixed64 fixed64Value = 5;
  float floatValue = 6;
  int32 int32Value = 7;
  int64 int64Value = 8;
  sfixed32 sFixed32Value = 9;
  sfixed64 sFixed64Value = 10;
  sint32 sInt32Value = 11;
  sint64 sInt64Value = 12;
  uint32 uInt32Value = 13;
  uint64 uInt64Value = 14;
}
package org.avlasov.test.primitives.dto

import kotlin.Boolean
import kotlin.Double
import kotlin.Float
import kotlin.Int
import kotlin.Long
import kotlin.String
import org.avlasov.test.primitives.TestPrimitive

data class TestPrimitiveDto(
    val stringValue: String = "",
    val doubleValue: Double = 0.0,
    val booleanValue: Boolean = false,
    val fixed32Value: Int = 0,
    val fixed64Value: Long = 0,
    val floatValue: Float = 0F,
    val int32Value: Int = 0,
    val int64Value: Long = 0,
    val sFixed32Value: Int = 0,
    val sFixed64Value: Long = 0,
    val sInt32Value: Int = 0,
    val sInt64Value: Long = 0,
    val uInt32Value: Int = 0,
    val uInt64Value: Long = 0
)

fun TestPrimitive.toDto(): TestPrimitiveDto =
	TestPrimitiveDto(
		stringValue = stringValue,
		doubleValue = doubleValue,
		booleanValue = booleanValue,
		fixed32Value = fixed32Value,
		fixed64Value = fixed64Value,
		floatValue = floatValue,
		int32Value = int32Value,
		int64Value = int64Value,
		sFixed32Value = sFixed32Value,
		sFixed64Value = sFixed64Value,
		sInt32Value = sInt32Value,
		sInt64Value = sInt64Value,
		uInt32Value = uInt32Value,
		uInt64Value = uInt64Value
	)

fun TestPrimitiveDto.toGrpc(): TestPrimitive =
	TestPrimitive.newBuilder().also {
    	it.stringValue = stringValue
    	it.doubleValue = doubleValue
    	it.booleanValue = booleanValue
    	it.fixed32Value = fixed32Value
    	it.fixed64Value = fixed64Value
    	it.floatValue = floatValue
    	it.int32Value = int32Value
    	it.int64Value = int64Value
    	it.sFixed32Value = sFixed32Value
    	it.sFixed64Value = sFixed64Value
    	it.sInt32Value = sInt32Value
    	it.sInt64Value = sInt64Value
    	it.uInt32Value = uInt32Value
    	it.uInt64Value = uInt64Value
    }
    .build()

Enum

DTO enum class won’t have UNRECOGNISED value. Default value for a enum class as a field is null

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.avlasov.test.enums";

package test.enums;

enum TestEnum {
  FIRST = 0;
  SECOND = 1;
  THIRD = 2;
}
package org.avlasov.test.enums.dto

import org.avlasov.test.enums.TestEnum

enum class TestEnumDto {
    FIRST,

    SECOND,

    THIRD
}

fun TestEnum.toDto(): TestEnumDto = TestEnumDto.valueOf(name)

fun TestEnumDto.toGrpc(): TestEnum = TestEnum.valueOf(name)

Message filed example

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.avlasov.test.oneof";

package test.oneof;

enum OneOfEnum {
  ONE_OF_ELEMENT = 0;
}

message OneOfMessageFirst {
  OneOfEnum enumValue = 1;
}
package org.avlasov.test.oneof.dto

import org.avlasov.test.oneof.OneOfMessageFirst

data class OneOfMessageFirstDto(
    val enumValue: OneOfEnumDto? = null
)

fun OneOfMessageFirst.toDto(): OneOfMessageFirstDto =
	OneOfMessageFirstDto(
		enumValue = enumValue.toDto()
	)

fun OneOfMessageFirstDto.toGrpc(): OneOfMessageFirst =
	OneOfMessageFirst.newBuilder().also {
        if (enumValue != null) {
            it.enumValue = enumValue.toGrpc()
        }
    }
    .build()

One Of

By default all objects under oneOf tag is nullable types. You can set only one object. The generator set value if and only if Protobuf message has the value and if DTO value is not null.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.avlasov.test.oneof";

package test.oneof;

enum OneOfEnum {
  ONE_OF_ELEMENT = 0;
}

message OneOfMessageFirst {
  OneOfEnum enumValue = 1;
}

message OneOfMessageSecond {
  double doubleValue = 1;
}

message TestOneOf {
  oneof simple_oneOf {
      string stringOneOf = 1;
      OneOfMessageFirst firstMessage = 2;
      OneOfMessageSecond secondMessage = 3;
  }
}
package org.avlasov.test.oneof.dto

import org.avlasov.test.oneof.OneOfEnum

enum class OneOfEnumDto {
    ONE_OF_ELEMENT
}

fun OneOfEnum.toDto(): OneOfEnumDto = OneOfEnumDto.valueOf(name)

fun OneOfEnumDto.toGrpc(): OneOfEnum = OneOfEnum.valueOf(name)
package org.avlasov.test.oneof.dto

import org.avlasov.test.oneof.OneOfMessageFirst

data class OneOfMessageFirstDto(
    val enumValue: OneOfEnumDto? = null
)

fun OneOfMessageFirst.toDto(): OneOfMessageFirstDto =
	OneOfMessageFirstDto(
		enumValue = enumValue.toDto()
	)

fun OneOfMessageFirstDto.toGrpc(): OneOfMessageFirst =
	OneOfMessageFirst.newBuilder().also {
        if (enumValue != null) {
            it.enumValue = enumValue.toGrpc()
        }
    }
    .build()
package org.avlasov.test.oneof.dto

import kotlin.Double
import org.avlasov.test.oneof.OneOfMessageSecond

data class OneOfMessageSecondDto(
    val doubleValue: Double = 0.0
)

fun OneOfMessageSecond.toDto(): OneOfMessageSecondDto =
	OneOfMessageSecondDto(
		doubleValue = doubleValue
	)

fun OneOfMessageSecondDto.toGrpc(): OneOfMessageSecond =
	OneOfMessageSecond.newBuilder().also {
    	it.doubleValue = doubleValue
    }
    .build()
package org.avlasov.test.oneof.dto

import kotlin.String
import org.avlasov.test.oneof.TestOneOf

data class TestOneOfDto(
    val stringOneOf: String? = null,
    val firstMessage: OneOfMessageFirstDto? = null,
    val secondMessage: OneOfMessageSecondDto? = null
)

fun TestOneOf.toDto(): TestOneOfDto =
	TestOneOfDto(
		stringOneOf = if (hasStringOneOf()) stringOneOf else null,
		firstMessage = if (hasFirstMessage()) firstMessage.toDto() else null,
		secondMessage = if (hasSecondMessage()) secondMessage.toDto() else null
	)

fun TestOneOfDto.toGrpc(): TestOneOf =
	TestOneOf.newBuilder().also {
        if (stringOneOf != null) {
            it.stringOneOf = stringOneOf
        }
        if (firstMessage != null) {
            it.firstMessage = firstMessage.toGrpc()
        }
        if (secondMessage != null) {
            it.secondMessage = secondMessage.toGrpc()
        }
    }
    .build()

Optional

Any field marked as optional is nullable by default.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.avlasov.test.optional";

package test.optional;

message OptionalObject {
  bool test = 1;
}

message TestOptional {
  optional OptionalObject optionalObject = 1;
  optional string str = 2;
}
package org.avlasov.test.optional.dto

import kotlin.Boolean
import org.avlasov.test.optional.OptionalObject

data class OptionalObjectDto(
    val test: Boolean = false
)

fun OptionalObject.toDto(): OptionalObjectDto =
	OptionalObjectDto(
		test = test
	)

fun OptionalObjectDto.toGrpc(): OptionalObject =
	OptionalObject.newBuilder().also {
    	it.test = test
    }
    .build()
package org.avlasov.test.optional.dto

import kotlin.String
import org.avlasov.test.optional.TestOptional

data class TestOptionalDto(
    val optionalObject: OptionalObjectDto? = null,
    val str: String? = null
)

fun TestOptional.toDto(): TestOptionalDto =
	TestOptionalDto(
		optionalObject = if (hasOptionalObject()) optionalObject.toDto() else null,
		str = if (hasStr()) str else null
	)

fun TestOptionalDto.toGrpc(): TestOptional =
	TestOptional.newBuilder().also {
        if (optionalObject != null) {
            it.optionalObject = optionalObject.toGrpc()
        }
        if (str != null) {
            it.str = str
        }
    }
    .build()

Repeated

By default all fields marked as repeated has default value listOf(). The content of the repeated list converts from gRPC -> DTO and vice versa.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.avlasov.test.oneof";

package test.oneof;

enum OneOfEnum {
  ONE_OF_ELEMENT = 0;
}

message OneOfMessageFirst {
  OneOfEnum enumValue = 1;
}

message OneOfMessageSecond {
  double doubleValue = 1;
}

message TestOneOf {
  oneof simple_oneOf {
      string stringOneOf = 1;
      OneOfMessageFirst firstMessage = 2;
      OneOfMessageSecond secondMessage = 3;
  }
}
package org.avlasov.test.repeated.dto

import kotlin.Boolean
import org.avlasov.test.repeated.RepeatedObject

data class RepeatedObjectDto(
    val test: Boolean = false
)

fun RepeatedObject.toDto(): RepeatedObjectDto =
	RepeatedObjectDto(
		test = test
	)

fun RepeatedObjectDto.toGrpc(): RepeatedObject =
	RepeatedObject.newBuilder().also {
    	it.test = test
    }
    .build()
package org.avlasov.test.repeated.dto

import kotlin.Int
import kotlin.String
import kotlin.collections.List
import org.avlasov.test.repeated.TestRepeated

data class TestRepeatedDto(
    val objects: List<RepeatedObjectDto> = listOf(),
    val strings: List<String> = listOf(),
    val ints: List<Int> = listOf()
)

fun TestRepeated.toDto(): TestRepeatedDto =
	TestRepeatedDto(
		objects = objectsList.map { it.toDto() },
		strings = stringsList,
		ints = intsList
	)

fun TestRepeatedDto.toGrpc(): TestRepeated =
	TestRepeated.newBuilder().also {
    	it.addAllObjects(objects.map { o -> o.toGrpc() })
    	it.addAllStrings(strings)
    	it.addAllInts(ints)
    }
    .build()

Special scenarios

One of the special scenarios is the message that not generates as DTO. For example: type - google.protobuf.Any

NOTE At this moment the feature is not fully covered. Please, fill free to contribute or create an issue.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.avlasov.test.special";

import "google/protobuf/any.proto";

package test.special;

message TestSpecial {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}
package org.avlasov.test.special.dto

import com.google.protobuf.Any
import kotlin.String
import kotlin.collections.List
import org.avlasov.test.special.TestSpecial

data class TestSpecialDto(
    val message: String = "",
    val details: List<Any> = listOf()
)

fun TestSpecial.toDto(): TestSpecialDto =
	TestSpecialDto(
		message = message,
		details = detailsList
	)

fun TestSpecialDto.toGrpc(): TestSpecial =
	TestSpecial.newBuilder().also {
    	it.message = message
    	it.addAllDetails(details)
    }
    .build()
You might also like...
Kotlin-oop - Repositório criado para ser utilizado pelo projeto de Kotlin OOP desenvolvido em Kotlin nas aulas feitas através da plataforma Alura.

Projeto React OOP Repositório criado para ser utilizado pelo projeto de Kotlin OOP desenvolvido em Kotlin nas aulas feitas através da plataforma Alura

Kotlin-koans - Kotlin Koans are a series of exercises to get you familiar with the Kotlin Syntax

kotlin-koans-edu Kotlin Koans are a series of exercises to get you familiar with

Kotlin TodoMVC – full-stack Kotlin application demo

Kotlin full stack TodoMVC This project is an example implementation of the TodoMVC app written in Kotlin. More specifically, it's the Kotlin port of t

Integration Testing Kotlin Multiplatform Kata for Kotlin Developers. The main goal is to practice integration testing using Ktor and Ktor Client Mock
Integration Testing Kotlin Multiplatform Kata for Kotlin Developers. The main goal is to practice integration testing using Ktor and Ktor Client Mock

This kata is a Kotlin multiplatform version of the kata KataTODOApiClientKotlin of Karumi. We are here to practice integration testing using HTTP stub

Small kotlin library for persisting _single instances_ of kotlin data classes
Small kotlin library for persisting _single instances_ of kotlin data classes

PerSista Small library for persisting single instances of kotlin data classes. NB: PerSista uses typeOf() internally which is marked as @ExperimentalS

Kotlin Leaning Notes from Udacity Course | Kotlin Bootcamp for Programmers by Google
Kotlin Leaning Notes from Udacity Course | Kotlin Bootcamp for Programmers by Google

Kotlin Beginners Notes These are all personal notes taken from the Udacity Course (ud9011) of Kotlin Bootcamp for Programmers by Google as well as oth

Saga pattern implementation in Kotlin build in top of Kotlin's Coroutines.

Module Saga Website can be found here Add in build.gradle.kts repositories { mavenCentral() } dependencies { implementation("io.github.nomisr

Kotlin microservices with REST, and gRPC using BFF pattern. This repository contains backend services. Everything is dockerized and ready to
Kotlin microservices with REST, and gRPC using BFF pattern. This repository contains backend services. Everything is dockerized and ready to "Go" actually "Kotlin" :-)

Microservices Kotlin gRPC Deployed in EC2, Check it out! This repo contains microservices written in Kotlin with BFF pattern for performing CRUD opera

A sample skeleton backend app built using Spring Boot kotlin, Expedia Kotlin Graphql, Reactive Web that can be deployed to Google App Engine Flexible environmennt

spring-kotlin-gql-gae This is a sample skeleton of a backend app that was built using: Spring Boot(Kotlin) Reactive Web Sprinng Data R2DBC with MYSQL

Owner
Vlasov Artem
Vlasov Artem
Discode - Discord-like app that executes code in messages

Like Discord, but you can paste and run code in the chat About This is a Discord

Iulian Rusu 1 Jan 24, 2022
A clean OpenAPI client generator for Kotlin multiplatform

kotlin-openapi-generator A not yet feature complete client generator. Features: generates 100% Kotlin multiplatform code does not generate any useless

Jakob K 5 Jun 6, 2022
Ktor OpenAPI Spec Generator

Kompendium What is Kompendium Kompendium is intended to be a minimally invasive OpenApi Specification generator for Ktor. Minimally invasive meaning t

Backbone 91 Jan 4, 2023
A metadata generator intended for the Android source tree

Metalava (Also known as "doclava2", but deliberately not named doclava2 since crucially it does not generate docs; it's intended only for metadata ext

null 0 Nov 17, 2021
Gradle plugin for Hugo static site generator

gradle-hugo-plugin Wrapper for Hugo static site generator. The plugin provides a declarative approach for the Hugo binary used to build the static sit

François Staudt 10 Jun 28, 2022
Event State Processor Generator plugin is compatible with IntelliJ and Android Studio.

Event State Processor Generator plugin is compatible with IntelliJ and Android Studio. It provides source code generation for the EventStateProcessor Library to increase code productivity in Flutter apps development.

Extreme Vietnam Public 2 Dec 7, 2021
A korge map generator for Pocket Palm Heroes remastered

Korge mapgen This is a korge map generator for Pocket Palm Heroes remastered. Algorithm Is based mostly on this presentation by Gus Smedstad, who is t

Alexey Kononov 4 Sep 6, 2022
This simple project will consist of an endless cat fact generator

MVVM is a structural design pattern and its based on the separation of the project structure into 3 main components: The Model, which is responsible f

Chinmay Deshpande 0 Dec 15, 2021
Repo: Programming problems with solutions in Kotlin to help avid Kotlin learners to get a strong hold on Kotlin programming.

Kotlin_practice_problems Repo: Programming problems with solutions in Kotlin to help avid Kotlin learners to get a strong hold on Kotlin programming.

Aman 0 Oct 14, 2021
Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP)

Mockative Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP). Installation Mockative uses KSP to generate

Mockative 121 Dec 26, 2022