go.uber.org/yarpc@v1.72.1/encoding/thrift/thriftrw-plugin-yarpc/server.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  	"path/filepath"
    25  
    26  	"go.uber.org/thriftrw/plugin"
    27  )
    28  
    29  const serverTemplate = `
    30  // Code generated by thriftrw-plugin-yarpc
    31  // @generated
    32  
    33  <$pkgname := printf "%sserver" (lower .Name)>
    34  package <$pkgname>
    35  
    36  <$thrift      := import "go.uber.org/yarpc/encoding/thrift">
    37  <$transport   := import "go.uber.org/yarpc/api/transport">
    38  
    39  <$contextImportPath   := .ContextImportPath>
    40  <$onewayWrapperImport := .OnewayWrapperImport>
    41  <$onewayWrapperFunc   := .OnewayWrapperFunc>
    42  <$unaryWrapperImport  := .UnaryWrapperImport>
    43  <$unaryWrapperFunc    := .UnaryWrapperFunc>
    44  
    45  </* Note that we import things like "context" inside loops rather than at the
    46      top-level because they will end up unused if the service does not have any
    47      functions.
    48   */>
    49  
    50  // Interface is the server-side interface for the <.Name> service.
    51  type Interface interface {
    52  	<if .Parent><import .ParentServerPackagePath>.Interface
    53  	<end>
    54  	<range .Functions>
    55  		<$context := import $contextImportPath>
    56  		<.Name>(
    57  			ctx <$context>.Context, <range .Arguments>
    58  			<.Name> <formatType .Type>,<end>
    59  		)<if .OneWay> error
    60  		<else if .ReturnType> (<formatType .ReturnType>, error)
    61  		<else> error
    62  		<end>
    63  	<end>
    64  }
    65  
    66  <$module := .Module>
    67  
    68  // New prepares an implementation of the <.Name> service for
    69  // registration.
    70  //
    71  // 	handler := <.Name>Handler{}
    72  // 	dispatcher.Register(<$pkgname>.New(handler))
    73  func New(impl Interface, opts ...<$thrift>.RegisterOption) []<$transport>.Procedure {
    74  	<if .Functions>h := handler{impl}<end>
    75  	service := <$thrift>.Service{
    76  		Name: "<.Name>",
    77  		Methods: []<$thrift>.Method{
    78  		<range .Functions>
    79  			<$thrift>.Method{
    80  				Name: "<.ThriftName>",
    81  				HandlerSpec: <$thrift>.HandlerSpec{
    82  				<if .OneWay>
    83  					Type: <$transport>.Oneway,
    84  					Oneway: <import $onewayWrapperImport>.<$onewayWrapperFunc>(h.<.Name>),
    85  				<else>
    86  					Type: <$transport>.Unary,
    87  					Unary: <import $unaryWrapperImport>.<$unaryWrapperFunc>(h.<.Name>),
    88  				<end ->
    89  					NoWire: <(lower .Name)>_NoWireHandler{impl},
    90  				},
    91  				Signature: "<.Name>(<range $i, $v := .Arguments><if ne $i 0>, <end><.Name> <formatType .Type><end>)<if not .OneWay | and .ReturnType> (<formatType .ReturnType>)<end>",
    92  				ThriftModule: <import $module.ImportPath>.ThriftModule,
    93  				},
    94  		<end>},
    95  	}
    96  
    97  	procedures := make([]<$transport>.Procedure, 0, <len .Functions>)
    98  	<if .Parent>
    99  	procedures = append(
   100  		procedures,
   101  		<import .ParentServerPackagePath>.New(
   102  			impl,
   103  			append(
   104  				opts,
   105  				<$thrift>.Named(<printf "%q" .Name>),
   106  			)...,
   107  		)...,
   108  	)
   109  	<end ->
   110  	procedures = append(procedures, <$thrift>.BuildProcedures(service, opts...)...)
   111  	return procedures
   112  }
   113  
   114  <if .Functions>
   115  type handler struct{ impl Interface }
   116  
   117  <$yarpcerrors := import "go.uber.org/yarpc/yarpcerrors">
   118  
   119  type yarpcErrorNamer interface { YARPCErrorName() string }
   120  
   121  type yarpcErrorCoder interface { YARPCErrorCode() *yarpcerrors.Code }
   122  <end>
   123  
   124  <$service := .>
   125  <$module := .Module>
   126  <range .Functions>
   127  <$context := import $contextImportPath>
   128  <$prefix := printf "%s.%s_%s_" (import $module.ImportPath) $service.Name .Name>
   129  
   130  <$wire := import "go.uber.org/thriftrw/wire">
   131  
   132  <if .OneWay>
   133  func (h handler) <.Name>(ctx <$context>.Context, body <$wire>.Value) error {
   134  	var args <$prefix>Args
   135  	if err := args.FromWire(body); err != nil {
   136  		return err
   137  	}
   138  
   139  	return h.impl.<.Name>(ctx, <range .Arguments>args.<.Name>,<end>)
   140  }
   141  <else>
   142  <$yarpcerrors := import "go.uber.org/yarpc/yarpcerrors">
   143  func (h handler) <.Name>(ctx <$context>.Context, body <$wire>.Value) (<$thrift>.Response, error) {
   144  	var args <$prefix>Args
   145  	if err := args.FromWire(body); err != nil {
   146  		return <$thrift>.Response{}, <$yarpcerrors>.InvalidArgumentErrorf(
   147  			"could not decode Thrift request for service '<$service.Name>' procedure '<.Name>': %w", err)
   148  	}
   149  
   150  	<if .ReturnType>
   151  		success, appErr := h.impl.<.Name>(ctx, <range .Arguments>args.<.Name>,<end>)
   152  	<else>
   153  		appErr := h.impl.<.Name>(ctx, <range .Arguments>args.<.Name>,<end>)
   154  	<end>
   155  
   156  	hadError := appErr != nil
   157  	result, err := <$prefix>Helper.WrapResponse(<if .ReturnType>success,<end> appErr)
   158  
   159  	var response <$thrift>.Response
   160  	if err == nil {
   161  		response.IsApplicationError = hadError
   162  		response.Body = result
   163  		if namer, ok := appErr.(yarpcErrorNamer); ok {
   164  			response.ApplicationErrorName = namer.YARPCErrorName()
   165   		}
   166  		if extractor, ok := appErr.(yarpcErrorCoder); ok {
   167  			response.ApplicationErrorCode = extractor.YARPCErrorCode()
   168  		}
   169  		if appErr != nil {
   170  			response.ApplicationErrorDetails = appErr.Error()
   171  		}
   172  	}
   173  
   174  	return response, err
   175  }
   176  <end>
   177  <end>
   178  
   179  <range .Functions>
   180  <$context := import $contextImportPath>
   181  <$yarpcerrors := import "go.uber.org/yarpc/yarpcerrors">
   182  <$prefix := printf "%s.%s_%s_" (import $module.ImportPath) $service.Name .Name>
   183  
   184  type <(lower .Name)>_NoWireHandler struct{ impl Interface }
   185  
   186  func (h <(lower .Name)>_NoWireHandler) HandleNoWire(ctx <$context>.Context, nwc *<$thrift>.NoWireCall) (<$thrift>.NoWireResponse, error) {
   187  	var (
   188  		args <$prefix>Args
   189  		<if not .OneWay>rw <import "go.uber.org/thriftrw/protocol/stream">.ResponseWriter<end>
   190  		err error
   191  	)
   192  
   193  	<if .OneWay>
   194  	if _, err = nwc.RequestReader.ReadRequest(ctx, nwc.EnvelopeType, nwc.Reader, &args); err != nil {
   195  		return <$thrift>.NoWireResponse{}, <$yarpcerrors>.InvalidArgumentErrorf(
   196  			"could not decode (via no wire) Thrift request for service '<$service.Name>' procedure '<.Name>': %w", err)
   197  	}
   198  
   199  	return <$thrift>.NoWireResponse{}, h.impl.<.Name>(ctx, <range .Arguments>args.<.Name>,<end>)
   200  	<else>
   201  	rw, err = nwc.RequestReader.ReadRequest(ctx, nwc.EnvelopeType, nwc.Reader, &args)
   202  	if err != nil {
   203  		return <$thrift>.NoWireResponse{}, <$yarpcerrors>.InvalidArgumentErrorf(
   204  			"could not decode (via no wire) Thrift request for service '<$service.Name>' procedure '<.Name>': %w", err)
   205  	}
   206  
   207  	<if .ReturnType>
   208  	success, appErr := h.impl.<.Name>(ctx, <range .Arguments>args.<.Name>,<end>)
   209  	<else>
   210  	appErr := h.impl.<.Name>(ctx, <range .Arguments>args.<.Name>,<end>)
   211  	<end>
   212  
   213  	hadError := appErr != nil
   214  	result, err := <$prefix>Helper.WrapResponse(<if .ReturnType>success,<end> appErr)
   215  	response := <$thrift>.NoWireResponse{ResponseWriter: rw}
   216  	if err == nil {
   217  		response.IsApplicationError = hadError
   218  		response.Body = result
   219  		if namer, ok := appErr.(yarpcErrorNamer); ok {
   220  			response.ApplicationErrorName = namer.YARPCErrorName()
   221  		}
   222  		if extractor, ok := appErr.(yarpcErrorCoder); ok {
   223  			response.ApplicationErrorCode = extractor.YARPCErrorCode()
   224  		}
   225  		if appErr != nil {
   226  			response.ApplicationErrorDetails = appErr.Error()
   227  		}
   228  	}
   229  	return response, err
   230  	<end>
   231  }
   232  <end>
   233  `
   234  
   235  func serverGenerator(data *serviceTemplateData, files map[string][]byte) (err error) {
   236  	packageName := filepath.Base(data.ServerPackagePath())
   237  	// kv.thrift => .../kv/keyvalueserver/server.go
   238  	path := filepath.Join(data.Module.Directory, packageName, "server.go")
   239  	files[path], err = plugin.GoFileFromTemplate(path, serverTemplate, data, templateOptions...)
   240  	return
   241  }