This post is going to be short and sweet. And likely a huge relief for long-time Protobuf users.
Oneofs are a disaster. The generated code for using oneofs is awful in some languages (such as Go), and oneofs have basic limitations — like their inability to use repeated and map fields, and backwards-compatibility issues — that make their use often impractical. Alas, there's no virtue in crying over spilled milk. So instead of continuing to whine about it, the Buf team did what it always does: we fixed it.
Instead of using oneofs, you can now use the new (buf.validate.message).oneof Protovalidate annotation. As long as you're validating your messages with Protovalidate (and if you aren't at this point—you should be), (buf.validate.message).oneof does exactly what you'd expect, with none of the pain.
As an example, assume you had a UserRef message as follows:
message UserRef { oneof value { string id = 1; string name = 2; } }Instead, now do:
import "buf/validate/validate.proto"; message UserRef { option (buf.validate.message).oneof = { fields: ["id", "name"] }; string id = 1; string name = 2; }What if you want to ensure that exactly one field is set, rather than at most one? Previously, you'd do:
import "buf/validate/validate.proto"; message UserRef { oneof value { option (buf.validate.oneof).required = true; string id = 1; string name = 2; } }Now:
import "buf/validate/validate.proto"; message UserRef { option (buf.validate.message).oneof = { fields: ["id", "name"], required: true }; string id = 1; string name = 2; }Why this is so much better
Generated code
Generated oneof code is painful in some languages. Anyone who has used oneofs in Go will be familiar with this nightmare:
userRef := &userv1.UserRef{ Value: &userv1.UserRef_Name{ Name: "alice", }, }Now? It's just a field!
userRef := &userv1.UserRef{ Name: "alice", }Repeated and map fields
What about repeated and map fields in a oneof? Say we wanted a list of names instead of a single name. Formerly, we'd have to do this:
message NameList { repeated string values = 1; } message UserRef { oneof value { string id = 1; NameList name_list = 2; } }How does this translate to Go?
userRef := &userv1.UserRef{ Value: &userv1.UserRef_Names{ Names: &userv1.NameList{ Values: []string{"alice", "bob"}, }, }, }Disgusting! What about in the new world? Here's your Protobuf definition:
import "buf/validate/validate.proto"; message UserRef { option (buf.validate.message).oneof = { fields: ["id", "names"] }; string id = 1; repeated string names = 2; }And it Just Works™ as you'd expect: id can be a non-empty string, or names can be a non-empty list, but not both. The Go code is crisp and clear:
userRef := &userv1.UserRef{ Names: []string{"alice", "bob"}, }Backwards-compatibility issues
Want to stop using a oneof? And then use it again? Have at it! No annoying backwards-compatibility issues to worry about: it just works, and is fully backwards- and forwards-compatible. Moving oneof to a runtime check has a similar effect to Protovalidate's (buf.validate.field).required validation: required is dangerous as implemented in the Protobuf spec, but works great as a runtime validation that you can add and remove as you please.
Where to go from here
- Read the docs on MessageRules for the oneof annotation. They're short and explain the exact semantics.
- Update Protovalidate to start using (buf.validate.message).oneof. Pull the latest version of the BSR module via buf dep update, and update your Protovalidate language implementation to the latest version.
- Start using Protovalidate if you haven't already. Whether it's validating RPC requests or preventing bad data from entering your Kafka topics, Protovalidate has your back. Protovalidate is available in Go, Java, Python, C++, and TypeScript/JavaScript. v1.0 is imminent, after years of production usage across some of the world's biggest companies.
- Support Buf by checking out our other projects. If you're interested in our schema-driven development story, take 10 minutes to read our latest blog entry on the subject.
We hope you love this as much as we do. Happy Protobuf'ing!