Skip to content

Commit

Permalink
Adds support for proto3opt
Browse files Browse the repository at this point in the history
Attempts to detect optional fields by using
`field.Oneof.Desc.IsSynthetic()` and relies on nil checking pointers for
the marshall and unmarshalling
  • Loading branch information
gburek-fastly authored and paralin committed Oct 29, 2024
1 parent c84205a commit 4ff7f98
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 23 deletions.
14 changes: 10 additions & 4 deletions features/json/message-marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,13 @@ nextField:

// If this is the first field in a oneof, write the if statement that checks for nil
// and start the switch statement for the oneof type.
if field.Oneof != nil && field == field.Oneof.Fields[0] {
if field.Oneof != nil && field == field.Oneof.Fields[0] && !field.Oneof.Desc.IsSynthetic() {
// NOTE: we don't support field masks here (yet).
g.P("if x.", field.Oneof.GoName, " != nil {")
g.P("switch ov := x.", field.Oneof.GoName, ".(type) {")
}

if field.Oneof != nil {
if field.Oneof != nil && !field.Oneof.Desc.IsSynthetic() {
// If we're in a oneof, check if this is the field that's set in the oneof.
g.P("case *", field.GoIdent.GoName, ":")
messageOrOneofIdent = "ov"
Expand Down Expand Up @@ -192,6 +192,12 @@ nextField:
switch field.Desc.Kind() {
default:
// Scalar types can be written by the library.
if field.Oneof != nil && field.Oneof.Desc.IsSynthetic() {
g.P("s.Write", g.libNameForField(field), "(*", messageOrOneofIdent, ".", fieldGoName, ")")
} else {
g.P("s.Write", g.libNameForField(field), "(", messageOrOneofIdent, ".", fieldGoName, ")")
}
case protoreflect.BytesKind:
g.P("s.Write", g.libNameForField(field), "(", messageOrOneofIdent, ".", fieldGoName, ")")
case protoreflect.EnumKind:
// If the field is of type enum, and the enum has a marshaler, use that.
Expand All @@ -207,12 +213,12 @@ nextField:
}

// If we're not in a oneof, end the "if not zero".
if field.Oneof == nil {
if field.Oneof == nil || field.Oneof.Desc.IsSynthetic() {
g.P("}") // end if x.{field.GoName} != zero value {
}

// If this is the last field in the oneof, close the switch and if statements.
if field.Oneof != nil && field == field.Oneof.Fields[len(field.Oneof.Fields)-1] {
if field.Oneof != nil && field == field.Oneof.Fields[len(field.Oneof.Fields)-1] && !field.Oneof.Desc.IsSynthetic() {
g.P("}") // end switch v := x.{field.Oneof.GoName}.(type) {
g.P("}") // end if x.{field.Oneof.GoName} != nil {
}
Expand Down
9 changes: 8 additions & 1 deletion features/json/message-unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ nextField:
messageOrOneofIdent := "x"

// If this field is in a oneof, allocate a new oneof value wrapper.
if field.Oneof != nil {
if field.Oneof != nil && !field.Oneof.Desc.IsSynthetic() {
g.P("ov := &", field.GoIdent.GoName, "{}")
g.P("x.", field.Oneof.GoName, " = ov")
messageOrOneofIdent = "ov"
Expand Down Expand Up @@ -190,6 +190,13 @@ nextField:
switch field.Desc.Kind() {
default:
// Scalar types can be read by the library.
if field.Oneof != nil && field.Oneof.Desc.IsSynthetic() {
g.P("t := s.Read", g.libNameForField(field), "()")
g.P(messageOrOneofIdent, ".", fieldGoName, " = &t")
} else {
g.P(messageOrOneofIdent, ".", fieldGoName, " = s.Read", g.libNameForField(field), "()")
}
case protoreflect.BytesKind:
g.P(messageOrOneofIdent, ".", fieldGoName, " = s.Read", g.libNameForField(field), "()")
case protoreflect.EnumKind:
// If the field is of type enum, and the enum has an unmarshaler, call the unmarshaler.
Expand Down
24 changes: 7 additions & 17 deletions features/json/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package json

import (
"fmt"
"slices"

"github.com/aperturerobotics/protobuf-go-lite/compiler/protogen"
"google.golang.org/protobuf/reflect/protoreflect"
Expand All @@ -28,26 +27,17 @@ func (g *jsonGenerator) genMessage(message *protogen.Message) {
return
}

// Check if the message has any optional fields and skip generation if so.
anyOptional := slices.ContainsFunc(message.Fields, func(f *protogen.Field) bool {
return f.Desc.HasOptionalKeyword()
})
g.genMessageMarshaler(message)
g.genStdMessageMarshaler(message)

if !anyOptional {
g.genMessageMarshaler(message)
g.genStdMessageMarshaler(message)

g.genMessageUnmarshaler(message)
g.genStdMessageUnmarshaler(message)
} else {
// We do not support marshaling this field, skip the entire message.
g.P("// NOTE: protobuf-go-lite json only supports proto3 and not proto3opt (optional fields).")
g.P()
}
g.genMessageUnmarshaler(message)
g.genStdMessageUnmarshaler(message)
}

func fieldIsNullable(field *protogen.Field) bool {
// In the supported subset of syntax (proto3 and not proto3opt) we only use pointers for messages.
if field.Oneof != nil && field.Oneof.Desc.IsSynthetic() {
return true
}
nullable := field.Desc.Kind() == protoreflect.MessageKind
return nullable
}
Expand Down
240 changes: 239 additions & 1 deletion testproto/proto3opt/opt.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4ff7f98

Please sign in to comment.