[#123] protogen: Treat bytes field as non-nullable

In protobuf 3.12 they have added an support for `optional` keyword,
which has made it into the main branch in 3.15.
https://github.com/protocolbuffers/protobuf/blob/main/docs/implementing_proto3_presence.md
https://github.com/protocolbuffers/protobuf/blob/v3.12.0/docs/field_presence.md#presence-in-proto3-apis

This means that without an explicit `optional` keyword field presence
for scalars is not tracked, thus empty string in JSON should be
unmarshaled to a nil byte slice. Relevant decoding code and tests from
protojson:
fb995f184a/internal/impl/message_reflect_field.go (L327)
fb995f184a/encoding/protojson/decode_test.go (L134)
fb995f184a/encoding/protojson/decode_test.go (L156)

We do not support `optional` keyword and the generator will fail if it sees on.
So only implement the default behaviour.

Refs #122

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
This commit is contained in:
Evgenii Stratonikov 2024-10-11 10:17:42 +03:00
parent 29c522d5d8
commit 29f2157563
Signed by: fyrchik
SSH key fingerprint: SHA256:m/TTwCzjnRkXgnzEx9X92ccxy1CcVeinOgDb3NPWWmg
15 changed files with 12 additions and 10 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -50,9 +50,6 @@ func TestStableMarshalSingle(t *testing.T) {
var actualFrostfs generated.Primitives
require.NoError(t, actualFrostfs.UnmarshalJSON(r))
if len(actualFrostfs.FieldA) == 0 {
actualFrostfs.FieldA = nil
}
require.Equal(t, input, &actualFrostfs)
primitivesEqual(t, input, &actual)
@ -110,9 +107,6 @@ func TestStableMarshalSingle(t *testing.T) {
var actualFrostfs generated.Primitives
require.NoError(t, actualFrostfs.UnmarshalJSON(r))
if len(actualFrostfs.FieldA) == 0 {
actualFrostfs.FieldA = nil
}
require.Equal(t, tc.input, &actualFrostfs)
primitivesEqual(t, tc.input, &actual)
@ -124,9 +118,7 @@ func TestStableMarshalSingle(t *testing.T) {
func primitivesEqual(t *testing.T, a *generated.Primitives, b *test.Primitives) {
// Compare each field directly, because proto-generated code has private fields.
require.Equal(t, len(a.FieldA), len(b.FieldA))
if len(a.FieldA) != 0 {
require.Equal(t, a.FieldA, b.FieldA)
}
require.Equal(t, a.FieldB, b.FieldB)
require.Equal(t, a.FieldC, b.FieldC)
require.Equal(t, a.FieldD, b.FieldD)

Binary file not shown.

View file

@ -129,7 +129,17 @@ func emitJSONFieldRead(g *protogen.GeneratedFile, f *protogen.Field, name string
case protoreflect.StringKind:
template = "%s = in.String()"
case protoreflect.BytesKind:
template = "%s = in.Bytes()"
// Since some time ago proto3 support optional keyword, thus the presence is not tracked by default:
// https://github.com/protocolbuffers/protobuf-go/blob/fb995f184a1719ec42b247a3771d1036d92adf67/internal/impl/message_reflect_field.go#L327
// We do not explicitly support `optional` keyword, protoc will fail on such fileds.
// Thus, treat empty string as `[]byte(nil)`.
template = `{
tmp := in.Bytes()
if len(tmp) == 0 {
tmp = nil
}
%s = tmp
}`
case protoreflect.MessageKind:
if f.Desc.IsList() {
g.P("f = ", fieldType(g, f)[2:], "{}")