github.com/gogo/protobuf@v1.3.2/plugin/stringer/stringer.go (about) 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 /* 30 The stringer plugin generates a String method for each message. 31 32 It is enabled by the following extensions: 33 34 - stringer 35 - stringer_all 36 37 The stringer plugin also generates a test given it is enabled using one of the following extensions: 38 39 - testgen 40 - testgen_all 41 42 Let us look at: 43 44 github.com/gogo/protobuf/test/example/example.proto 45 46 Btw all the output can be seen at: 47 48 github.com/gogo/protobuf/test/example/* 49 50 The following message: 51 52 option (gogoproto.goproto_stringer_all) = false; 53 option (gogoproto.stringer_all) = true; 54 55 message A { 56 optional string Description = 1 [(gogoproto.nullable) = false]; 57 optional int64 Number = 2 [(gogoproto.nullable) = false]; 58 optional bytes Id = 3 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uuid", (gogoproto.nullable) = false]; 59 } 60 61 given to the stringer stringer, will generate the following code: 62 63 func (this *A) String() string { 64 if this == nil { 65 return "nil" 66 } 67 s := strings.Join([]string{`&A{`, 68 `Description:` + fmt.Sprintf("%v", this.Description) + `,`, 69 `Number:` + fmt.Sprintf("%v", this.Number) + `,`, 70 `Id:` + fmt.Sprintf("%v", this.Id) + `,`, 71 `XXX_unrecognized:` + fmt.Sprintf("%v", this.XXX_unrecognized) + `,`, 72 `}`, 73 }, "") 74 return s 75 } 76 77 and the following test code: 78 79 func TestAStringer(t *testing4.T) { 80 popr := math_rand4.New(math_rand4.NewSource(time4.Now().UnixNano())) 81 p := NewPopulatedA(popr, false) 82 s1 := p.String() 83 s2 := fmt1.Sprintf("%v", p) 84 if s1 != s2 { 85 t.Fatalf("String want %v got %v", s1, s2) 86 } 87 } 88 89 Typically fmt.Printf("%v") will stop to print when it reaches a pointer and 90 not print their values, while the generated String method will always print all values, recursively. 91 92 */ 93 package stringer 94 95 import ( 96 "github.com/gogo/protobuf/gogoproto" 97 "github.com/gogo/protobuf/protoc-gen-gogo/generator" 98 "strings" 99 ) 100 101 type stringer struct { 102 *generator.Generator 103 generator.PluginImports 104 atleastOne bool 105 localName string 106 } 107 108 func NewStringer() *stringer { 109 return &stringer{} 110 } 111 112 func (p *stringer) Name() string { 113 return "stringer" 114 } 115 116 func (p *stringer) Init(g *generator.Generator) { 117 p.Generator = g 118 } 119 120 func (p *stringer) Generate(file *generator.FileDescriptor) { 121 proto3 := gogoproto.IsProto3(file.FileDescriptorProto) 122 p.PluginImports = generator.NewPluginImports(p.Generator) 123 p.atleastOne = false 124 125 p.localName = generator.FileName(file) 126 127 fmtPkg := p.NewImport("fmt") 128 stringsPkg := p.NewImport("strings") 129 reflectPkg := p.NewImport("reflect") 130 sortKeysPkg := p.NewImport("github.com/gogo/protobuf/sortkeys") 131 protoPkg := p.NewImport("github.com/gogo/protobuf/proto") 132 for _, message := range file.Messages() { 133 if !gogoproto.IsStringer(file.FileDescriptorProto, message.DescriptorProto) { 134 continue 135 } 136 if gogoproto.EnabledGoStringer(file.FileDescriptorProto, message.DescriptorProto) { 137 panic("old string method needs to be disabled, please use gogoproto.goproto_stringer or gogoproto.goproto_stringer_all and set it to false") 138 } 139 if message.DescriptorProto.GetOptions().GetMapEntry() { 140 continue 141 } 142 p.atleastOne = true 143 ccTypeName := generator.CamelCaseSlice(message.TypeName()) 144 p.P(`func (this *`, ccTypeName, `) String() string {`) 145 p.In() 146 p.P(`if this == nil {`) 147 p.In() 148 p.P(`return "nil"`) 149 p.Out() 150 p.P(`}`) 151 for _, field := range message.Field { 152 if p.IsMap(field) || !field.IsRepeated() { 153 continue 154 } 155 if (field.IsMessage() && !gogoproto.IsCustomType(field)) || p.IsGroup(field) { 156 nullable := gogoproto.IsNullable(field) 157 desc := p.ObjectNamed(field.GetTypeName()) 158 msgname := p.TypeName(desc) 159 msgnames := strings.Split(msgname, ".") 160 typeName := msgnames[len(msgnames)-1] 161 fieldMessageDesc := file.GetMessage(msgname) 162 gogoStringer := false 163 if fieldMessageDesc != nil { 164 gogoStringer = gogoproto.IsStringer(file.FileDescriptorProto, fieldMessageDesc) 165 } 166 fieldname := p.GetFieldName(message, field) 167 stringfunc := fmtPkg.Use() + `.Sprintf("%v", f)` 168 if gogoStringer { 169 stringfunc = `f.String()` 170 } 171 repeatedName := `repeatedStringFor` + fieldname 172 if nullable { 173 p.P(repeatedName, ` := "[]*`, typeName, `{"`) 174 } else { 175 p.P(repeatedName, ` := "[]`, typeName, `{"`) 176 } 177 178 p.P(`for _, f := range `, `this.`, fieldname, ` {`) 179 p.In() 180 if nullable { 181 p.P(repeatedName, " += ", stringsPkg.Use(), `.Replace(`, stringfunc, `, "`, typeName, `","`, msgname, `"`, ", 1)", ` + ","`) 182 } else if gogoStringer { 183 p.P(repeatedName, " += ", stringsPkg.Use(), `.Replace(`, stringsPkg.Use(), `.Replace(`, stringfunc, `, "`, typeName, `","`, msgname, `"`, ", 1),`&`,``,1)", ` + ","`) 184 } else { 185 p.P(repeatedName, " += ", stringfunc, ` + ","`) 186 } 187 p.Out() 188 p.P(`}`) 189 p.P(repeatedName, ` += "}"`) 190 } 191 } 192 for _, field := range message.Field { 193 if !p.IsMap(field) { 194 continue 195 } 196 fieldname := p.GetFieldName(message, field) 197 198 m := p.GoMapType(nil, field) 199 mapgoTyp, keyField, keyAliasField := m.GoType, m.KeyField, m.KeyAliasField 200 keysName := `keysFor` + fieldname 201 keygoTyp, _ := p.GoType(nil, keyField) 202 keygoTyp = strings.Replace(keygoTyp, "*", "", 1) 203 keygoAliasTyp, _ := p.GoType(nil, keyAliasField) 204 keygoAliasTyp = strings.Replace(keygoAliasTyp, "*", "", 1) 205 keyCapTyp := generator.CamelCase(keygoTyp) 206 p.P(keysName, ` := make([]`, keygoTyp, `, 0, len(this.`, fieldname, `))`) 207 p.P(`for k, _ := range this.`, fieldname, ` {`) 208 p.In() 209 if keygoAliasTyp == keygoTyp { 210 p.P(keysName, ` = append(`, keysName, `, k)`) 211 } else { 212 p.P(keysName, ` = append(`, keysName, `, `, keygoTyp, `(k))`) 213 } 214 p.Out() 215 p.P(`}`) 216 p.P(sortKeysPkg.Use(), `.`, keyCapTyp, `s(`, keysName, `)`) 217 mapName := `mapStringFor` + fieldname 218 p.P(mapName, ` := "`, mapgoTyp, `{"`) 219 p.P(`for _, k := range `, keysName, ` {`) 220 p.In() 221 if keygoAliasTyp == keygoTyp { 222 p.P(mapName, ` += fmt.Sprintf("%v: %v,", k, this.`, fieldname, `[k])`) 223 } else { 224 p.P(mapName, ` += fmt.Sprintf("%v: %v,", k, this.`, fieldname, `[`, keygoAliasTyp, `(k)])`) 225 } 226 p.Out() 227 p.P(`}`) 228 p.P(mapName, ` += "}"`) 229 } 230 p.P("s := ", stringsPkg.Use(), ".Join([]string{`&", ccTypeName, "{`,") 231 oneofs := make(map[string]struct{}) 232 for _, field := range message.Field { 233 nullable := gogoproto.IsNullable(field) 234 repeated := field.IsRepeated() 235 fieldname := p.GetFieldName(message, field) 236 oneof := field.OneofIndex != nil 237 if oneof { 238 if _, ok := oneofs[fieldname]; ok { 239 continue 240 } else { 241 oneofs[fieldname] = struct{}{} 242 } 243 p.P("`", fieldname, ":`", ` + `, fmtPkg.Use(), `.Sprintf("%v", this.`, fieldname, ") + `,", "`,") 244 } else if p.IsMap(field) { 245 mapName := `mapStringFor` + fieldname 246 p.P("`", fieldname, ":`", ` + `, mapName, " + `,", "`,") 247 } else if (field.IsMessage() && !gogoproto.IsCustomType(field)) || p.IsGroup(field) { 248 desc := p.ObjectNamed(field.GetTypeName()) 249 msgname := p.TypeName(desc) 250 msgnames := strings.Split(msgname, ".") 251 typeName := msgnames[len(msgnames)-1] 252 fieldMessageDesc := file.GetMessage(msgname) 253 gogoStringer := false 254 if fieldMessageDesc != nil { 255 gogoStringer = gogoproto.IsStringer(file.FileDescriptorProto, fieldMessageDesc) 256 } 257 stringfunc := fmtPkg.Use() + `.Sprintf("%v", this.` + fieldname + `)` 258 if gogoStringer { 259 stringfunc = `this.` + fieldname + `.String()` 260 } 261 if nullable && !repeated { 262 p.P("`", fieldname, ":`", ` + `, stringsPkg.Use(), `.Replace(`, stringfunc, `, "`, typeName, `","`, msgname, `"`, ", 1) + `,", "`,") 263 } else if repeated { 264 repeatedName := `repeatedStringFor` + fieldname 265 p.P("`", fieldname, ":`", ` + `, repeatedName, " + `,", "`,") 266 } else { 267 p.P("`", fieldname, ":`", ` + `, stringsPkg.Use(), `.Replace(`, stringsPkg.Use(), `.Replace(`, stringfunc, `, "`, typeName, `","`, msgname, `"`, ", 1),`&`,``,1) + `,", "`,") 268 } 269 } else { 270 if nullable && !repeated && !proto3 { 271 p.P("`", fieldname, ":`", ` + valueToString`, p.localName, `(this.`, fieldname, ") + `,", "`,") 272 } else { 273 p.P("`", fieldname, ":`", ` + `, fmtPkg.Use(), `.Sprintf("%v", this.`, fieldname, ") + `,", "`,") 274 } 275 } 276 } 277 if message.DescriptorProto.HasExtension() { 278 if gogoproto.HasExtensionsMap(file.FileDescriptorProto, message.DescriptorProto) { 279 p.P("`XXX_InternalExtensions:` + ", protoPkg.Use(), ".StringFromInternalExtension(this) + `,`,") 280 } else { 281 p.P("`XXX_extensions:` + ", protoPkg.Use(), ".StringFromExtensionsBytes(this.XXX_extensions) + `,`,") 282 } 283 } 284 if gogoproto.HasUnrecognized(file.FileDescriptorProto, message.DescriptorProto) { 285 p.P("`XXX_unrecognized:` + ", fmtPkg.Use(), `.Sprintf("%v", this.XXX_unrecognized) + `, "`,`,") 286 } 287 p.P("`}`,") 288 p.P(`}`, `,""`, ")") 289 p.P(`return s`) 290 p.Out() 291 p.P(`}`) 292 293 //Generate String methods for oneof fields 294 for _, field := range message.Field { 295 oneof := field.OneofIndex != nil 296 if !oneof { 297 continue 298 } 299 ccTypeName := p.OneOfTypeName(message, field) 300 p.P(`func (this *`, ccTypeName, `) String() string {`) 301 p.In() 302 p.P(`if this == nil {`) 303 p.In() 304 p.P(`return "nil"`) 305 p.Out() 306 p.P(`}`) 307 p.P("s := ", stringsPkg.Use(), ".Join([]string{`&", ccTypeName, "{`,") 308 fieldname := p.GetOneOfFieldName(message, field) 309 if field.IsMessage() || p.IsGroup(field) { 310 desc := p.ObjectNamed(field.GetTypeName()) 311 msgname := p.TypeName(desc) 312 msgnames := strings.Split(msgname, ".") 313 typeName := msgnames[len(msgnames)-1] 314 p.P("`", fieldname, ":`", ` + `, stringsPkg.Use(), `.Replace(`, fmtPkg.Use(), `.Sprintf("%v", this.`, fieldname, `), "`, typeName, `","`, msgname, `"`, ", 1) + `,", "`,") 315 } else { 316 p.P("`", fieldname, ":`", ` + `, fmtPkg.Use(), `.Sprintf("%v", this.`, fieldname, ") + `,", "`,") 317 } 318 p.P("`}`,") 319 p.P(`}`, `,""`, ")") 320 p.P(`return s`) 321 p.Out() 322 p.P(`}`) 323 } 324 } 325 326 if !p.atleastOne { 327 return 328 } 329 330 p.P(`func valueToString`, p.localName, `(v interface{}) string {`) 331 p.In() 332 p.P(`rv := `, reflectPkg.Use(), `.ValueOf(v)`) 333 p.P(`if rv.IsNil() {`) 334 p.In() 335 p.P(`return "nil"`) 336 p.Out() 337 p.P(`}`) 338 p.P(`pv := `, reflectPkg.Use(), `.Indirect(rv).Interface()`) 339 p.P(`return `, fmtPkg.Use(), `.Sprintf("*%v", pv)`) 340 p.Out() 341 p.P(`}`) 342 343 } 344 345 func init() { 346 generator.RegisterPlugin(NewStringer()) 347 }