proto: add IsInitialized
Move all checks for required fields into a proto.IsInitialized function.
Initial testing makes me confident that we can provide a fast-path
implementation of IsInitialized which will perform more than
acceptably. (In the degenerate-but-common case where a message
transitively contains no required fields, this check can be nearly
zero cost.)
Unifying checks into a single function provides consistent behavior
between the wire, text, and json codecs.
Performing the check after decoding eliminates the wire decoder bug
where a split message is incorrectly seen as missing required fields.
Performing the check after decoding also provides consistent and
arguably more correct behavior when the target message was partially
prepopulated.
Change-Id: I9478b7bebb263af00c0d9f66a1f26e31ff553522
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/170787
Reviewed-by: Herbie Ong <herbie@google.com>
diff --git a/proto/decode.go b/proto/decode.go
index 2b871c4..11fed46 100644
--- a/proto/decode.go
+++ b/proto/decode.go
@@ -42,10 +42,18 @@
// Unmarshal parses the wire-format message in b and places the result in m.
func (o UnmarshalOptions) Unmarshal(b []byte, m Message) error {
// TODO: Reset m?
- if err := o.unmarshalMessageFast(b, m); err != errInternalNoFast {
+ err := o.unmarshalMessageFast(b, m)
+ if err == errInternalNoFast {
+ err = o.unmarshalMessage(b, m.ProtoReflect())
+ }
+ var nerr errors.NonFatal
+ if !nerr.Merge(err) {
return err
}
- return o.unmarshalMessage(b, m.ProtoReflect())
+ if !o.AllowPartial {
+ nerr.Merge(IsInitialized(m))
+ }
+ return nerr.E
}
func (o UnmarshalOptions) unmarshalMessageFast(b []byte, m Message) error {
@@ -100,9 +108,6 @@
}
b = b[tagLen+valLen:]
}
- if !o.AllowPartial {
- checkRequiredFields(m, &nerr)
- }
return nerr.E
}
@@ -204,9 +209,6 @@
if !haveVal {
switch valField.Kind() {
case protoreflect.GroupKind, protoreflect.MessageKind:
- if !o.AllowPartial {
- checkRequiredFields(val.Message(), &nerr)
- }
default:
val = valField.Default()
}