github.com/bakjos/protoreflect@v1.9.2/desc/internal/proto3_optional.go (about)

     1  package internal
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  
     7  	"github.com/bakjos/protoreflect/internal/codec"
     8  	"github.com/golang/protobuf/proto"
     9  	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
    10  
    11  	"github.com/bakjos/protoreflect/internal"
    12  )
    13  
    14  // NB: We use reflection or unknown fields in case we are linked against an older
    15  // version of the proto runtime which does not know about the proto3_optional field.
    16  // We don't require linking with newer version (which would greatly simplify this)
    17  // because that means pulling in v1.4+ of the protobuf runtime, which has some
    18  // compatibility issues. (We'll be nice to users and not require they upgrade to
    19  // that latest runtime to upgrade to newer protoreflect.)
    20  
    21  func GetProto3Optional(fd *dpb.FieldDescriptorProto) bool {
    22  	type newerFieldDesc interface {
    23  		GetProto3Optional() bool
    24  	}
    25  	var pm proto.Message = fd
    26  	if fd, ok := pm.(newerFieldDesc); ok {
    27  		return fd.GetProto3Optional()
    28  	}
    29  
    30  	// Field does not exist, so we have to examine unknown fields
    31  	// (we just silently bail if we have problems parsing them)
    32  	unk := internal.GetUnrecognized(pm)
    33  	buf := codec.NewBuffer(unk)
    34  	for {
    35  		tag, wt, err := buf.DecodeTagAndWireType()
    36  		if err != nil {
    37  			return false
    38  		}
    39  		if tag == Field_proto3OptionalTag && wt == proto.WireVarint {
    40  			v, _ := buf.DecodeVarint()
    41  			return v != 0
    42  		}
    43  		if err := buf.SkipField(wt); err != nil {
    44  			return false
    45  		}
    46  	}
    47  }
    48  
    49  func SetProto3Optional(fd *dpb.FieldDescriptorProto) {
    50  	rv := reflect.ValueOf(fd).Elem()
    51  	fld := rv.FieldByName("Proto3Optional")
    52  	if fld.IsValid() {
    53  		fld.Set(reflect.ValueOf(proto.Bool(true)))
    54  		return
    55  	}
    56  
    57  	// Field does not exist, so we have to store as unknown field.
    58  	var buf codec.Buffer
    59  	if err := buf.EncodeTagAndWireType(Field_proto3OptionalTag, proto.WireVarint); err != nil {
    60  		// TODO: panic? log?
    61  		return
    62  	}
    63  	if err := buf.EncodeVarint(1); err != nil {
    64  		// TODO: panic? log?
    65  		return
    66  	}
    67  	internal.SetUnrecognized(fd, buf.Bytes())
    68  }
    69  
    70  // ProcessProto3OptionalFields adds synthetic oneofs to the given message descriptor
    71  // for each proto3 optional field. It also updates the fields to have the correct
    72  // oneof index reference.
    73  func ProcessProto3OptionalFields(msgd *dpb.DescriptorProto) {
    74  	var allNames map[string]struct{}
    75  	for _, fd := range msgd.Field {
    76  		if GetProto3Optional(fd) {
    77  			// lazy init the set of all names
    78  			if allNames == nil {
    79  				allNames = map[string]struct{}{}
    80  				for _, fd := range msgd.Field {
    81  					allNames[fd.GetName()] = struct{}{}
    82  				}
    83  				for _, fd := range msgd.Extension {
    84  					allNames[fd.GetName()] = struct{}{}
    85  				}
    86  				for _, ed := range msgd.EnumType {
    87  					allNames[ed.GetName()] = struct{}{}
    88  					for _, evd := range ed.Value {
    89  						allNames[evd.GetName()] = struct{}{}
    90  					}
    91  				}
    92  				for _, fd := range msgd.NestedType {
    93  					allNames[fd.GetName()] = struct{}{}
    94  				}
    95  				for _, n := range msgd.ReservedName {
    96  					allNames[n] = struct{}{}
    97  				}
    98  			}
    99  
   100  			// Compute a name for the synthetic oneof. This uses the same
   101  			// algorithm as used in protoc:
   102  			//  https://github.com/protocolbuffers/protobuf/blob/74ad62759e0a9b5a21094f3fb9bb4ebfaa0d1ab8/src/google/protobuf/compiler/parser.cc#L785-L803
   103  			ooName := fd.GetName()
   104  			if !strings.HasPrefix(ooName, "_") {
   105  				ooName = "_" + ooName
   106  			}
   107  			for {
   108  				_, ok := allNames[ooName]
   109  				if !ok {
   110  					// found a unique name
   111  					allNames[ooName] = struct{}{}
   112  					break
   113  				}
   114  				ooName = "X" + ooName
   115  			}
   116  
   117  			fd.OneofIndex = proto.Int32(int32(len(msgd.OneofDecl)))
   118  			msgd.OneofDecl = append(msgd.OneofDecl, &dpb.OneofDescriptorProto{Name: proto.String(ooName)})
   119  		}
   120  	}
   121  }