| // Copyright 2018 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. |
| |
| // Package tag marshals and unmarshals the legacy struct tags as generated |
| // by historical versions of protoc-gen-go. |
| package tag |
| |
| import ( |
| "reflect" |
| "strconv" |
| "strings" |
| |
| "google.golang.org/protobuf/internal/encoding/defval" |
| "google.golang.org/protobuf/internal/filedesc" |
| "google.golang.org/protobuf/internal/strs" |
| "google.golang.org/protobuf/reflect/protoreflect" |
| ) |
| |
| var byteType = reflect.TypeOf(byte(0)) |
| |
| // Unmarshal decodes the tag into a prototype.Field. |
| // |
| // The goType is needed to determine the original protoreflect.Kind since the |
| // tag does not record sufficient information to determine that. |
| // The type is the underlying field type (e.g., a repeated field may be |
| // represented by []T, but the Go type passed in is just T). |
| // A list of enum value descriptors must be provided for enum fields. |
| // This does not populate the Enum or Message. |
| // |
| // This function is a best effort attempt; parsing errors are ignored. |
| func Unmarshal(tag string, goType reflect.Type, evs protoreflect.EnumValueDescriptors) protoreflect.FieldDescriptor { |
| f := new(filedesc.Field) |
| f.L0.ParentFile = filedesc.SurrogateProto2 |
| f.L1.EditionFeatures = f.L0.ParentFile.L1.EditionFeatures |
| for len(tag) > 0 { |
| i := strings.IndexByte(tag, ',') |
| if i < 0 { |
| i = len(tag) |
| } |
| switch s := tag[:i]; { |
| case strings.HasPrefix(s, "name="): |
| f.L0.FullName = protoreflect.FullName(s[len("name="):]) |
| case strings.Trim(s, "0123456789") == "": |
| n, _ := strconv.ParseUint(s, 10, 32) |
| f.L1.Number = protoreflect.FieldNumber(n) |
| case s == "opt": |
| f.L1.Cardinality = protoreflect.Optional |
| case s == "req": |
| f.L1.Cardinality = protoreflect.Required |
| case s == "rep": |
| f.L1.Cardinality = protoreflect.Repeated |
| case s == "varint": |
| switch goType.Kind() { |
| case reflect.Bool: |
| f.L1.Kind = protoreflect.BoolKind |
| case reflect.Int32: |
| f.L1.Kind = protoreflect.Int32Kind |
| case reflect.Int64: |
| f.L1.Kind = protoreflect.Int64Kind |
| case reflect.Uint32: |
| f.L1.Kind = protoreflect.Uint32Kind |
| case reflect.Uint64: |
| f.L1.Kind = protoreflect.Uint64Kind |
| } |
| case s == "zigzag32": |
| if goType.Kind() == reflect.Int32 { |
| f.L1.Kind = protoreflect.Sint32Kind |
| } |
| case s == "zigzag64": |
| if goType.Kind() == reflect.Int64 { |
| f.L1.Kind = protoreflect.Sint64Kind |
| } |
| case s == "fixed32": |
| switch goType.Kind() { |
| case reflect.Int32: |
| f.L1.Kind = protoreflect.Sfixed32Kind |
| case reflect.Uint32: |
| f.L1.Kind = protoreflect.Fixed32Kind |
| case reflect.Float32: |
| f.L1.Kind = protoreflect.FloatKind |
| } |
| case s == "fixed64": |
| switch goType.Kind() { |
| case reflect.Int64: |
| f.L1.Kind = protoreflect.Sfixed64Kind |
| case reflect.Uint64: |
| f.L1.Kind = protoreflect.Fixed64Kind |
| case reflect.Float64: |
| f.L1.Kind = protoreflect.DoubleKind |
| } |
| case s == "bytes": |
| switch { |
| case goType.Kind() == reflect.String: |
| f.L1.Kind = protoreflect.StringKind |
| case goType.Kind() == reflect.Slice && goType.Elem() == byteType: |
| f.L1.Kind = protoreflect.BytesKind |
| default: |
| f.L1.Kind = protoreflect.MessageKind |
| } |
| case s == "group": |
| f.L1.Kind = protoreflect.GroupKind |
| case strings.HasPrefix(s, "enum="): |
| f.L1.Kind = protoreflect.EnumKind |
| case strings.HasPrefix(s, "json="): |
| jsonName := s[len("json="):] |
| if jsonName != strs.JSONCamelCase(string(f.L0.FullName.Name())) { |
| f.L1.StringName.InitJSON(jsonName) |
| } |
| case s == "packed": |
| f.L1.EditionFeatures.IsPacked = true |
| case strings.HasPrefix(s, "def="): |
| // The default tag is special in that everything afterwards is the |
| // default regardless of the presence of commas. |
| s, i = tag[len("def="):], len(tag) |
| v, ev, _ := defval.Unmarshal(s, f.L1.Kind, evs, defval.GoTag) |
| f.L1.Default = filedesc.DefaultValue(v, ev) |
| case s == "proto3": |
| f.L0.ParentFile = filedesc.SurrogateProto3 |
| } |
| tag = strings.TrimPrefix(tag[i:], ",") |
| } |
| |
| // The generator uses the group message name instead of the field name. |
| // We obtain the real field name by lowercasing the group name. |
| if f.L1.Kind == protoreflect.GroupKind { |
| f.L0.FullName = protoreflect.FullName(strings.ToLower(string(f.L0.FullName))) |
| } |
| return f |
| } |
| |
| // Marshal encodes the protoreflect.FieldDescriptor as a tag. |
| // |
| // The enumName must be provided if the kind is an enum. |
| // Historically, the formulation of the enum "name" was the proto package |
| // dot-concatenated with the generated Go identifier for the enum type. |
| // Depending on the context on how Marshal is called, there are different ways |
| // through which that information is determined. As such it is the caller's |
| // responsibility to provide a function to obtain that information. |
| func Marshal(fd protoreflect.FieldDescriptor, enumName string) string { |
| var tag []string |
| switch fd.Kind() { |
| case protoreflect.BoolKind, protoreflect.EnumKind, protoreflect.Int32Kind, protoreflect.Uint32Kind, protoreflect.Int64Kind, protoreflect.Uint64Kind: |
| tag = append(tag, "varint") |
| case protoreflect.Sint32Kind: |
| tag = append(tag, "zigzag32") |
| case protoreflect.Sint64Kind: |
| tag = append(tag, "zigzag64") |
| case protoreflect.Sfixed32Kind, protoreflect.Fixed32Kind, protoreflect.FloatKind: |
| tag = append(tag, "fixed32") |
| case protoreflect.Sfixed64Kind, protoreflect.Fixed64Kind, protoreflect.DoubleKind: |
| tag = append(tag, "fixed64") |
| case protoreflect.StringKind, protoreflect.BytesKind, protoreflect.MessageKind: |
| tag = append(tag, "bytes") |
| case protoreflect.GroupKind: |
| tag = append(tag, "group") |
| } |
| tag = append(tag, strconv.Itoa(int(fd.Number()))) |
| switch fd.Cardinality() { |
| case protoreflect.Optional: |
| tag = append(tag, "opt") |
| case protoreflect.Required: |
| tag = append(tag, "req") |
| case protoreflect.Repeated: |
| tag = append(tag, "rep") |
| } |
| if fd.IsPacked() { |
| tag = append(tag, "packed") |
| } |
| name := string(fd.Name()) |
| if fd.Kind() == protoreflect.GroupKind { |
| // The name of the FieldDescriptor for a group field is |
| // lowercased. To find the original capitalization, we |
| // look in the field's MessageType. |
| name = string(fd.Message().Name()) |
| } |
| tag = append(tag, "name="+name) |
| if jsonName := fd.JSONName(); jsonName != "" && jsonName != name && !fd.IsExtension() { |
| // NOTE: The jsonName != name condition is suspect, but it preserve |
| // the exact same semantics from the previous generator. |
| tag = append(tag, "json="+jsonName) |
| } |
| // The previous implementation does not tag extension fields as proto3, |
| // even when the field is defined in a proto3 file. Match that behavior |
| // for consistency. |
| if fd.Syntax() == protoreflect.Proto3 && !fd.IsExtension() { |
| tag = append(tag, "proto3") |
| } |
| if fd.Kind() == protoreflect.EnumKind && enumName != "" { |
| tag = append(tag, "enum="+enumName) |
| } |
| if fd.ContainingOneof() != nil { |
| tag = append(tag, "oneof") |
| } |
| // This must appear last in the tag, since commas in strings aren't escaped. |
| if fd.HasDefault() { |
| def, _ := defval.Marshal(fd.Default(), fd.DefaultEnumValue(), fd.Kind(), defval.GoTag) |
| tag = append(tag, "def="+def) |
| } |
| return strings.Join(tag, ",") |
| } |