blob: 38f1931c6fd1a9975ff084e3fda1dbdff010556b [file] [log] [blame]
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +00001// 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.
7package tag
8
9import (
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
20var 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.
32func 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.
143func 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}