protogen

module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: Apache-2.0

README

🧬 protogen

One Go binary that turns .proto files into Go messages, gRPC, gRPC-gateway and OpenAPI v3 — with zero protoc and zero external plugins.

Единый Go-бинарник, который превращает .proto в Go-сообщения, gRPC, gRPC-gateway и OpenAPI v3 — без protoc и без внешних плагинов.

CI Go Reference Go Report Card Go License protoc in-process

English · Русский


✨ Features

  • 🚫 No protoc, no protoc-gen-* — nothing to install; parsing is pure Go (bufbuild/protocompile).
  • 🧱 Messages (*.pb.go) via the very same generator protoc-gen-go uses.
  • 🔌 gRPC (*_grpc.pb.go) — unary + server / client / bidi streaming.
  • 🌐 gRPC-gateway (*.pb.gw.go) — REST↔gRPC reverse proxy, unary + server-streaming.
  • 📖 OpenAPI v3 (openapi.yaml), honoring google.api.http and openapi.v3.* annotations (document info, operation summary/tags, schema/property overrides).
  • Validation with protovalidate — enforced at runtime and reflected into the OpenAPI schema (minLength, pattern, format, string enum, readOnly/writeOnly, required, …).
  • 🩹 ASP.NET Core-style 400 — validation failures become RFC 9457 problem+json with a field→messages map (and it's documented in OpenAPI too).
  • 🔐 Roles & permissions per method — annotate RPCs with (protogen.authz.requires) (any_of/all_of/none_of) and enforce them with the bundled gRPC interceptors.
  • 📦 Bundled well-known importsgoogle/api/* (incl. field_behavior), buf/validate/*, openapiv3/* and protogen/authz/* are embedded; no vendoring, no --proto_path for them.
  • 🗂️ Managed mode — synthesizes go_package/package when your protos omit them.
  • 🌳 Monorepo-friendly — point it at a directory (or glob) and it generates the whole tree at once.
  • ⚙️ Config file — commit a protogenall.yaml instead of a wall of flags.
  • 🔭 Reflection + healthgrpcx.Register(s) adds server reflection and the health service.
  • 🧰 go install-able, with --version.
  • 🚀 protogenall init — scaffolds a starter proto (with all the annotations wired up) and a config; then a bare protogenall (or protogenall <dir>) generates everything.

🎯 Why it works without protoc

protoc is a C++ toolchain and its plugins are separate binaries you must install. protogen replaces that whole pipeline with in-process Go:

Stage How
Parse .proto bufbuild/protocompile (pure-Go compiler)
Well-known types protocompile.WithStandardImports (embedded)
Bundled imports google/api/* + buf/validate/* + openapiv3/* are go:embed-ed and served by a composite resolver (--list-builtins)
Managed metadata inject go_package / package from flags
Messages protoc-gen-go/internal_gengo — importable (the path element is internal_gengo, not internal)
gRPC compact protogen generator, modeled on protoc-gen-go-grpc's generics output
gRPC-gateway own protogen generator + vendored internal/gateway/httprule for path-pattern compilation
OpenAPI v3 google/gnostic generator
Validation → OpenAPI internal/openapival post-processes the document

The one subtlety: protocompile materializes custom options (e.g. google.api.http) as dynamicpb messages that downstream generators can't read. normalizeExtensions re-encodes them through the global type registry into concrete Go types — exactly replicating the protoc↔plugin boundary. See internal/compile/compile.go.

📦 Install

go install github.com/dvislobokov/protogen/cmd/protogenall@latest
protogenall --version

Or run from a clone:

go run ./cmd/protogenall --help

🚀 Quick start

Scaffold a project and generate — two commands, zero flags:

protogenall init myapi     # creates myapi/proto/…/myapi.proto + myapi/protogenall.yaml
protogenall myapi          # or: cd myapi && protogenall
# → myapi/gen/…  (*.pb.go, *_grpc.pb.go, *.pb.gw.go, openapi.yaml)

init writes a starter proto that already demonstrates google.api.http, buf.validate, openapi.v3 and protogen.authz annotations, plus a protogenall.yaml; run it inside a Go module and go_package_prefix is derived from your go.mod. Existing files are never overwritten (use --force).

Or drive everything with flags:

protogenall \
  --proto_path=example/proto \
  --go-package-prefix=example.com/gen \
  --openapi-title="Greeter API" \
  --out=gen \
  greeter.proto
# → gen/greeter.pb.go  gen/greeter_grpc.pb.go  gen/greeter.pb.gw.go  gen/openapi.yaml

google/api/*, buf/validate/* and openapiv3/* are bundled in the binary, so you point --proto_path only at your protos.

🌳 Whole monorepo at once

Inputs may be files, directories, or globs — a directory is walked recursively and output mirrors the source tree:

protogenall --proto_path=monorepo --go-package-prefix=example.com/gen --out=gen monorepo
⚙️ Config file

Commit a protogenall.yaml (auto-detected in the CWD, or --config path); explicit flags override it.

proto_paths: [proto]
inputs: [proto]
out: gen
go_package_prefix: example.com/gen
openapi: { title: Checkout API, version: 1.0.0 }
generators: [messages, grpc, gateway, openapiv3]   # subset allowed; default all

✅ Validation → OpenAPI

Write protovalidate constraints in your proto:

message PlaceOrderRequest {
  string customer_email = 1 [(buf.validate.field).required = true, (buf.validate.field).string.email = true];
  Currency currency      = 5 [(buf.validate.field).enum = {defined_only: true, not_in: [0]}];
  repeated LineItem items = 6 [(buf.validate.field).repeated = {min_items: 1, max_items: 50}];
  string order_id        = 7 [(google.api.field_behavior) = OUTPUT_ONLY];
}

…and they show up in openapi.yaml (no separate validator is generated — protovalidate checks at runtime):

customerEmail: { type: string, format: email }
currency:      { type: string, enum: [USD, EUR, GBP] }   # string names, as protojson serializes them
items:         { type: array, minItems: 1, maxItems: 50 }
orderId:       { type: string, readOnly: true }
required: [customerEmail, ...]

Enums render as string names by default (matching grpc-gateway's JSON). Pass --openapi-enum-format=number (or openapi.enum_format: number) to get numeric enum values with an x-enum-varnames hint instead.

📝 OpenAPI annotations

Import openapiv3/annotations.proto (bundled — nothing to vendor) to customize the OpenAPI output right from the proto: document info, per-operation summary/description/tags, and schema/property overrides. These are gnostic's openapi.v3 options, read natively by the OpenAPI generator:

import "openapiv3/annotations.proto";

option (openapi.v3.document) = {
  info: { title: "Pet Store API", version: "1.2.3", description: "..." }
};

service PetService {
  rpc GetPet(GetPetRequest) returns (Pet) {
    option (google.api.http) = { get: "/v1/pets/{id}" };
    option (openapi.v3.operation) = {
      summary: "Fetch a pet"
      tags: ["pets"]
    };
  }
}

message Pet {
  option (openapi.v3.schema) = { description: "A pet in the store." };
  string name = 1 [(openapi.v3.property) = { max_length: 64 }];
}

(openapi.v3.document).info takes precedence over --openapi-title/--openapi-version.

🔐 Roles & permissions per method

Annotate methods with (protogen.authz.requires) (the proto is bundled) and enforce it with the interceptors from github.com/dvislobokov/protogen/authz:

import "protogen/authz/authz.proto";

service Greeter {
  // Default for methods without their own annotation.
  option (protogen.authz.default_requires) = { public: true };

  rpc SayHello(HelloRequest) returns (HelloReply) {
    option (protogen.authz.requires) = {
      roles: { any_of: ["admin", "greeter"] }        // "oneOf" semantics
      permissions: { all_of: ["greetings.write"] }   // "all" semantics
    };
  }
}

Rules support any_of (at least one), all_of (every one) and none_of (deny-list); all set fields must pass. {} means "any authenticated subject", { public: true } skips checks entirely, and unannotated methods (with no service default) are not checked.

Enforcement is one interceptor pair — you supply the SubjectFunc that extracts roles/permissions from the request (e.g. from a JWT):

subject := func(ctx context.Context) (*authz.Subject, error) {
    claims, err := verifyJWT(ctx) // your auth
    if err != nil || claims == nil {
        return nil, err // nil subject → Unauthenticated on protected methods
    }
    return &authz.Subject{Roles: claims.Roles, Permissions: claims.Scopes}, nil
}

s := grpc.NewServer(
    grpc.ChainUnaryInterceptor(authz.UnaryServerInterceptor(subject)),
    grpc.ChainStreamInterceptor(authz.StreamServerInterceptor(subject)),
)

Failures map to gRPC codes (Unauthenticated / PermissionDenied), which the gateway translates to HTTP 401/403. authz.Authorize(ctx, fullMethod, subject) is exported for custom transports.

🌊 Streaming

All four RPC kinds are generated. example/stream exercises each end to end (bufconn), plus a gateway server-streaming HTTP round trip:

service Chat {
  rpc Send(Message) returns (Ack);                       // unary
  rpc Subscribe(SubscribeRequest) returns (stream Message) {  // server streaming → HTTP chunked JSON
    option (google.api.http) = { get: "/v1/rooms/{room}/messages" };
  }
  rpc Upload(stream Chunk) returns (UploadSummary);      // client streaming
  rpc Converse(stream Message) returns (stream Message); // bidi
}

Client/bidi streaming can't be expressed over REST, so the gateway skips them (with a note); server-streaming is forwarded as a chunked JSON stream.

🩹 ASP.NET Core-style validation errors

rest.ProblemErrorHandler (a runtime.WithErrorHandler) turns a protovalidate failure into RFC 9457 problem+json, with keys in JSON (camelCase) so they match the payload and OpenAPI schema — try go run ./example/checkout:

HTTP 400  application/problem+json
{
  "type": "https://datatracker.ietf.org/doc/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "customerEmail": ["must be a valid email address"],
    "items":         ["must contain at least 1 item(s)"],
    "acceptTerms":   ["must equal true"]
  }
}

🔭 Reflection + health

s := grpc.NewServer()
shop.RegisterCheckoutServer(s, impl{})
grpcx.Register(s) // server reflection + health service (SERVING)

🏗️ Architecture

.proto ─▶ protocompile ─▶ managed mode ─▶ CodeGeneratorRequest ─▶ generators (in-process)
         (no protoc)      (go_package)                            ├─ messages   (internal_gengo)
                                                                  ├─ grpc       (own)
                                                                  ├─ gateway    (own + httprule)
                                                                  └─ openapi v3 (gnostic)
                                                                       └─ openapival enrichment

New generators just implement the gen.Generator interface (internal/gen/registry.go) and are appended in cmd/protogenall/main.go.

🗺️ Roadmap

  • 🔍 Breaking-change detection (pairs with --descriptor-set-out)
  • 🔁 Dual-mode: run as a protoc/buf plugin (read CodeGeneratorRequest from stdin)
  • 🧹 Proto linter

📄 License

Apache 2.0 — see LICENSE. Bundles Apache-licensed protos (google/api, buf/validate, gnostic's openapiv3) and vendors grpc-gateway's httprule package.


🇷🇺 Русский

English · Русский

protogen — единый Go-бинарник, который генерирует Go-сообщения, gRPC-стабы, gRPC-gateway и OpenAPI v3 из .proto без protoc и без внешних плагинов. Вся генерация — in-process.

✨ Возможности
  • 🚫 Ни protoc, ни protoc-gen-* — ставить нечего; парсинг на чистом Go (bufbuild/protocompile).
  • 🧱 Сообщения (*.pb.go) — тем же генератором, что и protoc-gen-go.
  • 🔌 gRPC (*_grpc.pb.go) — unary + server / client / bidi стриминг.
  • 🌐 gRPC-gateway (*.pb.gw.go) — REST↔gRPC прокси, unary + server-streaming.
  • 📖 OpenAPI v3 (openapi.yaml) с учётом google.api.http и аннотаций openapi.v3.* (info документа, summary/tags операций, переопределения схем и полей).
  • Валидация через protovalidate — проверка в рантайме и отражение в OpenAPI-схему (minLength, pattern, format, строковый enum, readOnly/writeOnly, required, …).
  • 🩹 Ошибки в стиле ASP.NET Core — невалидный запрос превращается в RFC 9457 problem+json с картой поле→сообщения (и это описано в OpenAPI).
  • 🔐 Роли и пермиссии на метод — аннотация (protogen.authz.requires) (any_of/all_of/none_of) + готовые gRPC-интерцепторы для проверки.
  • 📦 Встроенные well-known импортыgoogle/api/* (в т.ч. field_behavior), buf/validate/*, openapiv3/* и protogen/authz/* вшиты; ни вендоринга, ни --proto_path для них.
  • 🗂️ Managed mode — подставляет go_package/package, если их нет в proto.
  • 🌳 Дружит с монорепой — укажи папку (или glob), и всё дерево сгенерируется за раз.
  • ⚙️ Конфиг-файл — вместо простыни флагов коммить protogenall.yaml.
  • 🔭 Reflection + healthgrpcx.Register(s) добавляет server reflection и health-сервис.
  • 🧰 Ставится через go install, есть --version.
  • 🚀 protogenall init — скаффолдит стартовый proto (со всеми аннотациями) и конфиг; дальше достаточно protogenall без аргументов (или protogenall <папка>).
🎯 Почему это работает без protoc

protoc — это C++-тулчейн, а его плагины — отдельные бинарники, которые надо ставить. protogen заменяет весь этот конвейер на in-process Go:

Этап Как
Парсинг .proto bufbuild/protocompile (компилятор на чистом Go)
Well-known типы protocompile.WithStandardImports (встроены)
Встроенные импорты google/api/* + buf/validate/* + openapiv3/* через go:embed и композитный резолвер (--list-builtins)
Метаданные подстановка go_package / package из флагов
Сообщения protoc-gen-go/internal_gengo — импортируется (элемент пути — internal_gengo, не internal)
gRPC компактный protogen-генератор по образцу generics-вывода protoc-gen-go-grpc
gRPC-gateway свой protogen-генератор + завендоренный internal/gateway/httprule
OpenAPI v3 генератор google/gnostic
Валидация → OpenAPI пост-обработка в internal/openapival

Единственная тонкость: protocompile материализует кастомные опции (например google.api.http) как dynamicpb-сообщения, которые генераторы не читают. normalizeExtensions переупаковывает их через глобальный реестр типов в конкретные Go-типы — ровно как это происходит на границе protoc↔plugin. См. internal/compile/compile.go.

📦 Установка
go install github.com/dvislobokov/protogen/cmd/protogenall@latest
protogenall --version
🚀 Быстрый старт

Скаффолдинг и генерация — две команды без флагов:

protogenall init myapi     # создаст myapi/proto/…/myapi.proto + myapi/protogenall.yaml
protogenall myapi          # или: cd myapi && protogenall
# → myapi/gen/…  (*.pb.go, *_grpc.pb.go, *.pb.gw.go, openapi.yaml)

init кладёт стартовый proto с уже подключёнными аннотациями (google.api.http, buf.validate, openapi.v3, protogen.authz) и protogenall.yaml; внутри Go-модуля go_package_prefix берётся из go.mod. Существующие файлы не перезаписываются (есть --force).

Или всё то же флагами:

protogenall \
  --proto_path=example/proto \
  --go-package-prefix=example.com/gen \
  --openapi-title="Greeter API" \
  --out=gen \
  greeter.proto

google/api/*, buf/validate/* и openapiv3/* вшиты в бинарник, поэтому --proto_path указывает только на твои proto.

🌳 Вся монорепа за раз

На вход можно подать файлы, папки или glob-и; папка обходится рекурсивно, вывод зеркалит структуру:

protogenall --proto_path=monorepo --go-package-prefix=example.com/gen --out=gen monorepo
⚙️ Конфиг-файл

Коммить protogenall.yaml (авто-детект в CWD или --config path); явные флаги переопределяют его.

proto_paths: [proto]
inputs: [proto]
out: gen
go_package_prefix: example.com/gen
openapi: { title: Checkout API, version: 1.0.0 }
generators: [messages, grpc, gateway, openapiv3]   # можно подмножество; по умолчанию все
✅ Валидация → OpenAPI

Пишешь констрейнты protovalidate в proto — и они появляются в openapi.yaml (отдельный валидатор не генерируется, protovalidate проверяет в рантайме): форматы (email/uuid), pattern, диапазоны, строковый enum (имена значений, как их сериализует grpc-gateway; --openapi-enum-format=number вернёт числа), readOnly/writeOnly (из field_behavior), required.

📝 OpenAPI-аннотации

Импортируй openapiv3/annotations.proto (вшит в бинарник) и настраивай OpenAPI прямо из proto: (openapi.v3.document) — info документа, (openapi.v3.operation) — summary/description/tags операции, (openapi.v3.schema) и (openapi.v3.property) — переопределения схем и полей. Это опции openapi.v3 из gnostic; (openapi.v3.document).info имеет приоритет над --openapi-title/--openapi-version.

🔐 Роли и пермиссии на метод

Импортируй protogen/authz/authz.proto (вшит) и вешай на методы (protogen.authz.requires) с правилами any_of (хотя бы одна — «oneOf»), all_of (все — «all») и none_of (запрещённые); (protogen.authz.default_requires) на сервисе задаёт дефолт. {} — «любой аутентифицированный», { public: true } — без проверок, метод без аннотаций не проверяется. Enforcement — интерцепторы authz.UnaryServerInterceptor / authz.StreamServerInterceptor из github.com/dvislobokov/protogen/authz: ты передаёшь SubjectFunc, достающий роли/пермиссии из контекста (например из JWT), а отказ маппится в Unauthenticated/PermissionDenied (401/403 через gateway). Пример — example/proto/greeter.proto и authz/interceptor_test.go.

🌊 Стриминг

Генерируются все четыре вида RPC. example/stream гоняет каждый end-to-end (bufconn) + HTTP server-streaming через gateway. Client/bidi по REST невыразимы — gateway их пропускает; server-streaming форвардится как chunked JSON.

🩹 Ошибки в стиле ASP.NET Core

rest.ProblemErrorHandler превращает провал валидации в RFC 9457 problem+json с ключами в camelCase (совпадают с телом запроса и OpenAPI). Запусти go run ./example/checkout:

HTTP 400  application/problem+json
{
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "customerEmail": ["must be a valid email address"],
    "items":         ["must contain at least 1 item(s)"]
  }
}
🔭 Reflection + health
s := grpc.NewServer()
shop.RegisterCheckoutServer(s, impl{})
grpcx.Register(s) // server reflection + health (SERVING)
🗺️ Планы
  • 🔍 Детектор breaking changes (пара к --descriptor-set-out)
  • 🔁 Dual-mode: работать как плагин protoc/buf (читать CodeGeneratorRequest из stdin)
  • 🧹 Proto-линтер
📄 Лицензия

Apache 2.0 — см. LICENSE.


Built to show that the whole proto → Go/gRPC/gateway/OpenAPI pipeline can live in a single dependency-free-at-runtime Go binary.

Directories

Path Synopsis
Package authz enforces the (protogen.authz.requires) proto annotations at runtime via gRPC server interceptors.
Package authz enforces the (protogen.authz.requires) proto annotations at runtime via gRPC server interceptors.
cmd
protogenall command
Command protogenall generates Go message code, gRPC stubs and an OpenAPI v3 document from .proto files — without protoc or any external plugin binaries.
Command protogenall generates Go message code, gRPC stubs and an OpenAPI v3 document from .proto files — without protoc or any external plugin binaries.
example
checkout command
Command checkout demonstrates the full stack for one POST endpoint:
Command checkout demonstrates the full stack for one POST endpoint:
consume command
Command consume proves the generated code is real and usable: it implements the generated GreeterServer, registers it, and constructs a client — all against types produced by protogenall from greeter.proto.
Command consume proves the generated code is real and usable: it implements the generated GreeterServer, registers it, and constructs a client — all against types produced by protogenall from greeter.proto.
Package grpcx bundles the two things almost every gRPC server wants but that aren't generated: server reflection (so grpcurl / Postman can discover the API) and the standard health service.
Package grpcx bundles the two things almost every gRPC server wants but that aren't generated: server reflection (so grpcurl / Postman can discover the API) and the standard health service.
internal
compile
Package compile turns a set of .proto files into descriptors using the pure-Go bufbuild/protocompile compiler.
Package compile turns a set of .proto files into descriptors using the pure-Go bufbuild/protocompile compiler.
config
Package config loads protogenall.yaml — a declarative alternative to the CLI flags, so a repo can commit its generation settings.
Package config loads protogenall.yaml — a declarative alternative to the CLI flags, so a repo can commit its generation settings.
gen
Package gen wires compiled descriptors into protogen and runs a set of in-process code generators (messages, gRPC, gateway, OpenAPI).
Package gen wires compiled descriptors into protogen and runs a set of in-process code generators (messages, gRPC, gateway, OpenAPI).
managed
Package managed implements "managed mode": filling in file-level metadata (go_package, java_package, ...) from CLI-provided defaults when a .proto file does not set it itself.
Package managed implements "managed mode": filling in file-level metadata (go_package, java_package, ...) from CLI-provided defaults when a .proto file does not set it itself.
openapival
Package openapival enriches a generated OpenAPI v3 document with the protovalidate (buf.validate) field constraints found in the descriptors, plus enum value lists — so the schema reflects the same rules enforced at runtime.
Package openapival enriches a generated OpenAPI v3 document with the protovalidate (buf.validate) field constraints found in the descriptors, plus enum value lists — so the schema reflects the same rules enforced at runtime.
Package rest turns protovalidate failures into an ASP.NET Core-style HTTP 400 response for grpc-gateway: RFC 9457 problem+json with a field->messages "errors" map, exactly like ASP.NET Core's ValidationProblemDetails.
Package rest turns protovalidate failures into an ASP.NET Core-style HTTP 400 response for grpc-gateway: RFC 9457 problem+json with a field->messages "errors" map, exactly like ASP.NET Core's ValidationProblemDetails.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL