Skip to content
Snippets Groups Projects
properties.go 8.89 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Copyright 2010 The Go Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style
    // license that can be found in the LICENSE file.
    
    ale's avatar
    ale committed
    
    package proto
    
    import (
    	"fmt"
    	"reflect"
    	"strconv"
    	"strings"
    	"sync"
    
    
    	"google.golang.org/protobuf/reflect/protoreflect"
    	"google.golang.org/protobuf/runtime/protoimpl"
    
    ale's avatar
    ale committed
    )
    
    
    // StructProperties represents protocol buffer type information for a
    // generated protobuf message in the open-struct API.
    //
    // Deprecated: Do not use.
    
    ale's avatar
    ale committed
    type StructProperties struct {
    
    	// Prop are the properties for each field.
    	//
    	// Fields belonging to a oneof are stored in OneofTypes instead, with a
    	// single Properties representing the parent oneof held here.
    	//
    	// The order of Prop matches the order of fields in the Go struct.
    	// Struct fields that are not related to protobufs have a "XXX_" prefix
    	// in the Properties.Name and must be ignored by the user.
    	Prop []*Properties
    
    ale's avatar
    ale committed
    
    	// OneofTypes contains information about the oneof fields in this message.
    
    	// It is keyed by the protobuf field name.
    
    ale's avatar
    ale committed
    	OneofTypes map[string]*OneofProperties
    }
    
    
    // Properties represents the type information for a protobuf message field.
    //
    // Deprecated: Do not use.
    
    ale's avatar
    ale committed
    type Properties struct {
    
    	// Name is a placeholder name with little meaningful semantic value.
    	// If the name has an "XXX_" prefix, the entire Properties must be ignored.
    	Name string
    	// OrigName is the protobuf field name or oneof name.
    	OrigName string
    	// JSONName is the JSON name for the protobuf field.
    	JSONName string
    	// Enum is a placeholder name for enums.
    	// For historical reasons, this is neither the Go name for the enum,
    	// nor the protobuf name for the enum.
    	Enum string // Deprecated: Do not use.
    	// Weak contains the full name of the weakly referenced message.
    	Weak string
    	// Wire is a string representation of the wire type.
    	Wire string
    	// WireType is the protobuf wire type for the field.
    
    ale's avatar
    ale committed
    	WireType int
    
    	// Tag is the protobuf field number.
    	Tag int
    	// Required reports whether this is a required field.
    
    ale's avatar
    ale committed
    	Required bool
    
    	// Optional reports whether this is a optional field.
    
    ale's avatar
    ale committed
    	Optional bool
    
    	// Repeated reports whether this is a repeated field.
    
    ale's avatar
    ale committed
    	Repeated bool
    
    	// Packed reports whether this is a packed repeated field of scalars.
    	Packed bool
    	// Proto3 reports whether this field operates under the proto3 syntax.
    	Proto3 bool
    	// Oneof reports whether this field belongs within a oneof.
    	Oneof bool
    
    	// Default is the default value in string form.
    	Default string
    	// HasDefault reports whether the field has a default value.
    	HasDefault bool
    
    	// MapKeyProp is the properties for the key field for a map field.
    	MapKeyProp *Properties
    	// MapValProp is the properties for the value field for a map field.
    	MapValProp *Properties
    }
    
    ale's avatar
    ale committed
    
    
    // OneofProperties represents the type information for a protobuf oneof.
    //
    // Deprecated: Do not use.
    type OneofProperties struct {
    	// Type is a pointer to the generated wrapper type for the field value.
    	// This is nil for messages that are not in the open-struct API.
    	Type reflect.Type
    	// Field is the index into StructProperties.Prop for the containing oneof.
    	Field int
    	// Prop is the properties for the field.
    	Prop *Properties
    
    ale's avatar
    ale committed
    }
    
    // String formats the properties in the protobuf struct field tag style.
    func (p *Properties) String() string {
    	s := p.Wire
    
    	s += "," + strconv.Itoa(p.Tag)
    
    ale's avatar
    ale committed
    	if p.Required {
    		s += ",req"
    	}
    	if p.Optional {
    		s += ",opt"
    	}
    	if p.Repeated {
    		s += ",rep"
    	}
    	if p.Packed {
    		s += ",packed"
    	}
    	s += ",name=" + p.OrigName
    
    	if p.JSONName != "" {
    
    ale's avatar
    ale committed
    		s += ",json=" + p.JSONName
    	}
    
    	if len(p.Enum) > 0 {
    		s += ",enum=" + p.Enum
    	}
    	if len(p.Weak) > 0 {
    		s += ",weak=" + p.Weak
    	}
    	if p.Proto3 {
    
    ale's avatar
    ale committed
    		s += ",proto3"
    	}
    
    	if p.Oneof {
    
    ale's avatar
    ale committed
    		s += ",oneof"
    	}
    	if p.HasDefault {
    		s += ",def=" + p.Default
    	}
    	return s
    }
    
    // Parse populates p by parsing a string in the protobuf struct field tag style.
    
    func (p *Properties) Parse(tag string) {
    	// For example: "bytes,49,opt,name=foo,def=hello!"
    	for len(tag) > 0 {
    		i := strings.IndexByte(tag, ',')
    		if i < 0 {
    			i = len(tag)
    		}
    		switch s := tag[:i]; {
    		case strings.HasPrefix(s, "name="):
    			p.OrigName = s[len("name="):]
    		case strings.HasPrefix(s, "json="):
    			p.JSONName = s[len("json="):]
    		case strings.HasPrefix(s, "enum="):
    			p.Enum = s[len("enum="):]
    		case strings.HasPrefix(s, "weak="):
    			p.Weak = s[len("weak="):]
    		case strings.Trim(s, "0123456789") == "":
    			n, _ := strconv.ParseUint(s, 10, 32)
    			p.Tag = int(n)
    		case s == "opt":
    
    ale's avatar
    ale committed
    			p.Optional = true
    
    		case s == "req":
    			p.Required = true
    		case s == "rep":
    
    ale's avatar
    ale committed
    			p.Repeated = true
    
    		case s == "varint" || s == "zigzag32" || s == "zigzag64":
    			p.Wire = s
    			p.WireType = WireVarint
    		case s == "fixed32":
    			p.Wire = s
    			p.WireType = WireFixed32
    		case s == "fixed64":
    			p.Wire = s
    			p.WireType = WireFixed64
    		case s == "bytes":
    			p.Wire = s
    			p.WireType = WireBytes
    		case s == "group":
    			p.Wire = s
    			p.WireType = WireStartGroup
    		case s == "packed":
    
    ale's avatar
    ale committed
    			p.Packed = true
    
    		case s == "proto3":
    			p.Proto3 = true
    		case s == "oneof":
    			p.Oneof = true
    		case strings.HasPrefix(s, "def="):
    			// The default tag is special in that everything afterwards is the
    			// default regardless of the presence of commas.
    
    ale's avatar
    ale committed
    			p.HasDefault = true
    
    			p.Default, i = tag[len("def="):], len(tag)
    
    ale's avatar
    ale committed
    		}
    
    		tag = strings.TrimPrefix(tag[i:], ",")
    
    ale's avatar
    ale committed
    	}
    }
    
    // Init populates the properties from a protocol buffer struct tag.
    
    //
    // Deprecated: Do not use.
    
    ale's avatar
    ale committed
    func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
    	p.Name = name
    	p.OrigName = name
    	if tag == "" {
    		return
    	}
    	p.Parse(tag)
    
    
    	if typ != nil && typ.Kind() == reflect.Map {
    		p.MapKeyProp = new(Properties)
    		p.MapKeyProp.Init(nil, "Key", f.Tag.Get("protobuf_key"), nil)
    		p.MapValProp = new(Properties)
    		p.MapValProp.Init(nil, "Value", f.Tag.Get("protobuf_val"), nil)
    	}
    
    ale's avatar
    ale committed
    }
    
    
    var propertiesCache sync.Map // map[reflect.Type]*StructProperties
    
    ale's avatar
    ale committed
    
    
    // GetProperties returns the list of properties for the type represented by t,
    // which must be a generated protocol buffer message in the open-struct API,
    // where protobuf message fields are represented by exported Go struct fields.
    //
    // Deprecated: Use protobuf reflection instead.
    
    ale's avatar
    ale committed
    func GetProperties(t reflect.Type) *StructProperties {
    
    	if p, ok := propertiesCache.Load(t); ok {
    		return p.(*StructProperties)
    
    ale's avatar
    ale committed
    	}
    
    	p, _ := propertiesCache.LoadOrStore(t, newProperties(t))
    	return p.(*StructProperties)
    
    ale's avatar
    ale committed
    }
    
    
    func newProperties(t reflect.Type) *StructProperties {
    	if t.Kind() != reflect.Struct {
    		panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t))
    
    ale's avatar
    ale committed
    	}
    
    
    	var hasOneof bool
    
    ale's avatar
    ale committed
    	prop := new(StructProperties)
    
    
    	// Construct a list of properties for each field in the struct.
    
    ale's avatar
    ale committed
    	for i := 0; i < t.NumField(); i++ {
    		p := new(Properties)
    
    		f := t.Field(i)
    		tagField := f.Tag.Get("protobuf")
    		p.Init(f.Type, f.Name, tagField, &f)
    
    ale's avatar
    ale committed
    
    
    		tagOneof := f.Tag.Get("protobuf_oneof")
    		if tagOneof != "" {
    			hasOneof = true
    			p.OrigName = tagOneof
    
    ale's avatar
    ale committed
    		}
    
    
    		// Rename unrelated struct fields with the "XXX_" prefix since so much
    		// user code simply checks for this to exclude special fields.
    		if tagField == "" && tagOneof == "" && !strings.HasPrefix(p.Name, "XXX_") {
    			p.Name = "XXX_" + p.Name
    			p.OrigName = "XXX_" + p.OrigName
    		} else if p.Weak != "" {
    			p.Name = p.OrigName // avoid possible "XXX_" prefix on weak field
    		}
    
    ale's avatar
    ale committed
    
    
    		prop.Prop = append(prop.Prop, p)
    
    ale's avatar
    ale committed
    	}
    
    
    	// Construct a mapping of oneof field names to properties.
    	if hasOneof {
    		var oneofWrappers []interface{}
    		if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofFuncs"); ok {
    			oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[3].Interface().([]interface{})
    		}
    		if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofWrappers"); ok {
    			oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0].Interface().([]interface{})
    		}
    		if m, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(protoreflect.ProtoMessage); ok {
    			if m, ok := m.ProtoReflect().(interface{ ProtoMessageInfo() *protoimpl.MessageInfo }); ok {
    				oneofWrappers = m.ProtoMessageInfo().OneofWrappers
    			}
    		}
    
    
    ale's avatar
    ale committed
    		prop.OneofTypes = make(map[string]*OneofProperties)
    
    		for _, wrapper := range oneofWrappers {
    			p := &OneofProperties{
    				Type: reflect.ValueOf(wrapper).Type(), // *T
    
    ale's avatar
    ale committed
    				Prop: new(Properties),
    			}
    
    			f := p.Type.Elem().Field(0)
    			p.Prop.Name = f.Name
    			p.Prop.Parse(f.Tag.Get("protobuf"))
    
    			// Determine the struct field that contains this oneof.
    			// Each wrapper is assignable to exactly one parent field.
    			var foundOneof bool
    			for i := 0; i < t.NumField() && !foundOneof; i++ {
    				if p.Type.AssignableTo(t.Field(i).Type) {
    					p.Field = i
    					foundOneof = true
    
    ale's avatar
    ale committed
    				}
    			}
    
    			if !foundOneof {
    				panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t))
    			}
    			prop.OneofTypes[p.Prop.OrigName] = p
    
    ale's avatar
    ale committed
    		}
    	}
    
    	return prop
    }
    
    
    func (sp *StructProperties) Len() int           { return len(sp.Prop) }
    func (sp *StructProperties) Less(i, j int) bool { return false }
    func (sp *StructProperties) Swap(i, j int)      { return }