go.uber.org/yarpc@v1.72.1/api/encoding/outbound_call.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 encoding 22 23 import ( 24 "context" 25 26 "go.uber.org/yarpc/api/transport" 27 "go.uber.org/yarpc/yarpcerrors" 28 ) 29 30 // OutboundCall is an outgoing call. It holds per-call options for a request. 31 // 32 // Encoding authors may use OutboundCall to provide a CallOption-based request 33 // customization mechanism, including returning response headers through 34 // ResponseHeaders. 35 type OutboundCall struct { 36 // request attributes to fill if non-nil 37 headers []keyValuePair 38 shardKey *string 39 routingKey *string 40 routingDelegate *string 41 42 // If non-nil, response headers should be written here. 43 responseHeaders *map[string]string 44 } 45 46 // NewOutboundCall constructs a new OutboundCall with the given options. 47 func NewOutboundCall(options ...CallOption) *OutboundCall { 48 var call OutboundCall 49 for _, opt := range options { 50 opt.opt.apply(&call) 51 } 52 return &call 53 } 54 55 // NewStreamOutboundCall constructs a new OutboundCall with the given 56 // options and enforces the OutboundCall is valid for streams. 57 func NewStreamOutboundCall(options ...CallOption) (*OutboundCall, error) { 58 call := NewOutboundCall(options...) 59 if call.responseHeaders != nil { 60 return nil, yarpcerrors.InvalidArgumentErrorf("response headers are not supported for streams") 61 } 62 return call, nil 63 } 64 65 // WriteToRequest fills the given request with request-specific options from 66 // the call. 67 // 68 // The context MAY be replaced by the OutboundCall. 69 func (c *OutboundCall) WriteToRequest(ctx context.Context, req *transport.Request) (context.Context, error) { 70 for _, h := range c.headers { 71 req.Headers = req.Headers.With(h.k, h.v) 72 } 73 74 if c.shardKey != nil { 75 req.ShardKey = *c.shardKey 76 } 77 if c.routingKey != nil { 78 req.RoutingKey = *c.routingKey 79 } 80 if c.routingDelegate != nil { 81 req.RoutingDelegate = *c.routingDelegate 82 } 83 84 // NB(abg): context and error are unused for now but we want to leave room 85 // for CallOptions which can fail or modify the context. 86 return ctx, nil 87 } 88 89 // WriteToRequestMeta fills the given request with request-specific options from 90 // the call. 91 // 92 // The context MAY be replaced by the OutboundCall. 93 func (c *OutboundCall) WriteToRequestMeta(ctx context.Context, reqMeta *transport.RequestMeta) (context.Context, error) { 94 for _, h := range c.headers { 95 reqMeta.Headers = reqMeta.Headers.With(h.k, h.v) 96 } 97 98 if c.shardKey != nil { 99 reqMeta.ShardKey = *c.shardKey 100 } 101 if c.routingKey != nil { 102 reqMeta.RoutingKey = *c.routingKey 103 } 104 if c.routingDelegate != nil { 105 reqMeta.RoutingDelegate = *c.routingDelegate 106 } 107 108 // NB(abg): context and error are unused for now but we want to leave room 109 // for CallOptions which can fail or modify the context. 110 return ctx, nil 111 } 112 113 // ReadFromResponse reads information from the response for this call. 114 // 115 // This should be called only if the request is unary. 116 func (c *OutboundCall) ReadFromResponse(ctx context.Context, res *transport.Response) (context.Context, error) { 117 // We're not using ctx right now but we may in the future. 118 if c.responseHeaders != nil && res.Headers.Len() > 0 { 119 // We make a copy of the response headers because Headers.Items() must 120 // never be mutated. 121 headers := make(map[string]string, res.Headers.Len()) 122 for k, v := range res.Headers.Items() { 123 headers[k] = v 124 } 125 *c.responseHeaders = headers 126 } 127 128 // NB(abg): context and error are unused for now but we want to leave room 129 // for CallOptions which can fail or modify the context. 130 return ctx, nil 131 }