David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 1 | // Copyright 2018 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. |
| 4 | |
| 5 | // Package tag marshals and unmarshals the legacy struct tags as generated |
| 6 | // by historical versions of protoc-gen-go. |
| 7 | package tag |
| 8 | |
| 9 | import ( |
| 10 | "reflect" |
| 11 | "strconv" |
| 12 | "strings" |
| 13 | |
| 14 | defval "google.golang.org/protobuf/internal/encoding/defval" |
| 15 | fdesc "google.golang.org/protobuf/internal/filedesc" |
| 16 | "google.golang.org/protobuf/internal/strs" |
| 17 | pref "google.golang.org/protobuf/reflect/protoreflect" |
| 18 | ) |
| 19 | |
| 20 | var byteType = reflect.TypeOf(byte(0)) |
| 21 | |
| 22 | // Unmarshal decodes the tag into a prototype.Field. |
| 23 | // |
| 24 | // The goType is needed to determine the original protoreflect.Kind since the |
| 25 | // tag does not record sufficient information to determine that. |
| 26 | // The type is the underlying field type (e.g., a repeated field may be |
| 27 | // represented by []T, but the Go type passed in is just T). |
| 28 | // A list of enum value descriptors must be provided for enum fields. |
| 29 | // This does not populate the Enum or Message (except for weak message). |
| 30 | // |
| 31 | // This function is a best effort attempt; parsing errors are ignored. |
| 32 | func Unmarshal(tag string, goType reflect.Type, evs pref.EnumValueDescriptors) pref.FieldDescriptor { |
| 33 | f := new(fdesc.Field) |
| 34 | f.L0.ParentFile = fdesc.SurrogateProto2 |
| 35 | for len(tag) > 0 { |
| 36 | i := strings.IndexByte(tag, ',') |
| 37 | if i < 0 { |
| 38 | i = len(tag) |
| 39 | } |
| 40 | switch s := tag[:i]; { |
| 41 | case strings.HasPrefix(s, "name="): |
| 42 | f.L0.FullName = pref.FullName(s[len("name="):]) |
| 43 | case strings.Trim(s, "0123456789") == "": |
| 44 | n, _ := strconv.ParseUint(s, 10, 32) |
| 45 | f.L1.Number = pref.FieldNumber(n) |
| 46 | case s == "opt": |
| 47 | f.L1.Cardinality = pref.Optional |
| 48 | case s == "req": |
| 49 | f.L1.Cardinality = pref.Required |
| 50 | case s == "rep": |
| 51 | f.L1.Cardinality = pref.Repeated |
| 52 | case s == "varint": |
| 53 | switch goType.Kind() { |
| 54 | case reflect.Bool: |
| 55 | f.L1.Kind = pref.BoolKind |
| 56 | case reflect.Int32: |
| 57 | f.L1.Kind = pref.Int32Kind |
| 58 | case reflect.Int64: |
| 59 | f.L1.Kind = pref.Int64Kind |
| 60 | case reflect.Uint32: |
| 61 | f.L1.Kind = pref.Uint32Kind |
| 62 | case reflect.Uint64: |
| 63 | f.L1.Kind = pref.Uint64Kind |
| 64 | } |
| 65 | case s == "zigzag32": |
| 66 | if goType.Kind() == reflect.Int32 { |
| 67 | f.L1.Kind = pref.Sint32Kind |
| 68 | } |
| 69 | case s == "zigzag64": |
| 70 | if goType.Kind() == reflect.Int64 { |
| 71 | f.L1.Kind = pref.Sint64Kind |
| 72 | } |
| 73 | case s == "fixed32": |
| 74 | switch goType.Kind() { |
| 75 | case reflect.Int32: |
| 76 | f.L1.Kind = pref.Sfixed32Kind |
| 77 | case reflect.Uint32: |
| 78 | f.L1.Kind = pref.Fixed32Kind |
| 79 | case reflect.Float32: |
| 80 | f.L1.Kind = pref.FloatKind |
| 81 | } |
| 82 | case s == "fixed64": |
| 83 | switch goType.Kind() { |
| 84 | case reflect.Int64: |
| 85 | f.L1.Kind = pref.Sfixed64Kind |
| 86 | case reflect.Uint64: |
| 87 | f.L1.Kind = pref.Fixed64Kind |
| 88 | case reflect.Float64: |
| 89 | f.L1.Kind = pref.DoubleKind |
| 90 | } |
| 91 | case s == "bytes": |
| 92 | switch { |
| 93 | case goType.Kind() == reflect.String: |
| 94 | f.L1.Kind = pref.StringKind |
| 95 | case goType.Kind() == reflect.Slice && goType.Elem() == byteType: |
| 96 | f.L1.Kind = pref.BytesKind |
| 97 | default: |
| 98 | f.L1.Kind = pref.MessageKind |
| 99 | } |
| 100 | case s == "group": |
| 101 | f.L1.Kind = pref.GroupKind |
| 102 | case strings.HasPrefix(s, "enum="): |
| 103 | f.L1.Kind = pref.EnumKind |
| 104 | case strings.HasPrefix(s, "json="): |
| 105 | jsonName := s[len("json="):] |
| 106 | if jsonName != strs.JSONCamelCase(string(f.L0.FullName.Name())) { |
| 107 | f.L1.StringName.InitJSON(jsonName) |
| 108 | } |
| 109 | case s == "packed": |
| 110 | f.L1.HasPacked = true |
| 111 | f.L1.IsPacked = true |
| 112 | case strings.HasPrefix(s, "weak="): |
| 113 | f.L1.IsWeak = true |
| 114 | f.L1.Message = fdesc.PlaceholderMessage(pref.FullName(s[len("weak="):])) |
| 115 | case strings.HasPrefix(s, "def="): |
| 116 | // The default tag is special in that everything afterwards is the |
| 117 | // default regardless of the presence of commas. |
| 118 | s, i = tag[len("def="):], len(tag) |
| 119 | v, ev, _ := defval.Unmarshal(s, f.L1.Kind, evs, defval.GoTag) |
| 120 | f.L1.Default = fdesc.DefaultValue(v, ev) |
| 121 | case s == "proto3": |
| 122 | f.L0.ParentFile = fdesc.SurrogateProto3 |
| 123 | } |
| 124 | tag = strings.TrimPrefix(tag[i:], ",") |
| 125 | } |
| 126 | |
| 127 | // The generator uses the group message name instead of the field name. |
| 128 | // We obtain the real field name by lowercasing the group name. |
| 129 | if f.L1.Kind == pref.GroupKind { |
| 130 | f.L0.FullName = pref.FullName(strings.ToLower(string(f.L0.FullName))) |
| 131 | } |
| 132 | return f |
| 133 | } |
| 134 | |
| 135 | // Marshal encodes the protoreflect.FieldDescriptor as a tag. |
| 136 | // |
| 137 | // The enumName must be provided if the kind is an enum. |
| 138 | // Historically, the formulation of the enum "name" was the proto package |
| 139 | // dot-concatenated with the generated Go identifier for the enum type. |
| 140 | // Depending on the context on how Marshal is called, there are different ways |
| 141 | // through which that information is determined. As such it is the caller's |
| 142 | // responsibility to provide a function to obtain that information. |
| 143 | func Marshal(fd pref.FieldDescriptor, enumName string) string { |
| 144 | var tag []string |
| 145 | switch fd.Kind() { |
| 146 | case pref.BoolKind, pref.EnumKind, pref.Int32Kind, pref.Uint32Kind, pref.Int64Kind, pref.Uint64Kind: |
| 147 | tag = append(tag, "varint") |
| 148 | case pref.Sint32Kind: |
| 149 | tag = append(tag, "zigzag32") |
| 150 | case pref.Sint64Kind: |
| 151 | tag = append(tag, "zigzag64") |
| 152 | case pref.Sfixed32Kind, pref.Fixed32Kind, pref.FloatKind: |
| 153 | tag = append(tag, "fixed32") |
| 154 | case pref.Sfixed64Kind, pref.Fixed64Kind, pref.DoubleKind: |
| 155 | tag = append(tag, "fixed64") |
| 156 | case pref.StringKind, pref.BytesKind, pref.MessageKind: |
| 157 | tag = append(tag, "bytes") |
| 158 | case pref.GroupKind: |
| 159 | tag = append(tag, "group") |
| 160 | } |
| 161 | tag = append(tag, strconv.Itoa(int(fd.Number()))) |
| 162 | switch fd.Cardinality() { |
| 163 | case pref.Optional: |
| 164 | tag = append(tag, "opt") |
| 165 | case pref.Required: |
| 166 | tag = append(tag, "req") |
| 167 | case pref.Repeated: |
| 168 | tag = append(tag, "rep") |
| 169 | } |
| 170 | if fd.IsPacked() { |
| 171 | tag = append(tag, "packed") |
| 172 | } |
| 173 | name := string(fd.Name()) |
| 174 | if fd.Kind() == pref.GroupKind { |
| 175 | // The name of the FieldDescriptor for a group field is |
| 176 | // lowercased. To find the original capitalization, we |
| 177 | // look in the field's MessageType. |
| 178 | name = string(fd.Message().Name()) |
| 179 | } |
| 180 | tag = append(tag, "name="+name) |
| 181 | if jsonName := fd.JSONName(); jsonName != "" && jsonName != name && !fd.IsExtension() { |
| 182 | // NOTE: The jsonName != name condition is suspect, but it preserve |
| 183 | // the exact same semantics from the previous generator. |
| 184 | tag = append(tag, "json="+jsonName) |
| 185 | } |
| 186 | if fd.IsWeak() { |
| 187 | tag = append(tag, "weak="+string(fd.Message().FullName())) |
| 188 | } |
| 189 | // The previous implementation does not tag extension fields as proto3, |
| 190 | // even when the field is defined in a proto3 file. Match that behavior |
| 191 | // for consistency. |
| 192 | if fd.Syntax() == pref.Proto3 && !fd.IsExtension() { |
| 193 | tag = append(tag, "proto3") |
| 194 | } |
| 195 | if fd.Kind() == pref.EnumKind && enumName != "" { |
| 196 | tag = append(tag, "enum="+enumName) |
| 197 | } |
| 198 | if fd.ContainingOneof() != nil { |
| 199 | tag = append(tag, "oneof") |
| 200 | } |
| 201 | // This must appear last in the tag, since commas in strings aren't escaped. |
| 202 | if fd.HasDefault() { |
| 203 | def, _ := defval.Marshal(fd.Default(), fd.DefaultEnumValue(), fd.Kind(), defval.GoTag) |
| 204 | tag = append(tag, "def="+def) |
| 205 | } |
| 206 | return strings.Join(tag, ",") |
| 207 | } |