go.uber.org/yarpc@v1.72.1/internal/outboundmiddleware/chain.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 outboundmiddleware 22 23 import ( 24 "context" 25 26 "go.uber.org/yarpc/api/middleware" 27 "go.uber.org/yarpc/api/transport" 28 "go.uber.org/yarpc/api/x/introspection" 29 ) 30 31 var ( 32 _ transport.Namer = (*unaryChainExec)(nil) 33 _ transport.Namer = (*onewayChainExec)(nil) 34 _ transport.Namer = (*streamChainExec)(nil) 35 ) 36 37 // UnaryChain combines a series of `UnaryOutbound`s into a single `UnaryOutbound`. 38 func UnaryChain(mw ...middleware.UnaryOutbound) middleware.UnaryOutbound { 39 unchained := make([]middleware.UnaryOutbound, 0, len(mw)) 40 for _, m := range mw { 41 if m == nil { 42 continue 43 } 44 if c, ok := m.(unaryChain); ok { 45 unchained = append(unchained, c...) 46 continue 47 } 48 unchained = append(unchained, m) 49 } 50 51 switch len(unchained) { 52 case 0: 53 return middleware.NopUnaryOutbound 54 case 1: 55 return unchained[0] 56 default: 57 return unaryChain(unchained) 58 } 59 } 60 61 type unaryChain []middleware.UnaryOutbound 62 63 func (c unaryChain) Call(ctx context.Context, request *transport.Request, out transport.UnaryOutbound) (*transport.Response, error) { 64 return unaryChainExec{ 65 Chain: []middleware.UnaryOutbound(c), 66 Final: out, 67 }.Call(ctx, request) 68 } 69 70 // unaryChainExec adapts a series of `UnaryOutbound`s into a `UnaryOutbound`. It 71 // is scoped to a single call of a UnaryOutbound and is not thread-safe. 72 type unaryChainExec struct { 73 Chain []middleware.UnaryOutbound 74 Final transport.UnaryOutbound 75 } 76 77 func (x unaryChainExec) TransportName() string { 78 var name string 79 if namer, ok := x.Final.(transport.Namer); ok { 80 name = namer.TransportName() 81 } 82 return name 83 } 84 85 func (x unaryChainExec) Transports() []transport.Transport { 86 return x.Final.Transports() 87 } 88 89 func (x unaryChainExec) Start() error { 90 return x.Final.Start() 91 } 92 93 func (x unaryChainExec) Stop() error { 94 return x.Final.Stop() 95 } 96 97 func (x unaryChainExec) IsRunning() bool { 98 return x.Final.IsRunning() 99 } 100 101 func (x unaryChainExec) Call(ctx context.Context, request *transport.Request) (*transport.Response, error) { 102 if len(x.Chain) == 0 { 103 return x.Final.Call(ctx, request) 104 } 105 next := x.Chain[0] 106 x.Chain = x.Chain[1:] 107 return next.Call(ctx, request, x) 108 } 109 110 func (x unaryChainExec) Introspect() introspection.OutboundStatus { 111 if o, ok := x.Final.(introspection.IntrospectableOutbound); ok { 112 return o.Introspect() 113 } 114 return introspection.OutboundStatusNotSupported 115 } 116 117 // OnewayChain combines a series of `OnewayOutbound`s into a single `OnewayOutbound`. 118 func OnewayChain(mw ...middleware.OnewayOutbound) middleware.OnewayOutbound { 119 unchained := make([]middleware.OnewayOutbound, 0, len(mw)) 120 for _, m := range mw { 121 if m == nil { 122 continue 123 } 124 if c, ok := m.(onewayChain); ok { 125 unchained = append(unchained, c...) 126 continue 127 } 128 unchained = append(unchained, m) 129 } 130 131 switch len(unchained) { 132 case 0: 133 return middleware.NopOnewayOutbound 134 case 1: 135 return unchained[0] 136 default: 137 return onewayChain(unchained) 138 } 139 } 140 141 type onewayChain []middleware.OnewayOutbound 142 143 func (c onewayChain) CallOneway(ctx context.Context, request *transport.Request, out transport.OnewayOutbound) (transport.Ack, error) { 144 return onewayChainExec{ 145 Chain: []middleware.OnewayOutbound(c), 146 Final: out, 147 }.CallOneway(ctx, request) 148 } 149 150 // onewayChainExec adapts a series of `OnewayOutbound`s into a `OnewayOutbound`. It 151 // is scoped to a single call of a OnewayOutbound and is not thread-safe. 152 type onewayChainExec struct { 153 Chain []middleware.OnewayOutbound 154 Final transport.OnewayOutbound 155 } 156 157 func (x onewayChainExec) TransportName() string { 158 var name string 159 if namer, ok := x.Final.(transport.Namer); ok { 160 name = namer.TransportName() 161 } 162 return name 163 } 164 165 func (x onewayChainExec) Transports() []transport.Transport { 166 return x.Final.Transports() 167 } 168 169 func (x onewayChainExec) Start() error { 170 return x.Final.Start() 171 } 172 173 func (x onewayChainExec) Stop() error { 174 return x.Final.Stop() 175 } 176 177 func (x onewayChainExec) IsRunning() bool { 178 return x.Final.IsRunning() 179 } 180 181 func (x onewayChainExec) CallOneway(ctx context.Context, request *transport.Request) (transport.Ack, error) { 182 if len(x.Chain) == 0 { 183 return x.Final.CallOneway(ctx, request) 184 } 185 next := x.Chain[0] 186 x.Chain = x.Chain[1:] 187 return next.CallOneway(ctx, request, x) 188 } 189 190 func (x onewayChainExec) Introspect() introspection.OutboundStatus { 191 if o, ok := x.Final.(introspection.IntrospectableOutbound); ok { 192 return o.Introspect() 193 } 194 return introspection.OutboundStatusNotSupported 195 } 196 197 // StreamChain combines a series of `StreamOutbound`s into a single `StreamOutbound`. 198 func StreamChain(mw ...middleware.StreamOutbound) middleware.StreamOutbound { 199 unchained := make([]middleware.StreamOutbound, 0, len(mw)) 200 for _, m := range mw { 201 if m == nil { 202 continue 203 } 204 if c, ok := m.(streamChain); ok { 205 unchained = append(unchained, c...) 206 continue 207 } 208 unchained = append(unchained, m) 209 } 210 211 switch len(unchained) { 212 case 0: 213 return middleware.NopStreamOutbound 214 case 1: 215 return unchained[0] 216 default: 217 return streamChain(unchained) 218 } 219 } 220 221 type streamChain []middleware.StreamOutbound 222 223 func (c streamChain) CallStream(ctx context.Context, request *transport.StreamRequest, out transport.StreamOutbound) (*transport.ClientStream, error) { 224 return streamChainExec{ 225 Chain: []middleware.StreamOutbound(c), 226 Final: out, 227 }.CallStream(ctx, request) 228 } 229 230 // streamChainExec adapts a series of `StreamOutbound`s into a `StreamOutbound`. It 231 // is scoped to a single call of a StreamOutbound and is not thread-safe. 232 type streamChainExec struct { 233 Chain []middleware.StreamOutbound 234 Final transport.StreamOutbound 235 } 236 237 func (x streamChainExec) TransportName() string { 238 var name string 239 if namer, ok := x.Final.(transport.Namer); ok { 240 name = namer.TransportName() 241 } 242 return name 243 } 244 245 func (x streamChainExec) Transports() []transport.Transport { 246 return x.Final.Transports() 247 } 248 249 func (x streamChainExec) Start() error { 250 return x.Final.Start() 251 } 252 253 func (x streamChainExec) Stop() error { 254 return x.Final.Stop() 255 } 256 257 func (x streamChainExec) IsRunning() bool { 258 return x.Final.IsRunning() 259 } 260 261 func (x streamChainExec) CallStream(ctx context.Context, request *transport.StreamRequest) (*transport.ClientStream, error) { 262 if len(x.Chain) == 0 { 263 return x.Final.CallStream(ctx, request) 264 } 265 next := x.Chain[0] 266 x.Chain = x.Chain[1:] 267 return next.CallStream(ctx, request, x) 268 }