1// Protocol Buffers for Go with Gadgets
2//
3// Copyright (c) 2013, The GoGo Authors. All rights reserved.
4// http://github.com/gogo/protobuf
5//
6// Redistribution and use in source and binary forms, with or without
7// modification, are permitted provided that the following conditions are
8// met:
9//
10//     * Redistributions of source code must retain the above copyright
11// notice, this list of conditions and the following disclaimer.
12//     * Redistributions in binary form must reproduce the above
13// copyright notice, this list of conditions and the following disclaimer
14// in the documentation and/or other materials provided with the
15// distribution.
16//
17// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29/*
30The gostring plugin generates a GoString method for each message.
31The GoString method is called whenever you use a fmt.Printf as such:
32
33  fmt.Printf("%#v", mymessage)
34
35or whenever you actually call GoString()
36The output produced by the GoString method can be copied from the output into code and used to set a variable.
37It is totally valid Go Code and is populated exactly as the struct that was printed out.
38
39It is enabled by the following extensions:
40
41  - gostring
42  - gostring_all
43
44The gostring plugin also generates a test given it is enabled using one of the following extensions:
45
46  - testgen
47  - testgen_all
48
49Let us look at:
50
51  github.com/gogo/protobuf/test/example/example.proto
52
53Btw all the output can be seen at:
54
55  github.com/gogo/protobuf/test/example/*
56
57The following message:
58
59  option (gogoproto.gostring_all) = true;
60
61  message A {
62	optional string Description = 1 [(gogoproto.nullable) = false];
63	optional int64 Number = 2 [(gogoproto.nullable) = false];
64	optional bytes Id = 3 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uuid", (gogoproto.nullable) = false];
65  }
66
67given to the gostring plugin, will generate the following code:
68
69  func (this *A) GoString() string {
70	if this == nil {
71		return "nil"
72	}
73	s := strings1.Join([]string{`&test.A{` + `Description:` + fmt1.Sprintf("%#v", this.Description), `Number:` + fmt1.Sprintf("%#v", this.Number), `Id:` + fmt1.Sprintf("%#v", this.Id), `XXX_unrecognized:` + fmt1.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ")
74	return s
75  }
76
77and the following test code:
78
79	func TestAGoString(t *testing6.T) {
80		popr := math_rand6.New(math_rand6.NewSource(time6.Now().UnixNano()))
81		p := NewPopulatedA(popr, false)
82		s1 := p.GoString()
83		s2 := fmt2.Sprintf("%#v", p)
84		if s1 != s2 {
85			t.Fatalf("GoString want %v got %v", s1, s2)
86		}
87		_, err := go_parser.ParseExpr(s1)
88		if err != nil {
89			panic(err)
90		}
91	}
92
93Typically fmt.Printf("%#v") will stop to print when it reaches a pointer and
94not print their values, while the generated GoString method will always print all values, recursively.
95
96*/
97package gostring
98
99import (
100	"fmt"
101	"os"
102	"strconv"
103	"strings"
104
105	"github.com/gogo/protobuf/gogoproto"
106	"github.com/gogo/protobuf/protoc-gen-gogo/generator"
107)
108
109type gostring struct {
110	*generator.Generator
111	generator.PluginImports
112	atleastOne bool
113	localName  string
114	overwrite  bool
115}
116
117func NewGoString() *gostring {
118	return &gostring{}
119}
120
121func (p *gostring) Name() string {
122	return "gostring"
123}
124
125func (p *gostring) Overwrite() {
126	p.overwrite = true
127}
128
129func (p *gostring) Init(g *generator.Generator) {
130	p.Generator = g
131}
132
133func (p *gostring) Generate(file *generator.FileDescriptor) {
134	proto3 := gogoproto.IsProto3(file.FileDescriptorProto)
135	p.PluginImports = generator.NewPluginImports(p.Generator)
136	p.atleastOne = false
137
138	p.localName = generator.FileName(file)
139
140	fmtPkg := p.NewImport("fmt")
141	stringsPkg := p.NewImport("strings")
142	protoPkg := p.NewImport("github.com/gogo/protobuf/proto")
143	if !gogoproto.ImportsGoGoProto(file.FileDescriptorProto) {
144		protoPkg = p.NewImport("github.com/golang/protobuf/proto")
145	}
146	sortPkg := p.NewImport("sort")
147	strconvPkg := p.NewImport("strconv")
148	reflectPkg := p.NewImport("reflect")
149	sortKeysPkg := p.NewImport("github.com/gogo/protobuf/sortkeys")
150
151	extensionToGoStringUsed := false
152	for _, message := range file.Messages() {
153		if !p.overwrite && !gogoproto.HasGoString(file.FileDescriptorProto, message.DescriptorProto) {
154			continue
155		}
156		if message.DescriptorProto.GetOptions().GetMapEntry() {
157			continue
158		}
159		p.atleastOne = true
160		packageName := file.GoPackageName()
161
162		ccTypeName := generator.CamelCaseSlice(message.TypeName())
163		p.P(`func (this *`, ccTypeName, `) GoString() string {`)
164		p.In()
165		p.P(`if this == nil {`)
166		p.In()
167		p.P(`return "nil"`)
168		p.Out()
169		p.P(`}`)
170
171		p.P(`s := make([]string, 0, `, strconv.Itoa(len(message.Field)+4), `)`)
172		p.P(`s = append(s, "&`, packageName, ".", ccTypeName, `{")`)
173
174		oneofs := make(map[string]struct{})
175		for _, field := range message.Field {
176			nullable := gogoproto.IsNullable(field)
177			repeated := field.IsRepeated()
178			fieldname := p.GetFieldName(message, field)
179			oneof := field.OneofIndex != nil
180			if oneof {
181				if _, ok := oneofs[fieldname]; ok {
182					continue
183				} else {
184					oneofs[fieldname] = struct{}{}
185				}
186				p.P(`if this.`, fieldname, ` != nil {`)
187				p.In()
188				p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
189				p.Out()
190				p.P(`}`)
191			} else if p.IsMap(field) {
192				m := p.GoMapType(nil, field)
193				mapgoTyp, keyField, keyAliasField := m.GoType, m.KeyField, m.KeyAliasField
194				keysName := `keysFor` + fieldname
195				keygoTyp, _ := p.GoType(nil, keyField)
196				keygoTyp = strings.Replace(keygoTyp, "*", "", 1)
197				keygoAliasTyp, _ := p.GoType(nil, keyAliasField)
198				keygoAliasTyp = strings.Replace(keygoAliasTyp, "*", "", 1)
199				keyCapTyp := generator.CamelCase(keygoTyp)
200				p.P(keysName, ` := make([]`, keygoTyp, `, 0, len(this.`, fieldname, `))`)
201				p.P(`for k, _ := range this.`, fieldname, ` {`)
202				p.In()
203				if keygoAliasTyp == keygoTyp {
204					p.P(keysName, ` = append(`, keysName, `, k)`)
205				} else {
206					p.P(keysName, ` = append(`, keysName, `, `, keygoTyp, `(k))`)
207				}
208				p.Out()
209				p.P(`}`)
210				p.P(sortKeysPkg.Use(), `.`, keyCapTyp, `s(`, keysName, `)`)
211				mapName := `mapStringFor` + fieldname
212				p.P(mapName, ` := "`, mapgoTyp, `{"`)
213				p.P(`for _, k := range `, keysName, ` {`)
214				p.In()
215				if keygoAliasTyp == keygoTyp {
216					p.P(mapName, ` += fmt.Sprintf("%#v: %#v,", k, this.`, fieldname, `[k])`)
217				} else {
218					p.P(mapName, ` += fmt.Sprintf("%#v: %#v,", k, this.`, fieldname, `[`, keygoAliasTyp, `(k)])`)
219				}
220				p.Out()
221				p.P(`}`)
222				p.P(mapName, ` += "}"`)
223				p.P(`if this.`, fieldname, ` != nil {`)
224				p.In()
225				p.P(`s = append(s, "`, fieldname, `: " + `, mapName, `+ ",\n")`)
226				p.Out()
227				p.P(`}`)
228			} else if (field.IsMessage() && !gogoproto.IsCustomType(field) && !gogoproto.IsStdType(field)) || p.IsGroup(field) {
229				if nullable || repeated {
230					p.P(`if this.`, fieldname, ` != nil {`)
231					p.In()
232				}
233				if nullable {
234					p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
235				} else if repeated {
236					if nullable {
237						p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
238					} else {
239						goTyp, _ := p.GoType(message, field)
240						goTyp = strings.Replace(goTyp, "[]", "", 1)
241						p.P("vs := make([]", goTyp, ", len(this.", fieldname, "))")
242						p.P("for i := range vs {")
243						p.In()
244						p.P("vs[i] = this.", fieldname, "[i]")
245						p.Out()
246						p.P("}")
247						p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", vs) + ",\n")`)
248					}
249				} else {
250					p.P(`s = append(s, "`, fieldname, `: " + `, stringsPkg.Use(), `.Replace(this.`, fieldname, `.GoString()`, ",`&`,``,1)", ` + ",\n")`)
251				}
252				if nullable || repeated {
253					p.Out()
254					p.P(`}`)
255				}
256			} else {
257				if !proto3 && (nullable || repeated) {
258					p.P(`if this.`, fieldname, ` != nil {`)
259					p.In()
260				}
261				if field.IsEnum() {
262					if nullable && !repeated && !proto3 {
263						goTyp, _ := p.GoType(message, field)
264						p.P(`s = append(s, "`, fieldname, `: " + valueToGoString`, p.localName, `(this.`, fieldname, `,"`, generator.GoTypeToName(goTyp), `"`, `) + ",\n")`)
265					} else {
266						p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
267					}
268				} else {
269					if nullable && !repeated && !proto3 {
270						goTyp, _ := p.GoType(message, field)
271						p.P(`s = append(s, "`, fieldname, `: " + valueToGoString`, p.localName, `(this.`, fieldname, `,"`, generator.GoTypeToName(goTyp), `"`, `) + ",\n")`)
272					} else {
273						p.P(`s = append(s, "`, fieldname, `: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `) + ",\n")`)
274					}
275				}
276				if !proto3 && (nullable || repeated) {
277					p.Out()
278					p.P(`}`)
279				}
280			}
281		}
282		if message.DescriptorProto.HasExtension() {
283			if gogoproto.HasExtensionsMap(file.FileDescriptorProto, message.DescriptorProto) {
284				p.P(`s = append(s, "XXX_InternalExtensions: " + extensionToGoString`, p.localName, `(this) + ",\n")`)
285				extensionToGoStringUsed = true
286			} else {
287				p.P(`if this.XXX_extensions != nil {`)
288				p.In()
289				p.P(`s = append(s, "XXX_extensions: " + `, fmtPkg.Use(), `.Sprintf("%#v", this.XXX_extensions) + ",\n")`)
290				p.Out()
291				p.P(`}`)
292			}
293		}
294		if gogoproto.HasUnrecognized(file.FileDescriptorProto, message.DescriptorProto) {
295			p.P(`if this.XXX_unrecognized != nil {`)
296			p.In()
297			p.P(`s = append(s, "XXX_unrecognized:" + `, fmtPkg.Use(), `.Sprintf("%#v", this.XXX_unrecognized) + ",\n")`)
298			p.Out()
299			p.P(`}`)
300		}
301
302		p.P(`s = append(s, "}")`)
303		p.P(`return `, stringsPkg.Use(), `.Join(s, "")`)
304		p.Out()
305		p.P(`}`)
306
307		//Generate GoString methods for oneof fields
308		for _, field := range message.Field {
309			oneof := field.OneofIndex != nil
310			if !oneof {
311				continue
312			}
313			ccTypeName := p.OneOfTypeName(message, field)
314			p.P(`func (this *`, ccTypeName, `) GoString() string {`)
315			p.In()
316			p.P(`if this == nil {`)
317			p.In()
318			p.P(`return "nil"`)
319			p.Out()
320			p.P(`}`)
321			fieldname := p.GetOneOfFieldName(message, field)
322			outStr := strings.Join([]string{
323				"s := ",
324				stringsPkg.Use(), ".Join([]string{`&", packageName, ".", ccTypeName, "{` + \n",
325				"`", fieldname, ":` + ", fmtPkg.Use(), `.Sprintf("%#v", this.`, fieldname, `)`,
326				" + `}`",
327				`}`,
328				`,", "`,
329				`)`}, "")
330			p.P(outStr)
331			p.P(`return s`)
332			p.Out()
333			p.P(`}`)
334		}
335	}
336
337	if !p.atleastOne {
338		return
339	}
340
341	p.P(`func valueToGoString`, p.localName, `(v interface{}, typ string) string {`)
342	p.In()
343	p.P(`rv := `, reflectPkg.Use(), `.ValueOf(v)`)
344	p.P(`if rv.IsNil() {`)
345	p.In()
346	p.P(`return "nil"`)
347	p.Out()
348	p.P(`}`)
349	p.P(`pv := `, reflectPkg.Use(), `.Indirect(rv).Interface()`)
350	p.P(`return `, fmtPkg.Use(), `.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)`)
351	p.Out()
352	p.P(`}`)
353
354	if extensionToGoStringUsed {
355		if !gogoproto.ImportsGoGoProto(file.FileDescriptorProto) {
356			fmt.Fprintf(os.Stderr, "The GoString plugin for messages with extensions requires importing gogoprotobuf. Please see file %s", file.GetName())
357			os.Exit(1)
358		}
359		p.P(`func extensionToGoString`, p.localName, `(m `, protoPkg.Use(), `.Message) string {`)
360		p.In()
361		p.P(`e := `, protoPkg.Use(), `.GetUnsafeExtensionsMap(m)`)
362		p.P(`if e == nil { return "nil" }`)
363		p.P(`s := "proto.NewUnsafeXXX_InternalExtensions(map[int32]proto.Extension{"`)
364		p.P(`keys := make([]int, 0, len(e))`)
365		p.P(`for k := range e {`)
366		p.In()
367		p.P(`keys = append(keys, int(k))`)
368		p.Out()
369		p.P(`}`)
370		p.P(sortPkg.Use(), `.Ints(keys)`)
371		p.P(`ss := []string{}`)
372		p.P(`for _, k := range keys {`)
373		p.In()
374		p.P(`ss = append(ss, `, strconvPkg.Use(), `.Itoa(k) + ": " + e[int32(k)].GoString())`)
375		p.Out()
376		p.P(`}`)
377		p.P(`s+=`, stringsPkg.Use(), `.Join(ss, ",") + "})"`)
378		p.P(`return s`)
379		p.Out()
380		p.P(`}`)
381	}
382}
383
384func init() {
385	generator.RegisterPlugin(NewGoString())
386}
387