go.uber.org/yarpc@v1.72.1/encoding/thrift/thriftrw-plugin-yarpc/main.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  // thriftrw-plugin-yarpc implements a plugin for ThriftRW that generates code
    22  // compatible with YARPC.
    23  //
    24  // thriftrw-plugin-yarpc supports "rpc.code" annotations on Thrift exceptions.
    25  // For example:
    26  //
    27  //  exception ExceptionWithCode {
    28  //    1: required string val
    29  //  } (
    30  //    rpc.code = "INVALID_ARGUMENT"
    31  //  )
    32  //
    33  // The "rpc.code" annotation can be any code matching the string name of gRPC
    34  // status enum codes. YARPC error codes match 1-1 with these codes, however gRPC
    35  // uses a different string name representation. We choose to use the raw gRPC
    36  // enum code names instead to ensure cross-language compatibility with other
    37  // languages, such as Java.
    38  //  - https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
    39  //
    40  // Available string names method:
    41  //  - "CANCELLED"
    42  //  - "UNKNOWN"
    43  //  - "INVALID_ARGUMENT"
    44  //  - "DEADLINE_EXCEEDED"
    45  //  - "NOT_FOUND"
    46  //  - "ALREADY_EXISTS"
    47  //  - "PERMISSION_DENIED"
    48  //  - "RESOURCE_EXHAUSTED"
    49  //  - "FAILED_PRECONDITION"
    50  //  - "ABORTED"
    51  //  - "OUT_OF_RANGE"
    52  //  - "UNIMPLEMENTED"
    53  //  - "INTERNAL"
    54  //  - "UNAVAILABLE"
    55  //  - "DATA_LOSS"
    56  //  - "UNAUTHENTICATED"
    57  //
    58  // Adding codes will affect YARPC's observability middleware classification of
    59  // client and server errors for Thrift exceptions.
    60  //
    61  // For more information on the Thrift encoding, check the documentation of the
    62  // parent package.
    63  package main
    64  
    65  import (
    66  	"flag"
    67  	"strings"
    68  
    69  	"go.uber.org/thriftrw/plugin"
    70  	"go.uber.org/thriftrw/plugin/api"
    71  )
    72  
    73  // Command line flags
    74  var (
    75  	_context = flag.String("context-import-path",
    76  		"context",
    77  		"Import path at which Context is available")
    78  	_unaryHandlerWrapper = flag.String("unary-handler-wrapper",
    79  		"go.uber.org/yarpc/encoding/thrift.UnaryHandler",
    80  		"Function used to wrap generic Thrift unary function handlers into YARPC handlers")
    81  	_onewayHandlerWrapper = flag.String("oneway-handler-wrapper",
    82  		"go.uber.org/yarpc/encoding/thrift.OnewayHandler",
    83  		"Function used to wrap generic Thrift oneway function handlers into YARPC handlers")
    84  	_noGomock = flag.Bool("no-gomock", false,
    85  		"Don't generate gomock mocks for service clients")
    86  	_noFx             = flag.Bool("no-fx", false, "Don't generate Fx module")
    87  	_sanitizeTChannel = flag.Bool("sanitize-tchannel", false, "Enable tchannel context sanitization")
    88  )
    89  
    90  type g struct {
    91  	SanitizeTChannel bool
    92  }
    93  
    94  func (g g) Generate(req *api.GenerateServiceRequest) (*api.GenerateServiceResponse, error) {
    95  	// moduleGenerators apply to all Thrift IDL files, even when no service
    96  	// definition exists
    97  	moduleGenerators := []moduleGenFunc{yarpcErrorGenerator}
    98  
    99  	// serviceGenerators apply only when one or more services are defined in the
   100  	// Thrift IDL file.
   101  	serviceGenerators := []serviceGenFunc{clientGenerator, serverGenerator}
   102  	if !*_noFx {
   103  		serviceGenerators = append(serviceGenerators, fxGenerator)
   104  	}
   105  	if !*_noGomock {
   106  		serviceGenerators = append(serviceGenerators, gomockGenerator)
   107  	}
   108  
   109  	unaryWrapperImport, unaryWrapperFunc := splitFunctionPath(*_unaryHandlerWrapper)
   110  	onewayWrapperImport, onewayWrapperFunc := splitFunctionPath(*_onewayHandlerWrapper)
   111  
   112  	files := make(map[string][]byte)
   113  
   114  	for _, serviceID := range req.RootServices {
   115  		data := serviceTemplateData{
   116  			Svc:                 buildSvc(serviceID, req),
   117  			ContextImportPath:   *_context,
   118  			UnaryWrapperImport:  unaryWrapperImport,
   119  			UnaryWrapperFunc:    unaryWrapperFunc,
   120  			OnewayWrapperImport: onewayWrapperImport,
   121  			OnewayWrapperFunc:   onewayWrapperFunc,
   122  			SanitizeTChannel:    g.SanitizeTChannel,
   123  		}
   124  		for _, gen := range serviceGenerators {
   125  			if err := gen(&data, files); err != nil {
   126  				return nil, err
   127  			}
   128  		}
   129  	}
   130  
   131  	for _, moduleID := range req.RootModules {
   132  		data := moduleTemplateData{
   133  			Module:            req.Modules[moduleID],
   134  			ContextImportPath: *_context,
   135  		}
   136  		for _, gen := range moduleGenerators {
   137  			if err := gen(&data, files); err != nil {
   138  				return nil, err
   139  			}
   140  		}
   141  	}
   142  	return &api.GenerateServiceResponse{Files: files}, nil
   143  }
   144  
   145  func splitFunctionPath(input string) (string, string) {
   146  	i := strings.LastIndex(input, ".")
   147  	return input[:i], input[i+1:]
   148  }
   149  
   150  func buildSvc(serviceID api.ServiceID, req *api.GenerateServiceRequest) *Svc {
   151  	service := req.Services[serviceID]
   152  	module := req.Modules[service.ModuleID]
   153  
   154  	var parents []*Svc
   155  	if service.ParentID != nil {
   156  		parentSvc := buildSvc(*service.ParentID, req)
   157  		parents = append(parents, parentSvc)
   158  		parents = append(parents, parentSvc.Parents...)
   159  	}
   160  
   161  	return &Svc{
   162  		Service: service,
   163  		Module:  module,
   164  		Parents: parents,
   165  	}
   166  }
   167  
   168  func main() {
   169  	flag.Parse()
   170  	plugin.Main(&plugin.Plugin{Name: "yarpc", ServiceGenerator: g{
   171  		SanitizeTChannel: *_sanitizeTChannel,
   172  	}})
   173  }