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 }