go.uber.org/yarpc@v1.72.1/encoding/json/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 json
    22  
    23  import (
    24  	"bytes"
    25  	"context"
    26  	"encoding/json"
    27  
    28  	"go.uber.org/yarpc"
    29  	encodingapi "go.uber.org/yarpc/api/encoding"
    30  	"go.uber.org/yarpc/api/transport"
    31  	"go.uber.org/yarpc/pkg/encoding"
    32  	"go.uber.org/yarpc/pkg/errors"
    33  )
    34  
    35  // Client makes JSON requests to a single service.
    36  type Client interface {
    37  	// Call performs an outbound JSON request.
    38  	//
    39  	// resBodyOut is a pointer to a value that can be filled with
    40  	// json.Unmarshal.
    41  	//
    42  	// Returns the response or an error if the request failed.
    43  	Call(ctx context.Context, procedure string, reqBody interface{}, resBodyOut interface{}, opts ...yarpc.CallOption) error
    44  	CallOneway(ctx context.Context, procedure string, reqBody interface{}, opts ...yarpc.CallOption) (transport.Ack, error)
    45  }
    46  
    47  // New builds a new JSON client.
    48  func New(c transport.ClientConfig) Client {
    49  	return jsonClient{cc: c}
    50  }
    51  
    52  func init() {
    53  	yarpc.RegisterClientBuilder(New)
    54  }
    55  
    56  type jsonClient struct {
    57  	cc transport.ClientConfig
    58  }
    59  
    60  func (c jsonClient) Call(ctx context.Context, procedure string, reqBody interface{}, resBodyOut interface{}, opts ...yarpc.CallOption) error {
    61  	call := encodingapi.NewOutboundCall(encoding.FromOptions(opts)...)
    62  	treq := transport.Request{
    63  		Caller:    c.cc.Caller(),
    64  		Service:   c.cc.Service(),
    65  		Procedure: procedure,
    66  		Encoding:  Encoding,
    67  	}
    68  
    69  	ctx, err := call.WriteToRequest(ctx, &treq)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	encoded, err := json.Marshal(reqBody)
    75  	if err != nil {
    76  		return errors.RequestBodyEncodeError(&treq, err)
    77  	}
    78  
    79  	treq.Body = bytes.NewReader(encoded)
    80  	treq.BodySize = len(encoded)
    81  
    82  	tres, appErr := c.cc.GetUnaryOutbound().Call(ctx, &treq)
    83  	if tres == nil {
    84  		return appErr
    85  	}
    86  
    87  	// we want to return the appErr if it exists as this is what
    88  	// the previous behavior was so we deprioritize this error
    89  	var decodeErr error
    90  	if _, err = call.ReadFromResponse(ctx, tres); err != nil {
    91  		decodeErr = err
    92  	}
    93  	if tres.Body != nil {
    94  		if err := json.NewDecoder(tres.Body).Decode(resBodyOut); err != nil && decodeErr == nil {
    95  			decodeErr = errors.ResponseBodyDecodeError(&treq, err)
    96  		}
    97  		if err := tres.Body.Close(); err != nil && decodeErr == nil {
    98  			decodeErr = err
    99  		}
   100  	}
   101  
   102  	if appErr != nil {
   103  		return appErr
   104  	}
   105  	return decodeErr
   106  }
   107  
   108  func (c jsonClient) CallOneway(ctx context.Context, procedure string, reqBody interface{}, opts ...yarpc.CallOption) (transport.Ack, error) {
   109  	call := encodingapi.NewOutboundCall(encoding.FromOptions(opts)...)
   110  	treq := transport.Request{
   111  		Caller:    c.cc.Caller(),
   112  		Service:   c.cc.Service(),
   113  		Procedure: procedure,
   114  		Encoding:  Encoding,
   115  	}
   116  
   117  	ctx, err := call.WriteToRequest(ctx, &treq)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	var buff bytes.Buffer
   123  	if err := json.NewEncoder(&buff).Encode(reqBody); err != nil {
   124  		return nil, errors.RequestBodyEncodeError(&treq, err)
   125  	}
   126  	treq.Body = &buff
   127  
   128  	return c.cc.GetOnewayOutbound().CallOneway(ctx, &treq)
   129  }