go.uber.org/yarpc@v1.72.1/internal/protoplugin-v2/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 protopluginv2 provides utilities for protoc plugins. 23 */ 24 package protopluginv2 25 26 import ( 27 "bytes" 28 "compress/gzip" 29 "fmt" 30 "io" 31 "io/ioutil" 32 "strings" 33 "text/template" 34 35 "github.com/golang/protobuf/protoc-gen-go/descriptor" 36 "github.com/golang/protobuf/protoc-gen-go/plugin" 37 "google.golang.org/protobuf/proto" 38 ) 39 40 // Do is a helper function for protobuf plugins. 41 // 42 // func main() { 43 // if err := protoplugin.Do(runner, os.Stdin, os.Stdout); err != nil { 44 // log.Fatal(err) 45 // } 46 // } 47 func Do(runner Runner, reader io.Reader, writer io.Writer) error { 48 request, err := ReadRequest(reader) 49 if err != nil { 50 return err 51 } 52 return WriteResponse(writer, runner.Run(request)) 53 } 54 55 // ReadRequest reads the request from the reader. 56 func ReadRequest(reader io.Reader) (*plugin_go.CodeGeneratorRequest, error) { 57 input, err := ioutil.ReadAll(reader) 58 if err != nil { 59 return nil, err 60 } 61 request := &plugin_go.CodeGeneratorRequest{} 62 if err := proto.Unmarshal(input, request); err != nil { 63 return nil, err 64 } 65 return request, nil 66 } 67 68 // WriteResponse writes the response to the writer. 69 func WriteResponse(writer io.Writer, response *plugin_go.CodeGeneratorResponse) error { 70 buf, err := proto.Marshal(response) 71 if err != nil { 72 return err 73 } 74 if _, err := writer.Write(buf); err != nil { 75 return err 76 } 77 return nil 78 } 79 80 // Runner runs the plugin logic. 81 type Runner interface { 82 Run(*plugin_go.CodeGeneratorRequest) *plugin_go.CodeGeneratorResponse 83 } 84 85 // NewRunner returns a new Runner. 86 func NewRunner( 87 tmpl *template.Template, 88 templateInfoChecker func(*TemplateInfo) error, 89 baseImports []string, 90 fileToOutputFilename func(*File) (string, error), 91 unknownFlagHandler func(key string, value string) error, 92 ) Runner { 93 return newRunner(tmpl, templateInfoChecker, baseImports, fileToOutputFilename, unknownFlagHandler) 94 } 95 96 // NewMultiRunner returns a new Runner that executes all the given Runners and 97 // merges the resulting CodeGeneratorResponses. 98 func NewMultiRunner(runners ...Runner) Runner { 99 return newMultiRunner(runners...) 100 } 101 102 // TemplateInfo is the info passed to a template. 103 type TemplateInfo struct { 104 *File 105 Imports []*GoPackage 106 } 107 108 // GoPackage represents a golang package. 109 type GoPackage struct { 110 Path string 111 Name string 112 // Alias is an alias of the package unique within the current invocation of the generator. 113 Alias string 114 } 115 116 // Standard returns whether the import is a golang standard package. 117 func (g *GoPackage) Standard() bool { 118 return !strings.Contains(g.Path, ".") 119 } 120 121 // String returns a string representation of this package in the form of import line in golang. 122 func (g *GoPackage) String() string { 123 if g.Alias == "" { 124 return fmt.Sprintf("%q", g.Path) 125 } 126 return fmt.Sprintf("%s %q", g.Alias, g.Path) 127 } 128 129 // File wraps descriptor.FileDescriptorProto for richer features. 130 type File struct { 131 *descriptor.FileDescriptorProto 132 GoPackage *GoPackage 133 Messages []*Message 134 Enums []*Enum 135 Services []*Service 136 TransitiveDependencies []*File 137 } 138 139 // SerializedFileDescriptor returns a gzipped marshalled representation of the FileDescriptor. 140 func (f *File) SerializedFileDescriptor() ([]byte, error) { 141 pb := proto.Clone(f.FileDescriptorProto).(*descriptor.FileDescriptorProto) 142 pb.SourceCodeInfo = nil 143 144 b, err := proto.Marshal(pb) 145 if err != nil { 146 return nil, err 147 } 148 149 var buf bytes.Buffer 150 w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) 151 if err != nil { 152 return nil, err 153 } 154 155 _, err = w.Write(b) 156 if err != nil { 157 return nil, err 158 } 159 w.Close() 160 return buf.Bytes(), nil 161 } 162 163 // Message describes a protocol buffer message types. 164 type Message struct { 165 *descriptor.DescriptorProto 166 File *File 167 // Outers is a list of outer messages if this message is a nested type. 168 Outers []string 169 Fields []*Field 170 // Index is proto path index of this message in File. 171 Index int 172 } 173 174 // FQMN returns a fully qualified message name of this message. 175 func (m *Message) FQMN() string { 176 components := []string{""} 177 if m.File.Package != nil { 178 components = append(components, m.File.GetPackage()) 179 } 180 components = append(components, m.Outers...) 181 components = append(components, m.GetName()) 182 return strings.Join(components, ".") 183 } 184 185 // GoType returns a go type name for the message type. 186 // It prefixes the type name with the package alias if 187 // its belonging package is not "currentPackage". 188 func (m *Message) GoType(currentPackage string) string { 189 var components []string 190 components = append(components, m.Outers...) 191 components = append(components, goCamelCase(m.GetName())) 192 193 name := strings.Join(components, "_") 194 if m.File.GoPackage.Path == currentPackage { 195 return name 196 } 197 pkg := m.File.GoPackage.Name 198 if alias := m.File.GoPackage.Alias; alias != "" { 199 pkg = alias 200 } 201 return fmt.Sprintf("%s.%s", pkg, name) 202 } 203 204 // Enum describes a protocol buffer enum type. 205 type Enum struct { 206 *descriptor.EnumDescriptorProto 207 File *File 208 // Outers is a list of outer messages if this enum is a nested type. 209 Outers []string 210 Index int 211 } 212 213 // FQEN returns a fully qualified enum name of this enum. 214 func (e *Enum) FQEN() string { 215 components := []string{""} 216 if e.File.Package != nil { 217 components = append(components, e.File.GetPackage()) 218 } 219 components = append(components, e.Outers...) 220 components = append(components, e.GetName()) 221 return strings.Join(components, ".") 222 } 223 224 // Service wraps descriptor.ServiceDescriptorProto for richer features. 225 type Service struct { 226 *descriptor.ServiceDescriptorProto 227 File *File 228 Methods []*Method 229 } 230 231 // FQSN returns a fully qualified service name of this service. 232 func (s *Service) FQSN() string { 233 components := []string{""} 234 if s.File.Package != nil { 235 components = append(components, s.File.GetPackage()) 236 } 237 components = append(components, s.GetName()) 238 return strings.Join(components, ".") 239 } 240 241 // Method wraps descriptor.MethodDescriptorProto for richer features. 242 type Method struct { 243 *descriptor.MethodDescriptorProto 244 Service *Service 245 RequestType *Message 246 ResponseType *Message 247 } 248 249 // Field wraps descriptor.FieldDescriptorProto for richer features. 250 type Field struct { 251 *descriptor.FieldDescriptorProto 252 // Message is the message type which this field belongs to. 253 Message *Message 254 // FieldMessage is the message type of the field. 255 FieldMessage *Message 256 }