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  }