Skip to content

Newspicel/protopoet

Repository files navigation

ProtoPoet

A Kotlin DSL for programmatically generating Protocol Buffer 3 (proto3) schema files.

ProtoPoet provides a type-safe builder API that makes it easy to generate .proto files from Kotlin code — useful for code generators, schema registries, or any tooling that needs to produce protobuf definitions at runtime.

Installation

Gradle (Kotlin DSL):

dependencies {
    implementation("dev.newspicel:protopoet:0.1.0")
}

Quick Start

import dev.newspicel.protopoet.*

val proto = protoFile {
    packageName = "example.v1"
    fileComment("Generated by ProtoPoet")

    import("google/protobuf/timestamp.proto")

    fileOption("java_package", "com.example.v1")

    message("Person") {
        comment("Represents a person")
        field(FieldType.STRING, "name", 1)
        field(FieldType.INT32, "age", 2)
        repeatedField(FieldType.STRING, "emails", 3)
        optionalField(FieldType.STRING, "nickname", 4)
    }

    enum("Status") {
        value("UNKNOWN", 0)
        value("ACTIVE", 1)
        value("INACTIVE", 2)
    }

    service("PersonService") {
        rpc("GetPerson") {
            request = "GetPersonRequest"
            response = "Person"
        }
    }
}

println(proto)

Output:

// Generated by ProtoPoet
syntax = "proto3";

package example.v1;

import "google/protobuf/timestamp.proto";

option java_package = "com.example.v1";

message Person {
  // Represents a person
  string name = 1;
  int32 age = 2;
  repeated string emails = 3;
  optional string nickname = 4;
}

enum Status {
  UNKNOWN = 0;
  ACTIVE = 1;
  INACTIVE = 2;
}

service PersonService {
  rpc GetPerson (GetPersonRequest) returns (Person);
}

Features

Messages

Messages support all proto3 field types, nested messages, nested enums, field comments, reserved fields, and message-level options.

message("Person") {
    comment("A person record")
    field(FieldType.STRING, "name", 1)
    repeatedField(FieldType.STRING, "tags", 2)
    optionalField(FieldType.BOOL, "active", 3)
    messageField("Address", "home_address", 4)
    mapField(FieldType.STRING, FieldType.STRING, "metadata", 5)

    oneof("contact") {
        field(FieldType.STRING, "phone", 6)
        field(FieldType.STRING, "email", 7)
    }

    message("Address") {
        field(FieldType.STRING, "street", 1)
        field(FieldType.STRING, "city", 2)
    }

    enum("PhoneType") {
        value("MOBILE", 0)
        value("HOME", 1)
    }

    reserved(10, 11, 12)
    reservedNames("old_field")
    reservedRange(100, 200)
}

Enums

enum("Status") {
    comment("Lifecycle status")
    allowAlias()
    value("UNKNOWN", 0)
    value("ACTIVE", 1)
    value("INACTIVE", 2)
    reserved(5, 6)
    reservedNames("DEPRECATED")
}

Services

Services support unary, client-streaming, server-streaming, and bidirectional-streaming RPCs with per-method options.

service("PersonService") {
    comment("Manages person records")

    rpc("GetPerson") {
        request = "GetPersonRequest"
        response = "Person"
    }

    rpc("ListPersons") {
        request = "ListPersonsRequest"
        response = "Person"
        responseStreaming = true
    }
}

Extensions

Define custom protobuf options via extend:

extension(OptionType.MESSAGE) {
    field(FieldType.STRING, "my_option", 51234)
}
// Automatically imports google/protobuf/descriptor.proto

File-level options

protoFile {
    fileOption("java_package", "com.example.v1")
    fileOption("java_multiple_files", true)
    fileOption("go_package", "example.com/v1")
}

Programmatic Builder API

In addition to the DSL, all specs expose a traditional builder API for cases where you construct definitions dynamically:

val msg = MessageSpec.builder("Person")
    .addField(MessageFieldSpec.builder(FieldType.STRING, "name", 1).build())
    .addField(MessageFieldSpec.builder(FieldType.INT32, "age", 2).build())
    .build()

val proto = ProtoFile.builder()
    .apply { packageName = "example.v1" }
    .addMessage(msg)
    .build()

Writing Output

// To string
val source = proto.toString()

// To any Appendable (Writer, StringBuilder, etc.)
proto.writeTo(fileWriter)

Supported Field Types

All proto3 scalar types are supported: DOUBLE, FLOAT, INT32, INT64, UINT32, UINT64, SINT32, SINT64, FIXED32, FIXED64, SFIXED32, SFIXED64, BOOL, STRING, BYTES, as well as MESSAGE and ENUM references.

Validation

ProtoPoet validates your definitions at build time:

  • Duplicate field numbers within a message
  • Duplicate field names within a message
  • Duplicate type names (messages, enums) within a scope
  • Conflicts between field numbers and reserved ranges/numbers
  • Invalid field types in oneof blocks (no maps, no repeated fields)

License

Licensed under the MIT License. See LICENSE for details.

About

A Kotlin DSL for programmatically generating Protocol Buffer v3 files

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages