khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 1 | // Copyright 2010 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 4 | |
| 5 | package proto |
| 6 | |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 7 | import ( |
| 8 | "fmt" |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 9 | "reflect" |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 10 | "strconv" |
| 11 | "strings" |
| 12 | "sync" |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 13 | |
| 14 | "google.golang.org/protobuf/reflect/protoreflect" |
| 15 | "google.golang.org/protobuf/runtime/protoimpl" |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 16 | ) |
| 17 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 18 | // StructProperties represents protocol buffer type information for a |
| 19 | // generated protobuf message in the open-struct API. |
| 20 | // |
| 21 | // Deprecated: Do not use. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 22 | type StructProperties struct { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 23 | // Prop are the properties for each field. |
| 24 | // |
| 25 | // Fields belonging to a oneof are stored in OneofTypes instead, with a |
| 26 | // single Properties representing the parent oneof held here. |
| 27 | // |
| 28 | // The order of Prop matches the order of fields in the Go struct. |
| 29 | // Struct fields that are not related to protobufs have a "XXX_" prefix |
| 30 | // in the Properties.Name and must be ignored by the user. |
| 31 | Prop []*Properties |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 32 | |
| 33 | // OneofTypes contains information about the oneof fields in this message. |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 34 | // It is keyed by the protobuf field name. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 35 | OneofTypes map[string]*OneofProperties |
| 36 | } |
| 37 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 38 | // Properties represents the type information for a protobuf message field. |
| 39 | // |
| 40 | // Deprecated: Do not use. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 41 | type Properties struct { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 42 | // Name is a placeholder name with little meaningful semantic value. |
| 43 | // If the name has an "XXX_" prefix, the entire Properties must be ignored. |
| 44 | Name string |
| 45 | // OrigName is the protobuf field name or oneof name. |
| 46 | OrigName string |
| 47 | // JSONName is the JSON name for the protobuf field. |
| 48 | JSONName string |
| 49 | // Enum is a placeholder name for enums. |
| 50 | // For historical reasons, this is neither the Go name for the enum, |
| 51 | // nor the protobuf name for the enum. |
| 52 | Enum string // Deprecated: Do not use. |
| 53 | // Weak contains the full name of the weakly referenced message. |
| 54 | Weak string |
| 55 | // Wire is a string representation of the wire type. |
| 56 | Wire string |
| 57 | // WireType is the protobuf wire type for the field. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 58 | WireType int |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 59 | // Tag is the protobuf field number. |
| 60 | Tag int |
| 61 | // Required reports whether this is a required field. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 62 | Required bool |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 63 | // Optional reports whether this is a optional field. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 64 | Optional bool |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 65 | // Repeated reports whether this is a repeated field. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 66 | Repeated bool |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 67 | // Packed reports whether this is a packed repeated field of scalars. |
| 68 | Packed bool |
| 69 | // Proto3 reports whether this field operates under the proto3 syntax. |
| 70 | Proto3 bool |
| 71 | // Oneof reports whether this field belongs within a oneof. |
| 72 | Oneof bool |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 73 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 74 | // Default is the default value in string form. |
| 75 | Default string |
| 76 | // HasDefault reports whether the field has a default value. |
| 77 | HasDefault bool |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 78 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 79 | // MapKeyProp is the properties for the key field for a map field. |
| 80 | MapKeyProp *Properties |
| 81 | // MapValProp is the properties for the value field for a map field. |
| 82 | MapValProp *Properties |
| 83 | } |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 84 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 85 | // OneofProperties represents the type information for a protobuf oneof. |
| 86 | // |
| 87 | // Deprecated: Do not use. |
| 88 | type OneofProperties struct { |
| 89 | // Type is a pointer to the generated wrapper type for the field value. |
| 90 | // This is nil for messages that are not in the open-struct API. |
| 91 | Type reflect.Type |
| 92 | // Field is the index into StructProperties.Prop for the containing oneof. |
| 93 | Field int |
| 94 | // Prop is the properties for the field. |
| 95 | Prop *Properties |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 96 | } |
| 97 | |
| 98 | // String formats the properties in the protobuf struct field tag style. |
| 99 | func (p *Properties) String() string { |
| 100 | s := p.Wire |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 101 | s += "," + strconv.Itoa(p.Tag) |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 102 | if p.Required { |
| 103 | s += ",req" |
| 104 | } |
| 105 | if p.Optional { |
| 106 | s += ",opt" |
| 107 | } |
| 108 | if p.Repeated { |
| 109 | s += ",rep" |
| 110 | } |
| 111 | if p.Packed { |
| 112 | s += ",packed" |
| 113 | } |
| 114 | s += ",name=" + p.OrigName |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 115 | if p.JSONName != "" { |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 116 | s += ",json=" + p.JSONName |
| 117 | } |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 118 | if len(p.Enum) > 0 { |
| 119 | s += ",enum=" + p.Enum |
| 120 | } |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 121 | if len(p.Weak) > 0 { |
| 122 | s += ",weak=" + p.Weak |
| 123 | } |
| 124 | if p.Proto3 { |
| 125 | s += ",proto3" |
| 126 | } |
| 127 | if p.Oneof { |
| 128 | s += ",oneof" |
| 129 | } |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 130 | if p.HasDefault { |
| 131 | s += ",def=" + p.Default |
| 132 | } |
| 133 | return s |
| 134 | } |
| 135 | |
| 136 | // Parse populates p by parsing a string in the protobuf struct field tag style. |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 137 | func (p *Properties) Parse(tag string) { |
| 138 | // For example: "bytes,49,opt,name=foo,def=hello!" |
| 139 | for len(tag) > 0 { |
| 140 | i := strings.IndexByte(tag, ',') |
| 141 | if i < 0 { |
| 142 | i = len(tag) |
| 143 | } |
| 144 | switch s := tag[:i]; { |
| 145 | case strings.HasPrefix(s, "name="): |
| 146 | p.OrigName = s[len("name="):] |
| 147 | case strings.HasPrefix(s, "json="): |
| 148 | p.JSONName = s[len("json="):] |
| 149 | case strings.HasPrefix(s, "enum="): |
| 150 | p.Enum = s[len("enum="):] |
| 151 | case strings.HasPrefix(s, "weak="): |
| 152 | p.Weak = s[len("weak="):] |
| 153 | case strings.Trim(s, "0123456789") == "": |
| 154 | n, _ := strconv.ParseUint(s, 10, 32) |
| 155 | p.Tag = int(n) |
| 156 | case s == "opt": |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 157 | p.Optional = true |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 158 | case s == "req": |
| 159 | p.Required = true |
| 160 | case s == "rep": |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 161 | p.Repeated = true |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 162 | case s == "varint" || s == "zigzag32" || s == "zigzag64": |
| 163 | p.Wire = s |
| 164 | p.WireType = WireVarint |
| 165 | case s == "fixed32": |
| 166 | p.Wire = s |
| 167 | p.WireType = WireFixed32 |
| 168 | case s == "fixed64": |
| 169 | p.Wire = s |
| 170 | p.WireType = WireFixed64 |
| 171 | case s == "bytes": |
| 172 | p.Wire = s |
| 173 | p.WireType = WireBytes |
| 174 | case s == "group": |
| 175 | p.Wire = s |
| 176 | p.WireType = WireStartGroup |
| 177 | case s == "packed": |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 178 | p.Packed = true |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 179 | case s == "proto3": |
| 180 | p.Proto3 = true |
| 181 | case s == "oneof": |
| 182 | p.Oneof = true |
| 183 | case strings.HasPrefix(s, "def="): |
| 184 | // The default tag is special in that everything afterwards is the |
| 185 | // default regardless of the presence of commas. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 186 | p.HasDefault = true |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 187 | p.Default, i = tag[len("def="):], len(tag) |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 188 | } |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 189 | tag = strings.TrimPrefix(tag[i:], ",") |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 190 | } |
| 191 | } |
| 192 | |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 193 | // Init populates the properties from a protocol buffer struct tag. |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 194 | // |
| 195 | // Deprecated: Do not use. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 196 | func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) { |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 197 | p.Name = name |
| 198 | p.OrigName = name |
| 199 | if tag == "" { |
| 200 | return |
| 201 | } |
| 202 | p.Parse(tag) |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 203 | |
| 204 | if typ != nil && typ.Kind() == reflect.Map { |
| 205 | p.MapKeyProp = new(Properties) |
| 206 | p.MapKeyProp.Init(nil, "Key", f.Tag.Get("protobuf_key"), nil) |
| 207 | p.MapValProp = new(Properties) |
| 208 | p.MapValProp.Init(nil, "Value", f.Tag.Get("protobuf_val"), nil) |
| 209 | } |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 210 | } |
| 211 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 212 | var propertiesCache sync.Map // map[reflect.Type]*StructProperties |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 213 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 214 | // GetProperties returns the list of properties for the type represented by t, |
| 215 | // which must be a generated protocol buffer message in the open-struct API, |
| 216 | // where protobuf message fields are represented by exported Go struct fields. |
| 217 | // |
| 218 | // Deprecated: Use protobuf reflection instead. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 219 | func GetProperties(t reflect.Type) *StructProperties { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 220 | if p, ok := propertiesCache.Load(t); ok { |
| 221 | return p.(*StructProperties) |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 222 | } |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 223 | p, _ := propertiesCache.LoadOrStore(t, newProperties(t)) |
| 224 | return p.(*StructProperties) |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 225 | } |
| 226 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 227 | func newProperties(t reflect.Type) *StructProperties { |
| 228 | if t.Kind() != reflect.Struct { |
| 229 | panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t)) |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 230 | } |
| 231 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 232 | var hasOneof bool |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 233 | prop := new(StructProperties) |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 234 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 235 | // Construct a list of properties for each field in the struct. |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 236 | for i := 0; i < t.NumField(); i++ { |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 237 | p := new(Properties) |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 238 | f := t.Field(i) |
| 239 | tagField := f.Tag.Get("protobuf") |
| 240 | p.Init(f.Type, f.Name, tagField, &f) |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 241 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 242 | tagOneof := f.Tag.Get("protobuf_oneof") |
| 243 | if tagOneof != "" { |
| 244 | hasOneof = true |
| 245 | p.OrigName = tagOneof |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 246 | } |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 247 | |
| 248 | // Rename unrelated struct fields with the "XXX_" prefix since so much |
| 249 | // user code simply checks for this to exclude special fields. |
| 250 | if tagField == "" && tagOneof == "" && !strings.HasPrefix(p.Name, "XXX_") { |
| 251 | p.Name = "XXX_" + p.Name |
| 252 | p.OrigName = "XXX_" + p.OrigName |
| 253 | } else if p.Weak != "" { |
| 254 | p.Name = p.OrigName // avoid possible "XXX_" prefix on weak field |
| 255 | } |
| 256 | |
| 257 | prop.Prop = append(prop.Prop, p) |
| 258 | } |
| 259 | |
| 260 | // Construct a mapping of oneof field names to properties. |
| 261 | if hasOneof { |
| 262 | var oneofWrappers []interface{} |
| 263 | if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofFuncs"); ok { |
| 264 | oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[3].Interface().([]interface{}) |
| 265 | } |
| 266 | if fn, ok := reflect.PtrTo(t).MethodByName("XXX_OneofWrappers"); ok { |
| 267 | oneofWrappers = fn.Func.Call([]reflect.Value{reflect.Zero(fn.Type.In(0))})[0].Interface().([]interface{}) |
| 268 | } |
| 269 | if m, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(protoreflect.ProtoMessage); ok { |
| 270 | if m, ok := m.ProtoReflect().(interface{ ProtoMessageInfo() *protoimpl.MessageInfo }); ok { |
| 271 | oneofWrappers = m.ProtoMessageInfo().OneofWrappers |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 272 | } |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 273 | } |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 274 | |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 275 | prop.OneofTypes = make(map[string]*OneofProperties) |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 276 | for _, wrapper := range oneofWrappers { |
| 277 | p := &OneofProperties{ |
| 278 | Type: reflect.ValueOf(wrapper).Type(), // *T |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 279 | Prop: new(Properties), |
| 280 | } |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 281 | f := p.Type.Elem().Field(0) |
| 282 | p.Prop.Name = f.Name |
| 283 | p.Prop.Parse(f.Tag.Get("protobuf")) |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 284 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 285 | // Determine the struct field that contains this oneof. |
| 286 | // Each wrapper is assignable to exactly one parent field. |
| 287 | var foundOneof bool |
| 288 | for i := 0; i < t.NumField() && !foundOneof; i++ { |
| 289 | if p.Type.AssignableTo(t.Field(i).Type) { |
| 290 | p.Field = i |
| 291 | foundOneof = true |
| 292 | } |
| 293 | } |
| 294 | if !foundOneof { |
| 295 | panic(fmt.Sprintf("%v is not a generated message in the open-struct API", t)) |
| 296 | } |
| 297 | prop.OneofTypes[p.Prop.OrigName] = p |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 298 | } |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 299 | } |
Scott Baker | 2c1c482 | 2019-10-16 11:02:41 -0700 | [diff] [blame] | 300 | |
| 301 | return prop |
| 302 | } |
| 303 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 304 | func (sp *StructProperties) Len() int { return len(sp.Prop) } |
| 305 | func (sp *StructProperties) Less(i, j int) bool { return false } |
| 306 | func (sp *StructProperties) Swap(i, j int) { return } |