Although this originated in a `prost`
discussion, the underlying issue concerns
how the official protobuf toolchain treats field presence. The intent here is to
clarify the expected stance and behavior of the generator.
**What language does this apply to?**
Primarily **Rust**
, but also interested in **Go**
and **TypeScript**
as they
relate to protobuf code generation.
**Describe the problem you are trying to solve.**
The generated types are structurally imprecise: **all fields are wrapped in
`Option<T>`
**
,
even those marked as `required`
(explicitly or implicitly) in the proto
definition.
This leads to:
-
Verbose boilerplate ( `Some(...)`
wrappers)
-
Increased cognitive overhead
-
Risk of runtime errors when unwrapping values that should be guaranteed
```rust
fn
calculate_minimum_bid_increment
(cmd :
&
StartAuctionRun
) ->
Result
< MoneyAmount
, Error
> {
match
&
cmd .
minimum_bid_increment_policy {
Some
(policy) =>
match
&
policy .
policy {
Some
( minimum_bid_increment_policy
::
Policy
::
Fixed
(fixed_policy)) =>
{
//...
}
// ...
// NOTE: this should never happen, it is required
None
=>
Err
( Error
::
MinimumBidIncrementPolicyRequired
),
},
// NOTE: this should never happen, it is required
None
=>
Err
( Error
::
MinimumBidIncrementPolicyRequired
),
}
}
```
Despite the domain clearly requiring this field, the type system does not
enforce it. Structurally speaking.
We're using protobuf as the canonical schema for all:
-
Commands
-
Events
-
Aggregate Snapshot
That applies across multiple language runtimes (via WASM modules) and is
critical for:
-
Schema evolution and change detection
-
Consistent interop across services
-
Serialization correctness
-
Avoiding runtime reflection or manual encoders/decoders
We treat protobuf-generated types as the source of truth and only validate
`commands`
post-deserialization (or via protovalidate extension).
Here is the existing callbacks (thus far):
```rust
pub
type
InitialState
< State
> =
fn
() ->
State
;
pub
type
IsTerminal
< State
> =
fn
(state :
State
) ->
bool
;
pub
type
Decide
< State
, Command
, Event
, Error
> =
fn
(state :
&
State
, command :
Command
) ->
Result
< Decision
< State
, Command
, Event
, Error
>, Error
>;
pub
type
Evolve
< State
, Event
> =
fn
(state :
&
State
, event :
Event
) ->
State
;
pub
type
GetStreamId
< Command
> =
fn
( Command
) ->
String
;
pub
type
IsOrigin
< Event
> =
fn
( Event
) ->
bool
;
pub
type
GetEventID
< Event
> =
fn
( Event
) ->
String
;
```
**Describe the solution you'd like**
The code generator should respect the `required`
field modifier in `.proto`
definitions, emitting non-optional Rust fields where appropriate.
That would:
-
Better align with schema intent
-
Eliminate unnecessary `Option<T>`
wrappers
-
Improve safety and ergonomics
**Describe alternatives you've considered**
Creating application-level copies of protobuf types, but such types
have very little value in our context. The distintion between application
and serialization matters better little to us, especially when protobuffer
files can not break change.
**Additional context**