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