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  }