go.uber.org/yarpc@v1.72.1/internal/protoplugin/protoplugin.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 /* 22 Package protoplugin provides utilities for protoc plugins. 23 24 The only functions that should be called as of now is Main. The rest are 25 not guaranteed to stay here. 26 27 This was HEAVILY adapted from github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway. 28 29 Eventually, a rewrite of this to be simpler for what we need would be nice, but this was 30 available to get us here, especially with handling go imports. 31 32 Note that "FQMN", "FQSN", etc stand for "Fully Qualified Message Name", 33 "Fully Qualified Service Name", etc, which denotes the package and object name together. 34 */ 35 package protoplugin 36 37 import ( 38 "bytes" 39 "compress/gzip" 40 "fmt" 41 "io" 42 "io/ioutil" 43 "strings" 44 "text/template" 45 46 "github.com/gogo/protobuf/proto" 47 "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" 48 gogogen "github.com/gogo/protobuf/protoc-gen-gogo/generator" 49 "github.com/gogo/protobuf/protoc-gen-gogo/plugin" 50 ) 51 52 // Do is a helper function for protobuf plugins. 53 // 54 // func main() { 55 // if err := protoplugin.Do(runner, os.Stdin, os.Stdout); err != nil { 56 // log.Fatal(err) 57 // } 58 // } 59 func Do(runner Runner, reader io.Reader, writer io.Writer) error { 60 request, err := ReadRequest(reader) 61 if err != nil { 62 return err 63 } 64 return WriteResponse(writer, runner.Run(request)) 65 } 66 67 // ReadRequest reads the request from the reader. 68 func ReadRequest(reader io.Reader) (*plugin_go.CodeGeneratorRequest, error) { 69 input, err := ioutil.ReadAll(reader) 70 if err != nil { 71 return nil, err 72 } 73 request := &plugin_go.CodeGeneratorRequest{} 74 if err := proto.Unmarshal(input, request); err != nil { 75 return nil, err 76 } 77 return request, nil 78 } 79 80 // WriteResponse writes the response to the writer. 81 func WriteResponse(writer io.Writer, response *plugin_go.CodeGeneratorResponse) error { 82 buf, err := proto.Marshal(response) 83 if err != nil { 84 return err 85 } 86 if _, err := writer.Write(buf); err != nil { 87 return err 88 } 89 return nil 90 } 91 92 // Runner runs the plugin logic. 93 type Runner interface { 94 Run(*plugin_go.CodeGeneratorRequest) *plugin_go.CodeGeneratorResponse 95 } 96 97 // NewRunner returns a new Runner. 98 func NewRunner( 99 tmpl *template.Template, 100 templateInfoChecker func(*TemplateInfo) error, 101 baseImports []string, 102 fileToOutputFilename func(*File) (string, error), 103 unknownFlagHandler func(key string, value string) error, 104 ) Runner { 105 return newRunner(tmpl, templateInfoChecker, baseImports, fileToOutputFilename, unknownFlagHandler) 106 } 107 108 // NewMultiRunner returns a new Runner that executes all the given Runners and 109 // merges the resulting CodeGeneratorResponses. 110 func NewMultiRunner(runners ...Runner) Runner { 111 return newMultiRunner(runners...) 112 } 113 114 // TemplateInfo is the info passed to a template. 115 type TemplateInfo struct { 116 *File 117 Imports []*GoPackage 118 } 119 120 // GoPackage represents a golang package. 121 type GoPackage struct { 122 Path string 123 Name string 124 // Alias is an alias of the package unique within the current invocation of the generator. 125 Alias string 126 } 127 128 // Standard returns whether the import is a golang standard package. 129 func (g *GoPackage) Standard() bool { 130 return !strings.Contains(g.Path, ".") 131 } 132 133 // String returns a string representation of this package in the form of import line in golang. 134 func (g *GoPackage) String() string { 135 if g.Alias == "" { 136 return fmt.Sprintf("%q", g.Path) 137 } 138 return fmt.Sprintf("%s %q", g.Alias, g.Path) 139 } 140 141 // File wraps descriptor.FileDescriptorProto for richer features. 142 type File struct { 143 *descriptor.FileDescriptorProto 144 GoPackage *GoPackage 145 Messages []*Message 146 Enums []*Enum 147 Services []*Service 148 TransitiveDependencies []*File 149 } 150 151 // SerializedFileDescriptor returns a gzipped marshalled representation of the FileDescriptor. 152 func (f *File) SerializedFileDescriptor() ([]byte, error) { 153 pb := proto.Clone(f.FileDescriptorProto).(*descriptor.FileDescriptorProto) 154 pb.SourceCodeInfo = nil 155 156 b, err := proto.Marshal(pb) 157 if err != nil { 158 return nil, err 159 } 160 161 var buf bytes.Buffer 162 w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) 163 if err != nil { 164 return nil, err 165 } 166 167 _, err = w.Write(b) 168 if err != nil { 169 return nil, err 170 } 171 w.Close() 172 return buf.Bytes(), nil 173 } 174 175 // Message describes a protocol buffer message types. 176 type Message struct { 177 *descriptor.DescriptorProto 178 File *File 179 // Outers is a list of outer messages if this message is a nested type. 180 Outers []string 181 Fields []*Field 182 // Index is proto path index of this message in File. 183 Index int 184 } 185 186 // FQMN returns a fully qualified message name of this message. 187 func (m *Message) FQMN() string { 188 components := []string{""} 189 if m.File.Package != nil { 190 components = append(components, m.File.GetPackage()) 191 } 192 components = append(components, m.Outers...) 193 components = append(components, m.GetName()) 194 return strings.Join(components, ".") 195 } 196 197 // GoType returns a go type name for the message type. 198 // It prefixes the type name with the package alias if 199 // its belonging package is not "currentPackage". 200 func (m *Message) GoType(currentPackage string) string { 201 var components []string 202 components = append(components, m.Outers...) 203 // gogo_protobuf uses CamelCaseSlice which internally uses CamelCase for the GoType name conversion. 204 // see: https://github.com/gogo/protobuf/blob/v1.3.1/protoc-gen-gogo/generator/generator.go#L1810 205 // Added gogogen.CamelCase({message_name}) to keep GoTypes consistent between gogo_protobuf and yarpc generated 206 // protobuf files. 207 components = append(components, gogogen.CamelCase(m.GetName())) 208 209 name := strings.Join(components, "_") 210 if m.File.GoPackage.Path == currentPackage { 211 return name 212 } 213 pkg := m.File.GoPackage.Name 214 if alias := m.File.GoPackage.Alias; alias != "" { 215 pkg = alias 216 } 217 return fmt.Sprintf("%s.%s", pkg, name) 218 } 219 220 // Enum describes a protocol buffer enum type. 221 type Enum struct { 222 *descriptor.EnumDescriptorProto 223 File *File 224 // Outers is a list of outer messages if this enum is a nested type. 225 Outers []string 226 Index int 227 } 228 229 // FQEN returns a fully qualified enum name of this enum. 230 func (e *Enum) FQEN() string { 231 components := []string{""} 232 if e.File.Package != nil { 233 components = append(components, e.File.GetPackage()) 234 } 235 components = append(components, e.Outers...) 236 components = append(components, e.GetName()) 237 return strings.Join(components, ".") 238 } 239 240 // Service wraps descriptor.ServiceDescriptorProto for richer features. 241 type Service struct { 242 *descriptor.ServiceDescriptorProto 243 File *File 244 Methods []*Method 245 } 246 247 // FQSN returns a fully qualified service name of this service. 248 func (s *Service) FQSN() string { 249 components := []string{""} 250 if s.File.Package != nil { 251 components = append(components, s.File.GetPackage()) 252 } 253 components = append(components, s.GetName()) 254 return strings.Join(components, ".") 255 } 256 257 // Method wraps descriptor.MethodDescriptorProto for richer features. 258 type Method struct { 259 *descriptor.MethodDescriptorProto 260 Service *Service 261 RequestType *Message 262 ResponseType *Message 263 } 264 265 // Field wraps descriptor.FieldDescriptorProto for richer features. 266 type Field struct { 267 *descriptor.FieldDescriptorProto 268 // Message is the message type which this field belongs to. 269 Message *Message 270 // FieldMessage is the message type of the field. 271 FieldMessage *Message 272 }