Migrating a codebase from prost to buffa and hit a snag around field naming. Writing it up since the fix looks small and it's a pretty common interop need.
What I need
Plenty of .proto files in the wild use camelCase field names — proto3-JSON style, or protos that are synced/generated from an external source and kept verbatim. With prost these generate idiomatic snake_case Rust fields automatically (webMessageInfo → web_message_info) while the wire/JSON name stays the original. buffa instead emits the Rust ident verbatim from the proto field name, so webMessageInfo in the proto becomes msg.webMessageInfo in Rust — non-idiomatic, and it breaks any code written against the prost-style field names.
So today the only options are:
- Hand-rename every field in the
.proto to snake_case. That doesn't survive regenerating the proto from its upstream source, and it's a large diff to keep in sync.
- Post-process the
FileDescriptorSet yourself in build.rs (decode → to_snake_case every field.name → re-encode → feed the rewritten descriptor to buffa via descriptor_set). Works, but it's boilerplate every buffa user with a camelCase proto ends up reinventing.
How prost does it
prost-build does this conversion for you. Its codegen runs proto field names through heck's ToSnakeCase to produce the Rust ident and keeps the original name as the proto/JSON name, so a camelCase proto "just works" and the generated API is idiomatic Rust:
message WebMessageInfo {
string remoteJid = 1;
uint64 messageTimestamp = 2;
}
// prost output
pub struct WebMessageInfo {
pub remote_jid: String,
pub message_timestamp: u64,
}
buffa's make_field_ident only escapes Rust keywords; it doesn't case-convert, so the same proto yields remoteJid / messageTimestamp fields.
The key point: this is purely a Rust-ident concern. The wire format keys on field numbers, and the proto/JSON name can stay the original camelCase — so the conversion is fully wire- and JSON-compatible.
Proposal
Add an opt-in codegen option, mirroring prost's default behavior. Something like:
buffa_build::Config::new()
.idiomatic_field_names(true) // or field_case(FieldCase::Snake)
// ...
When enabled, make_field_ident would apply to_snake_case to the proto field name (and oneof field names), while proto_name() / the serde rename / reflection keep the original name for wire + JSON parity. Default off, so existing output is unchanged.
A couple of finer points worth deciding:
- Global only vs. per-path (
_in variant). Global is enough for my case, but per-path would match the rest of the API.
json_name: keep emitting the descriptor's json_name (camelCase) so proto3-JSON round-trips unchanged.
This feels symmetric with the existing idiomatic_enum_aliases option — same "make the generated Rust nicer without touching the wire" spirit. Happy to send a PR if the direction sounds good; it looks like a small change localized to the ident generation (make_field_ident + a CodeGenConfig flag).
Migrating a codebase from prost to buffa and hit a snag around field naming. Writing it up since the fix looks small and it's a pretty common interop need.
What I need
Plenty of
.protofiles in the wild use camelCase field names — proto3-JSON style, or protos that are synced/generated from an external source and kept verbatim. With prost these generate idiomatic snake_case Rust fields automatically (webMessageInfo→web_message_info) while the wire/JSON name stays the original. buffa instead emits the Rust ident verbatim from the proto field name, sowebMessageInfoin the proto becomesmsg.webMessageInfoin Rust — non-idiomatic, and it breaks any code written against the prost-style field names.So today the only options are:
.prototo snake_case. That doesn't survive regenerating the proto from its upstream source, and it's a large diff to keep in sync.FileDescriptorSetyourself inbuild.rs(decode →to_snake_caseeveryfield.name→ re-encode → feed the rewritten descriptor to buffa viadescriptor_set). Works, but it's boilerplate every buffa user with a camelCase proto ends up reinventing.How prost does it
prost-build does this conversion for you. Its codegen runs proto field names through
heck'sToSnakeCaseto produce the Rust ident and keeps the original name as the proto/JSON name, so a camelCase proto "just works" and the generated API is idiomatic Rust:buffa's
make_field_identonly escapes Rust keywords; it doesn't case-convert, so the same proto yieldsremoteJid/messageTimestampfields.The key point: this is purely a Rust-ident concern. The wire format keys on field numbers, and the proto/JSON name can stay the original camelCase — so the conversion is fully wire- and JSON-compatible.
Proposal
Add an opt-in codegen option, mirroring prost's default behavior. Something like:
When enabled,
make_field_identwould applyto_snake_caseto the proto field name (and oneof field names), whileproto_name()/ the serde rename / reflection keep the original name for wire + JSON parity. Default off, so existing output is unchanged.A couple of finer points worth deciding:
_invariant). Global is enough for my case, but per-path would match the rest of the API.json_name: keep emitting the descriptor'sjson_name(camelCase) so proto3-JSON round-trips unchanged.This feels symmetric with the existing
idiomatic_enum_aliasesoption — same "make the generated Rust nicer without touching the wire" spirit. Happy to send a PR if the direction sounds good; it looks like a small change localized to the ident generation (make_field_ident+ aCodeGenConfigflag).