mirror of
https://github.com/protocolbuffers/protobuf-go.git
synced 2025-04-10 06:44:15 +00:00
encoding/textpb: add AllowPartial option to MarshalOptions and UnmarshalOptions
Provide AllowPartial option to accept messages with missing required field during marshaling and unmarshaling. Change-Id: Ia23783870a8125633f8ddc0b686984b4c5ca15ba Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/169500 Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com> Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
307a7930c8
commit
42577eaa4d
@ -18,15 +18,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Unmarshal reads the given []byte into the given proto.Message.
|
// Unmarshal reads the given []byte into the given proto.Message.
|
||||||
// TODO: may want to describe when Unmarshal returns error.
|
|
||||||
func Unmarshal(m proto.Message, b []byte) error {
|
func Unmarshal(m proto.Message, b []byte) error {
|
||||||
return UnmarshalOptions{}.Unmarshal(m, b)
|
return UnmarshalOptions{}.Unmarshal(m, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalOptions is a configurable textproto format parser.
|
// UnmarshalOptions is a configurable textproto format unmarshaler.
|
||||||
type UnmarshalOptions struct {
|
type UnmarshalOptions struct {
|
||||||
pragma.NoUnkeyedLiterals
|
pragma.NoUnkeyedLiterals
|
||||||
|
|
||||||
|
// AllowPartial accepts input for messages that will result in missing
|
||||||
|
// required fields. If AllowPartial is false (the default), Unmarshal will
|
||||||
|
// return error if there are any missing required fields.
|
||||||
|
AllowPartial bool
|
||||||
|
|
||||||
// Resolver is the registry used for type lookups when unmarshaling extensions
|
// Resolver is the registry used for type lookups when unmarshaling extensions
|
||||||
// and processing Any. If Resolver is not set, unmarshaling will default to
|
// and processing Any. If Resolver is not set, unmarshaling will default to
|
||||||
// using protoregistry.GlobalTypes.
|
// using protoregistry.GlobalTypes.
|
||||||
@ -161,19 +165,21 @@ func (o UnmarshalOptions) unmarshalMessage(tmsg [][2]text.Value, m pref.Message)
|
|||||||
if err := o.unmarshalSingular(tval, fd, knownFields); !nerr.Merge(err) {
|
if err := o.unmarshalSingular(tval, fd, knownFields); !nerr.Merge(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if cardinality == pref.Required {
|
if !o.AllowPartial && cardinality == pref.Required {
|
||||||
reqNums.Set(num)
|
reqNums.Set(num)
|
||||||
}
|
}
|
||||||
seenNums.Set(num)
|
seenNums.Set(num)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for any missing required fields.
|
if !o.AllowPartial {
|
||||||
allReqNums := msgType.RequiredNumbers()
|
// Check for any missing required fields.
|
||||||
if reqNums.Len() != allReqNums.Len() {
|
allReqNums := msgType.RequiredNumbers()
|
||||||
for i := 0; i < allReqNums.Len(); i++ {
|
if reqNums.Len() != allReqNums.Len() {
|
||||||
if num := allReqNums.Get(i); !reqNums.Has(uint64(num)) {
|
for i := 0; i < allReqNums.Len(); i++ {
|
||||||
nerr.AppendRequiredNotSet(string(fieldDescs.ByNumber(num).FullName()))
|
if num := allReqNums.Get(i); !reqNums.Has(uint64(num)) {
|
||||||
|
nerr.AppendRequiredNotSet(string(fieldDescs.ByNumber(num).FullName()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -952,18 +952,18 @@ int32_to_str: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
desc: "proto2 required fields not set",
|
desc: "required fields not set",
|
||||||
inputMessage: &pb2.Requireds{},
|
inputMessage: &pb2.Requireds{},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
}, {
|
}, {
|
||||||
desc: "proto2 required field set",
|
desc: "required field set",
|
||||||
inputMessage: &pb2.PartialRequired{},
|
inputMessage: &pb2.PartialRequired{},
|
||||||
inputText: "req_string: 'this is required'",
|
inputText: "req_string: 'this is required'",
|
||||||
wantMessage: &pb2.PartialRequired{
|
wantMessage: &pb2.PartialRequired{
|
||||||
ReqString: scalar.String("this is required"),
|
ReqString: scalar.String("this is required"),
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
desc: "proto2 required fields partially set",
|
desc: "required fields partially set",
|
||||||
inputMessage: &pb2.Requireds{},
|
inputMessage: &pb2.Requireds{},
|
||||||
inputText: `
|
inputText: `
|
||||||
req_bool: false
|
req_bool: false
|
||||||
@ -979,7 +979,23 @@ req_enum: ONE
|
|||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
}, {
|
}, {
|
||||||
desc: "proto2 required fields all set",
|
desc: "required fields partially set with AllowPartial",
|
||||||
|
umo: textpb.UnmarshalOptions{AllowPartial: true},
|
||||||
|
inputMessage: &pb2.Requireds{},
|
||||||
|
inputText: `
|
||||||
|
req_bool: false
|
||||||
|
req_sfixed64: 3203386110
|
||||||
|
req_string: "hello"
|
||||||
|
req_enum: ONE
|
||||||
|
`,
|
||||||
|
wantMessage: &pb2.Requireds{
|
||||||
|
ReqBool: scalar.Bool(false),
|
||||||
|
ReqSfixed64: scalar.Int64(0xbeefcafe),
|
||||||
|
ReqString: scalar.String("hello"),
|
||||||
|
ReqEnum: pb2.Enum_ONE.Enum(),
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
desc: "required fields all set",
|
||||||
inputMessage: &pb2.Requireds{},
|
inputMessage: &pb2.Requireds{},
|
||||||
inputText: `
|
inputText: `
|
||||||
req_bool: false
|
req_bool: false
|
||||||
@ -1005,6 +1021,14 @@ req_nested: {}
|
|||||||
OptNested: &pb2.NestedWithRequired{},
|
OptNested: &pb2.NestedWithRequired{},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
desc: "indirect required field with AllowPartial",
|
||||||
|
umo: textpb.UnmarshalOptions{AllowPartial: true},
|
||||||
|
inputMessage: &pb2.IndirectRequired{},
|
||||||
|
inputText: "opt_nested: {}",
|
||||||
|
wantMessage: &pb2.IndirectRequired{
|
||||||
|
OptNested: &pb2.NestedWithRequired{},
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
desc: "indirect required field in repeated",
|
desc: "indirect required field in repeated",
|
||||||
inputMessage: &pb2.IndirectRequired{},
|
inputMessage: &pb2.IndirectRequired{},
|
||||||
@ -1013,9 +1037,6 @@ rpt_nested: {
|
|||||||
req_string: "one"
|
req_string: "one"
|
||||||
}
|
}
|
||||||
rpt_nested: {}
|
rpt_nested: {}
|
||||||
rpt_nested: {
|
|
||||||
req_string: "three"
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
wantMessage: &pb2.IndirectRequired{
|
wantMessage: &pb2.IndirectRequired{
|
||||||
RptNested: []*pb2.NestedWithRequired{
|
RptNested: []*pb2.NestedWithRequired{
|
||||||
@ -1023,12 +1044,27 @@ rpt_nested: {
|
|||||||
ReqString: scalar.String("one"),
|
ReqString: scalar.String("one"),
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
{
|
|
||||||
ReqString: scalar.String("three"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
desc: "indirect required field in repeated with AllowPartial",
|
||||||
|
umo: textpb.UnmarshalOptions{AllowPartial: true},
|
||||||
|
inputMessage: &pb2.IndirectRequired{},
|
||||||
|
inputText: `
|
||||||
|
rpt_nested: {
|
||||||
|
req_string: "one"
|
||||||
|
}
|
||||||
|
rpt_nested: {}
|
||||||
|
`,
|
||||||
|
wantMessage: &pb2.IndirectRequired{
|
||||||
|
RptNested: []*pb2.NestedWithRequired{
|
||||||
|
{
|
||||||
|
ReqString: scalar.String("one"),
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
desc: "indirect required field in map",
|
desc: "indirect required field in map",
|
||||||
inputMessage: &pb2.IndirectRequired{},
|
inputMessage: &pb2.IndirectRequired{},
|
||||||
@ -1052,6 +1088,29 @@ str_to_nested: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
desc: "indirect required field in map with AllowPartial",
|
||||||
|
umo: textpb.UnmarshalOptions{AllowPartial: true},
|
||||||
|
inputMessage: &pb2.IndirectRequired{},
|
||||||
|
inputText: `
|
||||||
|
str_to_nested: {
|
||||||
|
key: "missing"
|
||||||
|
}
|
||||||
|
str_to_nested: {
|
||||||
|
key: "contains"
|
||||||
|
value: {
|
||||||
|
req_string: "here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
wantMessage: &pb2.IndirectRequired{
|
||||||
|
StrToNested: map[string]*pb2.NestedWithRequired{
|
||||||
|
"missing": &pb2.NestedWithRequired{},
|
||||||
|
"contains": &pb2.NestedWithRequired{
|
||||||
|
ReqString: scalar.String("here"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
desc: "indirect required field in oneof",
|
desc: "indirect required field in oneof",
|
||||||
inputMessage: &pb2.IndirectRequired{},
|
inputMessage: &pb2.IndirectRequired{},
|
||||||
@ -1063,6 +1122,17 @@ str_to_nested: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
desc: "indirect required field in oneof with AllowPartial",
|
||||||
|
umo: textpb.UnmarshalOptions{AllowPartial: true},
|
||||||
|
inputMessage: &pb2.IndirectRequired{},
|
||||||
|
inputText: `oneof_nested: {}
|
||||||
|
`,
|
||||||
|
wantMessage: &pb2.IndirectRequired{
|
||||||
|
Union: &pb2.IndirectRequired_OneofNested{
|
||||||
|
OneofNested: &pb2.NestedWithRequired{},
|
||||||
|
},
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
desc: "ignore reserved field",
|
desc: "ignore reserved field",
|
||||||
inputMessage: &pb2.Nests{},
|
inputMessage: &pb2.Nests{},
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Marshal writes the given proto.Message in textproto format using default options.
|
// Marshal writes the given proto.Message in textproto format using default options.
|
||||||
// TODO: may want to describe when Marshal returns error.
|
|
||||||
func Marshal(m proto.Message) ([]byte, error) {
|
func Marshal(m proto.Message) ([]byte, error) {
|
||||||
return MarshalOptions{}.Marshal(m)
|
return MarshalOptions{}.Marshal(m)
|
||||||
}
|
}
|
||||||
@ -27,6 +26,11 @@ func Marshal(m proto.Message) ([]byte, error) {
|
|||||||
type MarshalOptions struct {
|
type MarshalOptions struct {
|
||||||
pragma.NoUnkeyedLiterals
|
pragma.NoUnkeyedLiterals
|
||||||
|
|
||||||
|
// AllowPartial allows messages that have missing required fields to marshal
|
||||||
|
// without returning an error. If AllowPartial is false (the default),
|
||||||
|
// Marshal will return error if there are any missing required fields.
|
||||||
|
AllowPartial bool
|
||||||
|
|
||||||
// If Indent is a non-empty string, it causes entries for a Message to be
|
// If Indent is a non-empty string, it causes entries for a Message to be
|
||||||
// preceded by the indent and trailed by a newline. Indent can only be
|
// preceded by the indent and trailed by a newline. Indent can only be
|
||||||
// composed of space or tab characters.
|
// composed of space or tab characters.
|
||||||
@ -85,7 +89,7 @@ func (o MarshalOptions) marshalMessage(m pref.Message) (text.Value, error) {
|
|||||||
num := fd.Number()
|
num := fd.Number()
|
||||||
|
|
||||||
if !knownFields.Has(num) {
|
if !knownFields.Has(num) {
|
||||||
if fd.Cardinality() == pref.Required {
|
if !o.AllowPartial && fd.Cardinality() == pref.Required {
|
||||||
// Treat unset required fields as a non-fatal error.
|
// Treat unset required fields as a non-fatal error.
|
||||||
nerr.AppendRequiredNotSet(string(fd.FullName()))
|
nerr.AppendRequiredNotSet(string(fd.FullName()))
|
||||||
}
|
}
|
||||||
|
@ -683,12 +683,12 @@ str_to_oneofs: {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "proto2 required fields not set",
|
desc: "required fields not set",
|
||||||
input: &pb2.Requireds{},
|
input: &pb2.Requireds{},
|
||||||
want: "\n",
|
want: "\n",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
}, {
|
}, {
|
||||||
desc: "proto2 required fields partially set",
|
desc: "required fields partially set",
|
||||||
input: &pb2.Requireds{
|
input: &pb2.Requireds{
|
||||||
ReqBool: scalar.Bool(false),
|
ReqBool: scalar.Bool(false),
|
||||||
ReqSfixed64: scalar.Int64(0xbeefcafe),
|
ReqSfixed64: scalar.Int64(0xbeefcafe),
|
||||||
@ -704,7 +704,23 @@ req_enum: ONE
|
|||||||
`,
|
`,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
}, {
|
}, {
|
||||||
desc: "proto2 required fields all set",
|
desc: "required fields not set with AllowPartial",
|
||||||
|
mo: textpb.MarshalOptions{AllowPartial: true},
|
||||||
|
input: &pb2.Requireds{
|
||||||
|
ReqBool: scalar.Bool(false),
|
||||||
|
ReqSfixed64: scalar.Int64(0xbeefcafe),
|
||||||
|
ReqDouble: scalar.Float64(math.NaN()),
|
||||||
|
ReqString: scalar.String("hello"),
|
||||||
|
ReqEnum: pb2.Enum_ONE.Enum(),
|
||||||
|
},
|
||||||
|
want: `req_bool: false
|
||||||
|
req_sfixed64: 3203386110
|
||||||
|
req_double: nan
|
||||||
|
req_string: "hello"
|
||||||
|
req_enum: ONE
|
||||||
|
`,
|
||||||
|
}, {
|
||||||
|
desc: "required fields all set",
|
||||||
input: &pb2.Requireds{
|
input: &pb2.Requireds{
|
||||||
ReqBool: scalar.Bool(false),
|
ReqBool: scalar.Bool(false),
|
||||||
ReqSfixed64: scalar.Int64(0),
|
ReqSfixed64: scalar.Int64(0),
|
||||||
@ -727,6 +743,13 @@ req_nested: {}
|
|||||||
},
|
},
|
||||||
want: "opt_nested: {}\n",
|
want: "opt_nested: {}\n",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
desc: "indirect required field with AllowPartial",
|
||||||
|
mo: textpb.MarshalOptions{AllowPartial: true},
|
||||||
|
input: &pb2.IndirectRequired{
|
||||||
|
OptNested: &pb2.NestedWithRequired{},
|
||||||
|
},
|
||||||
|
want: "opt_nested: {}\n",
|
||||||
}, {
|
}, {
|
||||||
desc: "indirect required field in empty repeated",
|
desc: "indirect required field in empty repeated",
|
||||||
input: &pb2.IndirectRequired{
|
input: &pb2.IndirectRequired{
|
||||||
@ -742,6 +765,15 @@ req_nested: {}
|
|||||||
},
|
},
|
||||||
want: "rpt_nested: {}\n",
|
want: "rpt_nested: {}\n",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
desc: "indirect required field in repeated with AllowPartial",
|
||||||
|
mo: textpb.MarshalOptions{AllowPartial: true},
|
||||||
|
input: &pb2.IndirectRequired{
|
||||||
|
RptNested: []*pb2.NestedWithRequired{
|
||||||
|
&pb2.NestedWithRequired{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "rpt_nested: {}\n",
|
||||||
}, {
|
}, {
|
||||||
desc: "indirect required field in empty map",
|
desc: "indirect required field in empty map",
|
||||||
input: &pb2.IndirectRequired{
|
input: &pb2.IndirectRequired{
|
||||||
@ -761,6 +793,19 @@ req_nested: {}
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
desc: "indirect required field in map with AllowPartial",
|
||||||
|
mo: textpb.MarshalOptions{AllowPartial: true},
|
||||||
|
input: &pb2.IndirectRequired{
|
||||||
|
StrToNested: map[string]*pb2.NestedWithRequired{
|
||||||
|
"fail": &pb2.NestedWithRequired{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: `str_to_nested: {
|
||||||
|
key: "fail"
|
||||||
|
value: {}
|
||||||
|
}
|
||||||
|
`,
|
||||||
}, {
|
}, {
|
||||||
desc: "indirect required field in oneof",
|
desc: "indirect required field in oneof",
|
||||||
input: &pb2.IndirectRequired{
|
input: &pb2.IndirectRequired{
|
||||||
@ -770,6 +815,15 @@ req_nested: {}
|
|||||||
},
|
},
|
||||||
want: "oneof_nested: {}\n",
|
want: "oneof_nested: {}\n",
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
}, {
|
||||||
|
desc: "indirect required field in oneof with AllowPartial",
|
||||||
|
mo: textpb.MarshalOptions{AllowPartial: true},
|
||||||
|
input: &pb2.IndirectRequired{
|
||||||
|
Union: &pb2.IndirectRequired_OneofNested{
|
||||||
|
OneofNested: &pb2.NestedWithRequired{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "oneof_nested: {}\n",
|
||||||
}, {
|
}, {
|
||||||
desc: "unknown varint and fixed types",
|
desc: "unknown varint and fixed types",
|
||||||
input: &pb2.Scalars{
|
input: &pb2.Scalars{
|
||||||
|
@ -91,6 +91,6 @@ func (Export) ExtensionTypeOf(d pref.ExtensionDescriptor, t interface{}) pref.Ex
|
|||||||
// MessageStringOf returns the message value as a string,
|
// MessageStringOf returns the message value as a string,
|
||||||
// which is the message serialized in the protobuf text format.
|
// which is the message serialized in the protobuf text format.
|
||||||
func (Export) MessageStringOf(m pref.ProtoMessage) string {
|
func (Export) MessageStringOf(m pref.ProtoMessage) string {
|
||||||
b, _ := textpb.Marshal(m)
|
b, _ := textpb.MarshalOptions{AllowPartial: true}.Marshal(m)
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user