Primo is a protoc plugin that augments the Go code produced by
protoc-gen-go and protoc-gen-go-grpc. It leaves the standard generated
types untouched and adds extra methods and helpers alongside them, so it works
with both the open and opaque APIs.
Each .proto file compiled with Primo gains the following, written to a
companion xxx_primo.pb.go file:
- Field accessors —
TryGetmethods that report whether a one-of member is set. - Field mutators — fluent
Setmethods for each field. - Message builders — a fluent builder type for constructing each message.
- Exhaustive switches — functions that switch or map over every case of a one-of group or enumeration.
- Binary marshaling —
MarshalBinaryandUnmarshalBinarymethods for each message. - Formatting — a
Formatmethod that lets a message customize its%sand%qoutput. - gRPC client stubs — test doubles for each generated gRPC client interface.
For each member of a one-of group, Primo generates a TryGetXxx method that
returns the member's value along with a boolean reporting whether that member is
the one currently set.
The accessors that protoc-gen-go generates for one-of members return only
the value, leaving the caller to perform a separate type assertion on the
wrapper type to discover which member is set. The TryGetXxx methods fold both
steps into a single call, which reads more naturally and avoids referencing the
generated wrapper types directly.
For each field, Primo generates a SetXxx method that assigns the field and
returns the receiver, allowing calls to be chained.
These methods are generated only for open API files. The opaque API already
provides SetXxx methods, so Primo does not duplicate them.
For each message type, Primo generates a fluent builder — a XxxBuilder type
with a NewXxxBuilder constructor, WithXxx methods for each field, a From
method that seeds the builder from an existing message, and a Build method
that returns the finished message.
Builders present a single, uniform construction API regardless of whether a
message uses the open or opaque API. For open API messages the builder writes
struct fields directly; for opaque API messages it uses the generated
SetXxx methods.
For each one-of group and enumeration, Primo generates functions that dispatch on the active case:
Switch_Xxxinvokes one of a set of functions based on the current case, with aMustSwitch_Xxxvariant that panics rather than tolerating an unset value.Map_Xxxreturns a value derived from the current case.
Because the caller must supply a function for every case, the compiler flags any
call site that has not been updated after a new case is added to the .proto
definition. This turns an easily missed runtime bug into a compile-time error.
For each message type, Primo generates MarshalBinary and UnmarshalBinary
methods, equivalent to calling proto.Marshal and proto.Unmarshal.
These methods let each message satisfy the standard library's
encoding.BinaryMarshaler and encoding.BinaryUnmarshaler interfaces, so
messages can be used with any API that accepts those interfaces.
For each message type, Primo generates a Format method that implements
fmt.Formatter, taking precedence over the String method generated by
protoc-gen-go.
For the %s and %q verbs, the method uses the string returned by the
message's AsString method if the message provides one; otherwise it formats
the message as though the method were not defined. This gives a message a place
to define a human-readable representation without losing the standard Protocol
Buffers formatting for the other verbs.
For each gRPC service, Primo generates a XxxClientStub type — a test
implementation of the client interface produced by protoc-gen-go-grpc.
Each stub exposes a function field for every RPC method, so a test can supply behavior for the methods it exercises and leave the rest to their default no-op implementations. This removes the boilerplate of hand-writing a client double for each service under test.