Protobuf Message DSL
The Protobuf Message DSL generator creates lambda based builders and utility extensions for message types.
Getting Started
To enable this generator, at least one entry for protoBuilders
must be present within the configuration file supplied to the protoc-gen-kroto-plus
plugin. All available options are documented in ProtoBuildersGenOptions
protoBuilders:
- unwrapBuilders: false
useDslMarkers: true
{
"protoBuilders": [
{
"unwrapBuilders": false,
"useDslMarkers": true
}
]
}
proto_builders {
unwrap_builders: false
use_dsl_markers: true
}
Message DSL
The message DSL is achieved via a combination of top level and extension functions. Any message containing a field of type message will also receive an extension to allow construction of its nested field. The generated extensions allow composition of proto messages in a dsl style.
By default the generated utility methods for building messages are wrapped in an object similar to a proto outer class. The naming convention used for this outer object is {{java_outer_classname}}Builders
.
// Using default generator output
val starPlatinum = StandProtoBuilders.Stand {
...
}
// Using static import of the methods present
// in the 'StandProtoBuilders' object
val starPlatinum = Stand {
name = "Star Platinum"
powerLevel = 500
speed = 550
// Nested message field
attack {
name = "ORA ORA ORA"
damage = 100
range = Attack.Range.CLOSE
}
}
// starPlatinum.attack.damage == 100
For a more concise usage of the generated API is it recommended to statically importing the nested builder methods. Alternatively, the builder methods can be generated at the top level of the destination file without the wrapper object. Doing so may produce name collisions in certain circumstances. In these cases the generator will still implement these methods with a wrapper object to prevent breaking compilation. This output can be enabled using the following configuration option:
protoBuilders:
- unwrapBuilders: true
{
"protoBuilders": [
{
"unwrapBuilders": true
}
]
}
proto_builders {
unwrap_builders: true
}
DslMarker Support
Enabling DslMarker Support
Using dsl markers relies on protoc insertions. Care must be taken to ensure that the kroto-plus output directory is the same as the directory for generated java code.
Access to the parent lamba scope from within a nested builder scope can be restricted using Kotlin's DslMarker. This is done by generating an empty tagging interface annotated with @DslMarker
. The tagging interface is then inserted as a super type of the target messages builder. This is achieved using protoc insertions API, and more information regarding this API can be found in the official protobuf docs. This feature can be enabled using the following configuration option:
protoBuilders:
- useDslMarkers: true
{
"protoBuilders": [
{
"useDslMarkers": true
}
]
}
proto_builders {
use_dsl_markers: true
}
Protobuf Maven Plugin
Dsl markers cannot be configured when using the Protobuf Maven Plugin for code generation. The issue lies with the lack of support for protoc insertions in the Protobuf Maven Plugin. This is documented in depth in Kroto+ issue #28 on Github.
Message Utility Extensions
Convenience extensions are also generated for common message operations.
Copy Operator
// Generator Output
inline fun MyMessage.copy(block: MyMessage.Builder.() -> Unit): MyMessage =
this.toBuilder().apply(block).build()
// Example Usage
val myMessage = MyMessage {
count = 1
someField = "foo"
}
val myCopy = myMessage.copy {
someField = "bar"
}
myCopy.count == 1
myCopy.someField == "bar"
Plus Operator
// Generator Output
operator fun MyMessage.plus(other: MyMessage): MyMessage =
this.toBuilder().mergeFrom(other).build()
// Example Usage
val myMessage = MyMessage {
count = 1
someField = "foo"
}
val anotherMessage = MyMessage {
someField = "bar"
}
val mergedMessage = myMessage + anotherMessage
mergedMessage.count == 1
mergedMessage.someField == "bar"
OrDefault Operator
// Generator Output
fun MyMessage?.orDefault(): MyMessage =
this ?: MyMessage.getDefaultInstance()
// Example Usage
val myMessage: MyMessage? = null
myMessage.orDefault() == MyMessage.getDefaultInstance()
Updated almost 5 years ago