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 onlyone plugin generates code for the onlyone extension. 31 All fields must be nullable and only one of the fields may be set, like a union. 32 Two methods are generated 33 34 GetValue() interface{} 35 36 and 37 38 SetValue(v interface{}) (set bool) 39 40 These provide easier interaction with a onlyone. 41 42 The onlyone extension is not called union as this causes compile errors in the C++ generated code. 43 There can only be one ;) 44 45 It is enabled by the following extensions: 46 47 - onlyone 48 - onlyone_all 49 50 The onlyone plugin also generates a test given it is enabled using one of the following extensions: 51 52 - testgen 53 - testgen_all 54 55 Lets look at: 56 57 github.com/gogo/protobuf/test/example/example.proto 58 59 Btw all the output can be seen at: 60 61 github.com/gogo/protobuf/test/example/* 62 63 The following message: 64 65 message U { 66 option (gogoproto.onlyone) = true; 67 optional A A = 1; 68 optional B B = 2; 69 } 70 71 given to the onlyone plugin, will generate code which looks a lot like this: 72 73 func (this *U) GetValue() interface{} { 74 if this.A != nil { 75 return this.A 76 } 77 if this.B != nil { 78 return this.B 79 } 80 return nil 81 } 82 83 func (this *U) SetValue(value interface{}) bool { 84 switch vt := value.(type) { 85 case *A: 86 this.A = vt 87 case *B: 88 this.B = vt 89 default: 90 return false 91 } 92 return true 93 } 94 95 and the following test code: 96 97 func TestUUnion(t *testing.T) { 98 popr := math_rand.New(math_rand.NewSource(time.Now().UnixNano())) 99 p := NewPopulatedU(popr) 100 v := p.GetValue() 101 msg := &U{} 102 if !msg.SetValue(v) { 103 t.Fatalf("Union: Could not set Value") 104 } 105 if !p.Equal(msg) { 106 t.Fatalf("%#v !Union Equal %#v", msg, p) 107 } 108 } 109 110 */ 111 package union 112 113 import ( 114 "github.com/gogo/protobuf/gogoproto" 115 "github.com/gogo/protobuf/protoc-gen-gogo/generator" 116 ) 117 118 type union struct { 119 *generator.Generator 120 generator.PluginImports 121 } 122 123 func NewUnion() *union { 124 return &union{} 125 } 126 127 func (p *union) Name() string { 128 return "union" 129 } 130 131 func (p *union) Init(g *generator.Generator) { 132 p.Generator = g 133 } 134 135 func (p *union) Generate(file *generator.FileDescriptor) { 136 p.PluginImports = generator.NewPluginImports(p.Generator) 137 138 for _, message := range file.Messages() { 139 if !gogoproto.IsUnion(file.FileDescriptorProto, message.DescriptorProto) { 140 continue 141 } 142 if message.DescriptorProto.HasExtension() { 143 panic("onlyone does not currently support extensions") 144 } 145 if message.DescriptorProto.GetOptions().GetMapEntry() { 146 continue 147 } 148 149 ccTypeName := generator.CamelCaseSlice(message.TypeName()) 150 p.P(`func (this *`, ccTypeName, `) GetValue() interface{} {`) 151 p.In() 152 for _, field := range message.Field { 153 fieldname := p.GetFieldName(message, field) 154 if fieldname == "Value" { 155 panic("cannot have a onlyone message " + ccTypeName + " with a field named Value") 156 } 157 p.P(`if this.`, fieldname, ` != nil {`) 158 p.In() 159 p.P(`return this.`, fieldname) 160 p.Out() 161 p.P(`}`) 162 } 163 p.P(`return nil`) 164 p.Out() 165 p.P(`}`) 166 p.P(``) 167 p.P(`func (this *`, ccTypeName, `) SetValue(value interface{}) bool {`) 168 p.In() 169 p.P(`switch vt := value.(type) {`) 170 p.In() 171 for _, field := range message.Field { 172 fieldname := p.GetFieldName(message, field) 173 goTyp, _ := p.GoType(message, field) 174 p.P(`case `, goTyp, `:`) 175 p.In() 176 p.P(`this.`, fieldname, ` = vt`) 177 p.Out() 178 } 179 p.P(`default:`) 180 p.In() 181 for _, field := range message.Field { 182 fieldname := p.GetFieldName(message, field) 183 if field.IsMessage() { 184 goTyp, _ := p.GoType(message, field) 185 obj := p.ObjectNamed(field.GetTypeName()).(*generator.Descriptor) 186 187 if gogoproto.IsUnion(obj.File().FileDescriptorProto, obj.DescriptorProto) { 188 p.P(`this.`, fieldname, ` = new(`, generator.GoTypeToName(goTyp), `)`) 189 p.P(`if set := this.`, fieldname, `.SetValue(value); set {`) 190 p.In() 191 p.P(`return true`) 192 p.Out() 193 p.P(`}`) 194 p.P(`this.`, fieldname, ` = nil`) 195 } 196 } 197 } 198 p.P(`return false`) 199 p.Out() 200 p.P(`}`) 201 p.P(`return true`) 202 p.Out() 203 p.P(`}`) 204 } 205 } 206 207 func init() { 208 generator.RegisterPlugin(NewUnion()) 209 } 210