-
Notifications
You must be signed in to change notification settings - Fork 508
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generate nullable types without Option
: Opt in required fields
#908
Comments
As an experiment I tried making use of the existing "required" code path for this. I replaced the generated code with non-optional code using "required" via extern_path // lib.rs
include!(concat!(env!("OUT_DIR"), "/_.rs"));
pub use nonnull::*;
mod nonnull {
use super::Foo;
#[derive(Clone, PartialEq, prost::Message)]
pub struct Bar {
#[prost(string, tag = "1")]
pub id: String,
/// Always required in a valid message processed by the end server
#[prost(message, required, tag = "2")] // replaced "optional" attr with "required"
pub foo: Foo,
}
} // build.rs
fn main() {
prost_build::Config::default()
.extern_path(".Bar", "crate::nonnull::Bar")
.compile_protos(&["proto/foo.proto"], &["proto"])
.unwrap();
} However, it seems there is no actual validation happening at decode
A couple of outputs from this
|
I think it's impossible to stay consistent with decoding of "simple" values (like ints), due to protobuf3 semantic. And it's the same about |
Yes that's correct. In proto3 primitives (string/ints) default if absent, everything else is nullable. So prost indeed has the correct general approach by wrapping with However, this frequently results in nested types that are very unergonomic to use in actual applications. To workaround that there can be tons of boilerplate wrapper types with conversions that produce errors for logically required fields. This is also undesirable because of the amount of extra types and conversions that need to be written. Contrast with json->serde types. It is trivial to require a field in the serde type, even though json itself, of course, does not have a concept of "required". This makes it often straightforward to have a single type parsed from the input payload and validated without wrapping it and without having to deal with lots of nested So at an app level I'd like to be able to handle protobuf messages in a similar way, enforce my logical requirements with the minimum of type/wrapper complexity. I think proto3 removing "required" from the protocol was probably correct. But when the message gets to an app we'll always need to validate it and will always have logically required fields. Having to wrangle nested |
We've taken to maintaining a second struct for each Message we decode with prost. Typically the only difference is that it removes most Options. We decode with prost, then immediately parse into our "internal" representation. We would be able to get rid of a lot of boilerplate if prost could just do this as proposed above. |
This was also bugging me, so I tried patching prost-build to remove the options and add the It actually weren't many changes to get it working as expected and after removing all the now obsolete options from my application (and patching pbjson) it is still working! So I think adding this as a feature or a config to prost build should be relatively trivially possible. I did run into a problem with recursive messages though, where constructing these now caused a stackoverflow. This could probably be avoided by just having the nested struct in an option if it's recursive (and there is already a check for that because recursive structs are automatically boxed, so that should be pretty easy). I've pushed my changes here if someone wants to try this for themselves: https://github.com/lucasmerlin/prost/tree/required_message_fields Disclaimer: I don't know much about protobuf / prost internals and what I've done here might fail in horrible ways, so I don't recommend using it in it's current state. If the prost maintainers are open for such a change I'd be happy to update my code to wrap this in a feature / config and open a PR. |
In many cases nested proto contracts have many nullable types that are actually always logically invalid if null.
In these cases it can be quite painful to use the generated types directly. Similarly painful having to write wrapper types and conversions from generated types.
More nesting = more pain.
Example
foo
is nullable in the contract so the generated code correctly wraps it in anOption
.What I'd like instead
In the example imagine a common case where I consider any message missing
foo
to be invalid. I'd prefer to avoid:Option
and having two types with the same/similar names + conversion logic.I'd like to have a way to configure code gen to have no option, prost instead produces an error on decoding if the protobuf bytes are missing this field.
Perhaps it could work by opting in, in a similar way to extern_path, e.g.
fail_decode_if_missing(proto_path)
(or better name)Derived code would require
foo
is present, failing to decode otherwise.E.g. the following test would succeed.
The text was updated successfully, but these errors were encountered: