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  }