go.uber.org/yarpc@v1.72.1/yarpctest/fake_outbound.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 yarpctest
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"fmt"
    27  	"io/ioutil"
    28  
    29  	"go.uber.org/yarpc/api/peer"
    30  	"go.uber.org/yarpc/api/transport"
    31  	"go.uber.org/yarpc/api/transport/transporttest"
    32  	"go.uber.org/yarpc/pkg/lifecycle"
    33  )
    34  
    35  var (
    36  	_ transport.Namer          = (*FakeOutbound)(nil)
    37  	_ transport.UnaryOutbound  = (*FakeOutbound)(nil)
    38  	_ transport.OnewayOutbound = (*FakeOutbound)(nil)
    39  	_ transport.StreamOutbound = (*FakeOutbound)(nil)
    40  )
    41  
    42  // FakeOutboundOption is an option for FakeTransport.NewOutbound.
    43  type FakeOutboundOption func(*FakeOutbound)
    44  
    45  // NopOutboundOption returns an option to set the "nopOption" for a
    46  // FakeTransport.NewOutbound.
    47  // The nopOption has no effect exists only to verify that the option was
    48  // passed, via `FakeOutbound.NopOption()`.
    49  func NopOutboundOption(nopOption string) FakeOutboundOption {
    50  	return func(o *FakeOutbound) {
    51  		o.nopOption = nopOption
    52  	}
    53  }
    54  
    55  // OutboundName sets the name of the "fake" outbound.
    56  func OutboundName(name string) FakeOutboundOption {
    57  	return func(o *FakeOutbound) {
    58  		o.name = name
    59  	}
    60  }
    61  
    62  // OutboundCallable is a function that will be called for for an outbound's
    63  // `Call` method.
    64  type OutboundCallable func(ctx context.Context, req *transport.Request) (*transport.Response, error)
    65  
    66  // OutboundOnewayCallable is a function that will be called for for an outbound's
    67  // `Call` method.
    68  type OutboundOnewayCallable func(context.Context, *transport.Request) (transport.Ack, error)
    69  
    70  // OutboundStreamCallable is a function that will be called for for an outbound's
    71  // `Call` method.
    72  type OutboundStreamCallable func(context.Context, *transport.StreamRequest) (*transport.ClientStream, error)
    73  
    74  // OutboundRouter returns an option to set the router for outbound requests.
    75  // This connects the outbound to the inbound side of a handler for testing
    76  // purposes.
    77  func OutboundRouter(router transport.Router) FakeOutboundOption {
    78  	return func(o *FakeOutbound) {
    79  		o.router = router
    80  	}
    81  }
    82  
    83  // OutboundCallOverride returns an option to set the "callOverride" for a
    84  // FakeTransport.NewOutbound.
    85  // This can be used to set the functionality for the FakeOutbound's `Call`
    86  // function.
    87  func OutboundCallOverride(callable OutboundCallable) FakeOutboundOption {
    88  	return func(o *FakeOutbound) {
    89  		o.callOverride = callable
    90  	}
    91  }
    92  
    93  // OutboundCallOnewayOverride returns an option to set the "callOverride" for a
    94  // FakeTransport.NewOutbound.
    95  //
    96  // This can be used to set the functionality for the FakeOutbound's `CallOneway`
    97  // function.
    98  func OutboundCallOnewayOverride(callable OutboundOnewayCallable) FakeOutboundOption {
    99  	return func(o *FakeOutbound) {
   100  		o.callOnewayOverride = callable
   101  	}
   102  }
   103  
   104  // OutboundCallStreamOverride returns an option to set the "callOverride" for a
   105  // FakeTransport.NewOutbound.
   106  //
   107  // This can be used to set the functionality for the FakeOutbound's `CallStream`
   108  // function.
   109  func OutboundCallStreamOverride(callable OutboundStreamCallable) FakeOutboundOption {
   110  	return func(o *FakeOutbound) {
   111  		o.callStreamOverride = callable
   112  	}
   113  }
   114  
   115  // NewOutbound returns a FakeOutbound with a given peer chooser and options.
   116  func (t *FakeTransport) NewOutbound(c peer.Chooser, opts ...FakeOutboundOption) *FakeOutbound {
   117  	o := &FakeOutbound{
   118  		name:      "fake",
   119  		once:      lifecycle.NewOnce(),
   120  		transport: t,
   121  		chooser:   c,
   122  	}
   123  	for _, opt := range opts {
   124  		opt(o)
   125  	}
   126  	return o
   127  }
   128  
   129  // FakeOutbound is a unary outbound for the FakeTransport. It is fake.
   130  type FakeOutbound struct {
   131  	name      string
   132  	once      *lifecycle.Once
   133  	transport *FakeTransport
   134  	chooser   peer.Chooser
   135  	nopOption string
   136  	router    transport.Router
   137  
   138  	callOverride       OutboundCallable
   139  	callOnewayOverride OutboundOnewayCallable
   140  	callStreamOverride OutboundStreamCallable
   141  }
   142  
   143  // TransportName is "fake".
   144  func (o *FakeOutbound) TransportName() string {
   145  	return "fake"
   146  }
   147  
   148  // Chooser returns theis FakeOutbound's peer chooser.
   149  func (o *FakeOutbound) Chooser() peer.Chooser {
   150  	return o.chooser
   151  }
   152  
   153  // NopOption returns this FakeOutbound's nopOption. It is fake.
   154  func (o *FakeOutbound) NopOption() string {
   155  	return o.nopOption
   156  }
   157  
   158  // Start starts the fake outbound and its chooser.
   159  func (o *FakeOutbound) Start() error {
   160  	return o.once.Start(o.chooser.Start)
   161  }
   162  
   163  // Stop stops the fake outbound and its chooser.
   164  func (o *FakeOutbound) Stop() error {
   165  	return o.once.Stop(o.chooser.Stop)
   166  }
   167  
   168  // IsRunning returns whether the fake outbound is running.
   169  func (o *FakeOutbound) IsRunning() bool {
   170  	return o.once.IsRunning()
   171  }
   172  
   173  // Transports returns the FakeTransport that owns this outbound.
   174  func (o *FakeOutbound) Transports() []transport.Transport {
   175  	return []transport.Transport{o.transport}
   176  }
   177  
   178  // Call mimicks sending a oneway RPC.
   179  //
   180  // By default, this returns a error.
   181  // The OutboundCallOverride option supplies an alternate implementation.
   182  // Alternately, the OutboundRouter option may allow this function to route a
   183  // unary request to a unary handler.
   184  func (o *FakeOutbound) Call(ctx context.Context, req *transport.Request) (*transport.Response, error) {
   185  	if o.callOverride != nil {
   186  		return o.callOverride(ctx, req)
   187  	}
   188  
   189  	if o.router == nil {
   190  		return nil, errors.New(`no outbound callable specified on the fake outbound`)
   191  	}
   192  
   193  	handler, err := o.router.Choose(ctx, req)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	unaryHandler := handler.Unary()
   199  	if unaryHandler == nil {
   200  		return nil, fmt.Errorf(`procedure %q for encoding %q does not handle unary requests`, req.Procedure, req.Encoding)
   201  	}
   202  
   203  	resWriter := &transporttest.FakeResponseWriter{}
   204  	if err := unaryHandler.Handle(ctx, req, resWriter); err != nil {
   205  		return nil, err
   206  	}
   207  	return &transport.Response{
   208  		ApplicationError: resWriter.IsApplicationError,
   209  		Headers:          resWriter.Headers,
   210  		Body:             ioutil.NopCloser(&resWriter.Body),
   211  	}, nil
   212  }
   213  
   214  // CallOneway mimicks sending a oneway RPC.
   215  //
   216  // By default, this returns an error.
   217  // The OutboundCallOnewayOverride supplies an alternate implementation.
   218  // Atlernately, the OutboundRouter options may route the request through to a
   219  // oneway handler.
   220  func (o *FakeOutbound) CallOneway(ctx context.Context, req *transport.Request) (transport.Ack, error) {
   221  	if o.callOnewayOverride != nil {
   222  		return o.callOnewayOverride(ctx, req)
   223  	}
   224  
   225  	if o.router == nil {
   226  		return nil, errors.New(`fake outbound does not support call oneway`)
   227  	}
   228  
   229  	handler, err := o.router.Choose(ctx, req)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	onewayHandler := handler.Oneway()
   235  	if onewayHandler == nil {
   236  		return nil, fmt.Errorf(`procedure %q for encoding %q does not handle oneway requests`, req.Procedure, req.Encoding)
   237  	}
   238  
   239  	return nil, onewayHandler.HandleOneway(ctx, req)
   240  }
   241  
   242  // CallStream mimicks sending a streaming RPC.
   243  //
   244  // By default, this returns an error.
   245  // The OutboundCallStreamOverride option provides a hook to change the behavior.
   246  // Alternately, the OutboundRouter option may route the request through to a
   247  // streaming handler.
   248  func (o *FakeOutbound) CallStream(ctx context.Context, streamReq *transport.StreamRequest) (*transport.ClientStream, error) {
   249  	if o.callStreamOverride != nil {
   250  		return o.callStreamOverride(ctx, streamReq)
   251  	}
   252  
   253  	if o.router == nil {
   254  		return nil, errors.New(`fake outbound does not support call stream`)
   255  	}
   256  
   257  	req := streamReq.Meta.ToRequest()
   258  	handler, err := o.router.Choose(ctx, req)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	streamHandler := handler.Stream()
   264  	if streamHandler == nil {
   265  		return nil, fmt.Errorf(`procedure %q for encoding %q does not handle streaming requests`, req.Procedure, req.Encoding)
   266  	}
   267  
   268  	clientStream, serverStream, finish, err := transporttest.MessagePipe(ctx, streamReq)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	go func() {
   274  		finish(streamHandler.HandleStream(serverStream))
   275  	}()
   276  	return clientStream, nil
   277  }