go.uber.org/yarpc@v1.72.1/encoding/thrift/thriftrw-plugin-yarpc/exception.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  package main
    22  
    23  import (
    24  	"fmt"
    25  	"path/filepath"
    26  	"strings"
    27  
    28  	"go.uber.org/thriftrw/compile"
    29  	"go.uber.org/thriftrw/plugin"
    30  )
    31  
    32  const (
    33  	_errorCodeAnnotationKey = "rpc.code"
    34  )
    35  
    36  const yarpcerrorTemplate = `
    37  // Code generated by thriftrw-plugin-yarpc
    38  // @generated
    39  
    40  package <.Name>
    41  
    42  <range $key, $val := .Types>
    43  	<if (isException $val)>
    44  	<$yarpcerrors := import "go.uber.org/yarpc/yarpcerrors" ->
    45  	// YARPCErrorCode returns <if isSetYARPCCode .Annotations>a <getYARPCErrorCode .><else>nil<end> for <$val.Name>.
    46  	//
    47  	// This is derived from the rpc.code annotation on the Thrift exception.
    48  	func (e *<$val.Name>) YARPCErrorCode() *<$yarpcerrors>.Code {
    49  		<if isSetYARPCCode .Annotations>code := <getYARPCErrorCode .>
    50  		return &code
    51  		<else>
    52  		return nil
    53  		<end>}
    54  
    55  	// Name is the error name for <$val.Name>.
    56  	func (e *<$val.Name>) YARPCErrorName() string { return <getYARPCErrorName .> }
    57  	<end>
    58  <end>
    59  `
    60  
    61  var (
    62  	_gRPCCodeNameToYARPCErrorCodeType = map[string]string{
    63  		// https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
    64  		"CANCELLED":           "yarpcerrors.CodeCancelled",
    65  		"UNKNOWN":             "yarpcerrors.CodeUnknown",
    66  		"INVALID_ARGUMENT":    "yarpcerrors.CodeInvalidArgument",
    67  		"DEADLINE_EXCEEDED":   "yarpcerrors.CodeDeadlineExceeded",
    68  		"NOT_FOUND":           "yarpcerrors.CodeNotFound",
    69  		"ALREADY_EXISTS":      "yarpcerrors.CodeAlreadyExists",
    70  		"PERMISSION_DENIED":   "yarpcerrors.CodePermissionDenied",
    71  		"RESOURCE_EXHAUSTED":  "yarpcerrors.CodeResourceExhausted",
    72  		"FAILED_PRECONDITION": "yarpcerrors.CodeFailedPrecondition",
    73  		"ABORTED":             "yarpcerrors.CodeAborted",
    74  		"OUT_OF_RANGE":        "yarpcerrors.CodeOutOfRange",
    75  		"UNIMPLEMENTED":       "yarpcerrors.CodeUnimplemented",
    76  		"INTERNAL":            "yarpcerrors.CodeInternal",
    77  		"UNAVAILABLE":         "yarpcerrors.CodeUnavailable",
    78  		"DATA_LOSS":           "yarpcerrors.CodeDataLoss",
    79  		"UNAUTHENTICATED":     "yarpcerrors.CodeUnauthenticated",
    80  	}
    81  
    82  	_availableCodes = fmt.Sprintf(`Available codes: %s`, strings.Join(
    83  		// Codes are listed below in enum-order, derived from:
    84  		// - https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
    85  		[]string{
    86  			"CANCELLED",
    87  			"UNKNOWN",
    88  			"INVALID_ARGUMENT",
    89  			"DEADLINE_EXCEEDED",
    90  			"NOT_FOUND",
    91  			"ALREADY_EXISTS",
    92  			"PERMISSION_DENIED",
    93  			"RESOURCE_EXHAUSTED",
    94  			"FAILED_PRECONDITION",
    95  			"ABORTED",
    96  			"OUT_OF_RANGE",
    97  			"UNIMPLEMENTED",
    98  			"INTERNAL",
    99  			"UNAVAILABLE",
   100  			"DATA_LOSS",
   101  			"UNAUTHENTICATED",
   102  		}, ","))
   103  )
   104  
   105  func yarpcErrorGenerator(data *moduleTemplateData, files map[string][]byte) error {
   106  	// kv.thrift => .../kv/types_yarpc.go
   107  	path := filepath.Join(data.Module.Directory, "types_yarpc.go")
   108  
   109  	// Get the original thrift file path
   110  	thriftFilePath := data.Module.GetThriftFilePath()
   111  	// and re-compile it. Plugins do not actually have access to the original
   112  	// thrift file's module, and this is how we gain access to it, since this
   113  	// generator writes extra methods to exception types defined in the Thrift
   114  	// file.
   115  	compiledModule, err := compile.Compile(thriftFilePath)
   116  	if err != nil {
   117  		return fmt.Errorf("error compiling the thrift file: %s", err.Error())
   118  	}
   119  
   120  	templateOptions := append(templateOptions,
   121  		plugin.TemplateFunc("isException", func(t compile.TypeSpec) bool {
   122  			if t, ok := t.(*compile.StructSpec); ok {
   123  				return t.IsExceptionType()
   124  			}
   125  			return false
   126  		}),
   127  		plugin.TemplateFunc("isSetYARPCCode", func(a compile.Annotations) bool {
   128  			_, found := a[_errorCodeAnnotationKey]
   129  			return found
   130  		}),
   131  		plugin.TemplateFunc("getYARPCErrorCode", getYARPCErrorCode),
   132  		plugin.TemplateFunc("getYARPCErrorName", getYARPCErrorName),
   133  	)
   134  
   135  	files[path], err = plugin.GoFileFromTemplate(path, yarpcerrorTemplate, compiledModule, templateOptions...)
   136  	return err
   137  }
   138  
   139  func getYARPCErrorCode(t *compile.StructSpec) string {
   140  	errorCodeString := t.Annotations[_errorCodeAnnotationKey]
   141  	yCode, ok := _gRPCCodeNameToYARPCErrorCodeType[errorCodeString]
   142  	if !ok {
   143  		panic(fmt.Sprintf("invalid rpc.code annotation for %q: %q\n%s", t.Name, errorCodeString, _availableCodes))
   144  	}
   145  	return yCode
   146  }
   147  
   148  func getYARPCErrorName(t *compile.StructSpec) string {
   149  	return fmt.Sprintf("%q", t.ThriftName())
   150  }