olai-codegen
Proto-driven code generator that transforms compiled protobuf descriptors into idiomatic Rust server and client code, resource registries, and optional Python/Node.js/TypeScript bindings.
Overview
The single source of truth is your .proto files. Annotate them with standard Google API extensions and run proto-gen generate to emit:
| Output | What is generated |
|---|---|
common/ |
Axum extractors, shared request/response types, mod.rs |
models/ |
labels.rs — resource enums and olai_store::Label impls |
server/ |
Handler traits (one async method per RPC) and Axum route handlers |
client/ |
HTTP client struct with typed request builder methods |
python/ |
PyO3 bindings and .pyi type stubs |
node/ |
NAPI-RS bindings |
node_ts/ |
TypeScript client |
Pipeline
.proto files
│
▼ buf build --as-file-descriptor-set
descriptor binary (.bin)
│
▼ proto-gen generate
Source files (Rust, Python, TypeScript, …)
Proto Annotations
The generator reads standard Google API extensions. No custom annotations are required.
| Annotation | Effect |
|---|---|
google.api.resource |
Registers a message as a managed resource type; drives resource enum variants and label generation |
google.api.http |
Maps each RPC to an HTTP method and URI pattern; drives route handlers and client methods |
google.api.field_behavior |
Marks fields as OUTPUT_ONLY, REQUIRED, OPTIONAL, IDENTIFIER, etc.; shapes generated extractors and builders |
google.api.resource_reference |
Declares parent-child relationships between resource types; drives hierarchical name composition |
Example proto fragment (see proto/ for full working examples):
message Catalog {
option (google.api.resource) = {
type: "example.io/Catalog"
pattern: "catalogs/{catalog}"
singular: "catalog"
plural: "catalogs"
};
string name = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
string comment = 2 [(google.api.field_behavior) = OPTIONAL];
}
service CatalogService {
rpc GetCatalog(GetCatalogRequest) returns (Catalog) {
option (google.api.http) = { get: "/catalogs/{name}" };
}
rpc CreateCatalog(CreateCatalogRequest) returns (Catalog) {
option (google.api.http) = { post: "/catalogs" body: "*" };
}
}
Usage
Installation
Or build and run directly from the workspace:
Compile descriptors
Config file (recommended)
A YAML config file is the preferred way to invoke proto-gen, particularly in multi-crate workspaces where output directories span crate boundaries. CLI flags override any value from the file.
# proto-gen.yaml
descriptors: api.bin
buf_gen: buf.gen.yaml # optional: auto-derives models_path_template from prost plugin output
generate:
output_common: crates/server/src/gen/common
output_models: crates/common/src/models # labels.rs lands in <output_models>/_gen/
output_server: crates/server/src/gen/server
output_client: crates/client/src/gen/client
context_type: my_crate::RequestContext
result_type: my_crate::Result
# Required when buf_gen is not set:
models_path_template: my_models::models::{service}::v1
models_path_crate_template: crate::models::{service}::v1
# Resource enum generation (requires olai-store)
generate_resource_enum: true
generate_store_integration: true
generate_object_conversions: true
error_type_path: crate::Error
# Optional: language bindings
python:
output: crates/python/src/gen
error_type: my_crate::PyError
result_type: my_crate::PyResult
typescript:
output: crates/node/src/gen
aggregate_client_name: MyClient
client_crate_name: my_client
Any field can be overridden at the command line:
CLI flags (all options)
proto-gen generate
| Flag | Env var | Description |
|---|---|---|
--config / -c |
— | YAML config file; CLI flags override |
--descriptors / -d |
UC_BUILD_DESCRIPTORS |
Compiled proto descriptor binary |
--context-type |
UC_BUILD_CONTEXT_TYPE |
Rust path for the request context type |
--result-type |
UC_BUILD_RESULT_TYPE |
Rust path for the Result alias |
--models-path-template |
UC_BUILD_MODELS_PATH_TEMPLATE |
External import path template ({service} is replaced per service) |
--models-path-crate-template |
UC_BUILD_MODELS_PATH_CRATE_TEMPLATE |
Intra-crate import path template |
--output-common |
UC_BUILD_OUTPUT_COMMON |
Output dir for common extractors (required) |
--output-models |
UC_BUILD_OUTPUT_MODELS |
Parent dir for resource labels |
--models-subdir |
UC_BUILD_MODELS_SUBDIR |
Subdirectory inside models dir (default: _gen) |
--output-server |
UC_BUILD_OUTPUT_SERVER |
Output dir for handler traits and route handlers |
--output-client |
UC_BUILD_OUTPUT_CLIENT |
Output dir for HTTP client |
--output-python |
UC_BUILD_OUTPUT_PYTHON |
Output dir for PyO3 bindings |
--output-node |
UC_BUILD_OUTPUT_NODE |
Output dir for NAPI-RS bindings |
--output-node-ts |
UC_BUILD_OUTPUT_NODE_TS |
Output dir for TypeScript client |
--python-typings-filename |
UC_BUILD_PYTHON_TYPINGS_FILENAME |
Name of the .pyi stub file |
proto-gen enrich-openapi
Merges proto-derived validation rules into an OpenAPI YAML spec.
| Flag | Description |
|---|---|
--config / -c |
YAML config file (uses enrich_openapi: section) |
--spec |
OpenAPI YAML file to enrich (default: openapi/openapi.yaml) |
--jsonschema-dir |
Directory of JSON Schema files from buf (default: openapi/jsonschema) |
--descriptors |
Proto descriptor for deduplication pass; omit to skip |
--camel-case |
Convert snake_case field names to camelCase |
The same top-level config file covers both subcommands:
descriptors: api.bin
enrich_openapi:
spec: openapi/openapi.yaml
jsonschema_dir: openapi/jsonschema
camel_case: true
Recommended Project Structure
proto-gen writes generated files into output directories that can span multiple crates. The recommended layout separates concerns across four crates:
my-workspace/
├── Cargo.toml
├── proto/
│ └── my_service.proto # source of truth
├── api.bin # buf build output
├── proto-gen.yaml
└── crates/
├── common/ # shared model types
│ └── src/
│ └── models/
│ ├── mod.rs # hand-written: re-exports from _gen
│ └── _gen/
│ ├── labels.rs # generated: Resource enum, ObjectLabel, store impls
│ └── mod.rs # generated: include! prost output + re-exports
│
├── server/ # Axum service implementation
│ └── src/
│ ├── api/
│ │ ├── context.rs # hand-written: RequestContext (impl FromRequestParts)
│ │ └── error.rs # hand-written: Error type, Result alias
│ └── gen/
│ ├── common/ # generated: Axum extractors
│ └── server/ # generated: handler traits + route handlers
│
└── client/ # HTTP client
└── src/
└── gen/
├── common/ # generated: shared extractor types
└── client/ # generated: client struct + request builders
What you write vs. what is generated
Hand-written (once, then stable):
RequestContext— extracted from each Axum request (auth, request ID, etc.); must implementaxum::extract::FromRequestPartsError/Result<T>— your service error type and result alias; used in all generated handler and client signaturesparse_error_response— called by generated client code on non-2xx responses; signature:pub async fn parse_error_response(response: reqwest::Response) -> Errorcommon/src/models/mod.rs— re-exports from_genso the rest of the codebase imports from a stable path
Generated (overwritten on each run, do not edit):
common/src/models/_gen/labels.rs—Resourceenum,ObjectLabelenum,olai_store::Labelimpl,RESOURCE_DESCRIPTORScommon/src/models/_gen/mod.rs—include!directives into prost output +pub usere-exportsserver/src/gen/common/— Axum extractor structs for path/query/body parametersserver/src/gen/server/—*Handlertrait (oneasync fnper RPC) + Axum route handler functionsclient/src/gen/client/—*Clientstruct +with_*builder methods
Implementing a handler
The generated handler trait has one method per RPC. Implement it in your server crate:
use async_trait;
use CatalogHandler;
Wire the generated route handlers into Axum using the handler as state:
let handler = new;
let app = new
.merge;
Using the generated client
use CloudClient;
use CatalogClient;
let client = new;
let catalog = client.get_catalog.await?;
Cargo.toml dependencies
Add these to the crates that consume the generated code:
# server/Cargo.toml
[]
= "0.8"
= "0.10"
= "0.1"
= { = "../../crates/olai-store" } # if store integration enabled
# client/Cargo.toml
[]
= { = "../../crates/olai-http" }
= "2"
= "1"
# common/Cargo.toml — additional deps when generate_object_conversions = true
[]
= { = "../../crates/olai-store" }
= { = "1", = ["v4"] }
= "0.4"
= { = "0.26", = ["derive"] }
Library Usage
proto-gen handles the full workflow for most use cases. If you need to embed generation into a Rust program (e.g. a custom build tool), the library API exposes the same three-step pipeline:
use ;
use FileDescriptorSet;
use Message;
let descriptor_bytes = read?;
let fds = parse_from_bytes?;
let metadata = parse_file_descriptor_set?;
let config = CodeGenConfig ;
generate_code?;
Configuration Reference
CodeGenConfig
| Field | Type | Description |
|---|---|---|
context_type_path |
String |
Rust path for the request context type injected into handler methods |
result_type_path |
String |
Rust path for the Result alias used in handler signatures |
models_path_template |
String |
External import path for prost-generated models; {service} is replaced per service |
models_path_crate_template |
String |
Intra-crate import path (used inside generated server/client code) |
output |
CodeGenOutput |
Output directory configuration |
generate_resource_enum |
bool |
Emit Resource and ObjectLabel enums in labels.rs |
generate_store_integration |
bool |
Emit olai_store::Label impl and RESOURCE_DESCRIPTORS static |
error_type_path |
Option<String> |
Error type for TryFrom<Resource> impls; enables From/TryFrom generation |
generate_object_conversions |
bool |
Emit TryFrom<Object> and ResourceExt impls |
bindings |
Option<BindingsConfig> |
Language-binding configuration for Python/Node/TypeScript output |
models_gen_dir |
Option<String> |
Relative path to prost-generated gen/ directory |
resource_store_crate_name |
String |
Name of the store crate (default: olai_store) |
CodeGenOutput
| Field | Type | Description |
|---|---|---|
common |
PathBuf |
Required. Output dir for shared extractors and mod.rs |
models |
Option<PathBuf> |
Parent dir for labels.rs and model mod.rs |
models_subdir |
String |
Subdirectory inside models (default: _gen) |
server |
Option<PathBuf> |
Output dir for handler traits and Axum route handlers |
client |
Option<PathBuf> |
Output dir for HTTP client |
python |
Option<PathBuf> |
Output dir for PyO3 bindings |
node |
Option<PathBuf> |
Output dir for NAPI-RS bindings |
node_ts |
Option<PathBuf> |
Output dir for TypeScript client |
python_typings_filename |
String |
Stub filename (default: client.pyi) |
Examples
The proto/ directory contains fully-annotated example protobuf definitions (example_models.proto, example_service.proto) and a pre-compiled descriptor (example.bin). Integration tests in tests/ run the full pipeline against these examples and serve as executable documentation of the generated output.