go.uber.org/yarpc@v1.72.1/encoding/protobuf/error_integration_test.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 protobuf_test
    22  
    23  import (
    24  	"context"
    25  	"net"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/gogo/googleapis/google/rpc"
    30  	"github.com/gogo/protobuf/proto"
    31  	"github.com/gogo/protobuf/types"
    32  	"github.com/gogo/status"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  	"go.uber.org/yarpc"
    36  	"go.uber.org/yarpc/api/transport"
    37  	"go.uber.org/yarpc/encoding/json"
    38  	"go.uber.org/yarpc/encoding/protobuf"
    39  	"go.uber.org/yarpc/encoding/protobuf/internal/testpb"
    40  	"go.uber.org/yarpc/encoding/raw"
    41  	"go.uber.org/yarpc/transport/grpc"
    42  	"go.uber.org/yarpc/yarpcerrors"
    43  )
    44  
    45  type errorServer struct{}
    46  
    47  func (errorServer) Unary(ctx context.Context, msg *testpb.TestMessage) (*testpb.TestMessage, error) {
    48  	testDetails := []proto.Message{
    49  		&types.StringValue{Value: "string value"},
    50  		&types.Int32Value{Value: 100},
    51  	}
    52  	return nil,
    53  		protobuf.NewError(yarpcerrors.CodeInvalidArgument, msg.Value,
    54  			protobuf.WithErrorDetails(testDetails...))
    55  }
    56  
    57  func (errorServer) Duplex(stream testpb.TestServiceDuplexYARPCServer) error {
    58  	testDetails := []proto.Message{
    59  		&types.StringValue{Value: "string value"},
    60  		&types.Int32Value{Value: 100},
    61  	}
    62  	msg, err := stream.Recv()
    63  	if err != nil {
    64  		return err
    65  	}
    66  	return protobuf.NewError(yarpcerrors.CodeInvalidArgument, msg.Value,
    67  		protobuf.WithErrorDetails(testDetails...))
    68  }
    69  
    70  func TestProtoGrpcServerErrorDetails(t *testing.T) {
    71  	listener, err := net.Listen("tcp", "127.0.0.1:0")
    72  	require.NoError(t, err)
    73  
    74  	inbound := grpc.NewTransport().NewInbound(listener)
    75  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
    76  		Name:     _serverName,
    77  		Inbounds: yarpc.Inbounds{inbound},
    78  		Logging:  yarpc.LoggingConfig{},
    79  		Metrics:  yarpc.MetricsConfig{},
    80  	})
    81  
    82  	dispatcher.Register(testpb.BuildTestYARPCProcedures(&errorServer{}))
    83  	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")
    84  
    85  	addr := inbound.Addr().String()
    86  	outbound := grpc.NewTransport().NewSingleOutbound(addr)
    87  	clientDispatcher := yarpc.NewDispatcher(yarpc.Config{
    88  		Name: _clientName,
    89  		Outbounds: map[string]transport.Outbounds{
    90  			_serverName: {
    91  				ServiceName: _serverName,
    92  				Unary:       outbound,
    93  				Stream:      outbound,
    94  			},
    95  		},
    96  		Logging: yarpc.LoggingConfig{},
    97  		Metrics: yarpc.MetricsConfig{},
    98  	})
    99  
   100  	client := testpb.NewTestYARPCClient(clientDispatcher.ClientConfig(_serverName))
   101  	require.NoError(t, clientDispatcher.Start(), "could not start client dispatcher")
   102  
   103  	defer func() {
   104  		assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher")
   105  		assert.NoError(t, clientDispatcher.Stop(), "could not stop client dispatcher")
   106  	}()
   107  
   108  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   109  	defer cancel()
   110  
   111  	const errorMsg = "error msg"
   112  
   113  	_, err = client.Unary(ctx, &testpb.TestMessage{Value: errorMsg})
   114  	assert.NotNil(t, err, "unexpected nil error")
   115  	st := yarpcerrors.FromError(err)
   116  	assert.Equal(t, yarpcerrors.CodeInvalidArgument, st.Code(), "unexpected error code")
   117  	assert.Equal(t, errorMsg, st.Message(), "unexpected error message")
   118  	expectedDetails := []interface{}{
   119  		&types.StringValue{Value: "string value"},
   120  		&types.Int32Value{Value: 100},
   121  	}
   122  	actualDetails := protobuf.GetErrorDetails(err)
   123  	assert.Equal(t, expectedDetails, actualDetails, "unexpected error details")
   124  }
   125  
   126  func TestProtoGrpcStreamServerErrorDetails(t *testing.T) {
   127  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   128  	require.NoError(t, err)
   129  
   130  	inbound := grpc.NewTransport().NewInbound(listener)
   131  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
   132  		Name:     _serverName,
   133  		Inbounds: yarpc.Inbounds{inbound},
   134  		Logging:  yarpc.LoggingConfig{},
   135  		Metrics:  yarpc.MetricsConfig{},
   136  	})
   137  
   138  	dispatcher.Register(testpb.BuildTestYARPCProcedures(&errorServer{}))
   139  	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")
   140  
   141  	addr := inbound.Addr().String()
   142  	outbound := grpc.NewTransport().NewSingleOutbound(addr)
   143  	clientDispatcher := yarpc.NewDispatcher(yarpc.Config{
   144  		Name: _clientName,
   145  		Outbounds: map[string]transport.Outbounds{
   146  			_serverName: {
   147  				ServiceName: _serverName,
   148  				Unary:       outbound,
   149  				Stream:      outbound,
   150  			},
   151  		},
   152  		Logging: yarpc.LoggingConfig{},
   153  		Metrics: yarpc.MetricsConfig{},
   154  	})
   155  
   156  	client := testpb.NewTestYARPCClient(clientDispatcher.ClientConfig(_serverName))
   157  	require.NoError(t, clientDispatcher.Start(), "could not start client dispatcher")
   158  
   159  	defer func() {
   160  		assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher")
   161  		assert.NoError(t, clientDispatcher.Stop(), "could not stop client dispatcher")
   162  	}()
   163  
   164  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   165  	defer cancel()
   166  
   167  	const errorMsg = "stream error msg"
   168  	expectedDetails := []interface{}{
   169  		&types.StringValue{Value: "string value"},
   170  		&types.Int32Value{Value: 100},
   171  	}
   172  
   173  	streamHandle, err := client.Duplex(ctx)
   174  	assert.NoError(t, err, "unexpected error")
   175  
   176  	err = streamHandle.Send(&testpb.TestMessage{Value: errorMsg})
   177  	assert.NoError(t, err, "unexpected error")
   178  
   179  	msg, err := streamHandle.Recv()
   180  	assert.Nil(t, msg, "unexpected non-nil reply")
   181  	assert.Error(t, err, "unexpected nil error")
   182  
   183  	st := yarpcerrors.FromError(err)
   184  	assert.Equal(t, yarpcerrors.CodeInvalidArgument, st.Code(), "unexpected error code")
   185  	assert.Equal(t, errorMsg, st.Message(), "unexpected error message")
   186  
   187  	actualDetails := protobuf.GetErrorDetails(err)
   188  	assert.Equal(t, expectedDetails, actualDetails, "unexpected error details")
   189  }
   190  
   191  type errorRawServer struct{}
   192  
   193  func (errorRawServer) Handle(ctx context.Context, req *transport.Request, resw transport.ResponseWriter) error {
   194  	testDetails := []proto.Message{
   195  		&types.StringValue{Value: "string value"},
   196  		&types.Int32Value{Value: 100},
   197  	}
   198  	return protobuf.NewError(yarpcerrors.CodeInvalidArgument, "error message",
   199  		protobuf.WithErrorDetails(testDetails...))
   200  }
   201  
   202  func TestRawGrpcServerErrorDetails(t *testing.T) {
   203  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   204  	require.NoError(t, err)
   205  
   206  	inbound := grpc.NewTransport().NewInbound(listener)
   207  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
   208  		Name:     _serverName,
   209  		Inbounds: yarpc.Inbounds{inbound},
   210  		Logging:  yarpc.LoggingConfig{},
   211  		Metrics:  yarpc.MetricsConfig{},
   212  	})
   213  
   214  	dispatcher.Register([]transport.Procedure{{
   215  		Name:        "test::unary",
   216  		HandlerSpec: transport.NewUnaryHandlerSpec(&errorRawServer{}),
   217  		Encoding:    "raw",
   218  	}})
   219  	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")
   220  
   221  	addr := inbound.Addr().String()
   222  	outbound := grpc.NewTransport().NewSingleOutbound(addr)
   223  	clientDispatcher := yarpc.NewDispatcher(yarpc.Config{
   224  		Name: _clientName,
   225  		Outbounds: map[string]transport.Outbounds{
   226  			_serverName: {
   227  				ServiceName: _serverName,
   228  				Unary:       outbound,
   229  				Stream:      outbound,
   230  			},
   231  		},
   232  		Logging: yarpc.LoggingConfig{},
   233  		Metrics: yarpc.MetricsConfig{},
   234  	})
   235  
   236  	client := raw.New(clientDispatcher.ClientConfig(_serverName))
   237  	require.NoError(t, clientDispatcher.Start(), "could not start client dispatcher")
   238  
   239  	defer func() {
   240  		assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher")
   241  		assert.NoError(t, clientDispatcher.Stop(), "could not stop client dispatcher")
   242  	}()
   243  
   244  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   245  	defer cancel()
   246  
   247  	_, err = client.Call(ctx, "test::unary", nil)
   248  	assert.NotNil(t, err, "unexpected nil error")
   249  	yarpcStatus := yarpcerrors.FromError(err)
   250  	assert.Equal(t, yarpcerrors.CodeInvalidArgument, yarpcStatus.Code(), "unexpected error code")
   251  	assert.Equal(t, "error message", yarpcStatus.Message(), "unexpected error message")
   252  
   253  	var rpcStatus rpc.Status
   254  	proto.Unmarshal(yarpcStatus.Details(), &rpcStatus)
   255  	status := status.FromProto(&rpcStatus)
   256  	expectedDetails := []interface{}{
   257  		&types.StringValue{Value: "string value"},
   258  		&types.Int32Value{Value: 100},
   259  	}
   260  	assert.Equal(t, expectedDetails, status.Details(), "unexpected error details")
   261  }
   262  
   263  func TestJSONGrpcServerErrorDetails(t *testing.T) {
   264  	listener, err := net.Listen("tcp", "127.0.0.1:0")
   265  	require.NoError(t, err)
   266  
   267  	inbound := grpc.NewTransport().NewInbound(listener)
   268  	dispatcher := yarpc.NewDispatcher(yarpc.Config{
   269  		Name:     _serverName,
   270  		Inbounds: yarpc.Inbounds{inbound},
   271  		Logging:  yarpc.LoggingConfig{},
   272  		Metrics:  yarpc.MetricsConfig{},
   273  	})
   274  
   275  	dispatcher.Register(json.Procedure("test", func(ctx context.Context, req *struct{}) (*struct{}, error) {
   276  		testDetails := []proto.Message{
   277  			&types.StringValue{Value: "string value"},
   278  			&types.Int32Value{Value: 100},
   279  		}
   280  		return nil, protobuf.NewError(yarpcerrors.CodeInvalidArgument, "error message",
   281  			protobuf.WithErrorDetails(testDetails...))
   282  	}))
   283  	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")
   284  
   285  	addr := inbound.Addr().String()
   286  	outbound := grpc.NewTransport().NewSingleOutbound(addr)
   287  	clientDispatcher := yarpc.NewDispatcher(yarpc.Config{
   288  		Name: _clientName,
   289  		Outbounds: map[string]transport.Outbounds{
   290  			_serverName: {
   291  				ServiceName: _serverName,
   292  				Unary:       outbound,
   293  				Stream:      outbound,
   294  			},
   295  		},
   296  		Logging: yarpc.LoggingConfig{},
   297  		Metrics: yarpc.MetricsConfig{},
   298  	})
   299  
   300  	client := json.New(clientDispatcher.ClientConfig(_serverName))
   301  	require.NoError(t, clientDispatcher.Start(), "could not start client dispatcher")
   302  
   303  	defer func() {
   304  		assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher")
   305  		assert.NoError(t, clientDispatcher.Stop(), "could not stop client dispatcher")
   306  	}()
   307  
   308  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   309  	defer cancel()
   310  
   311  	err = client.Call(ctx, "test", nil, nil)
   312  	assert.NotNil(t, err, "unexpected nil error")
   313  	yarpcStatus := yarpcerrors.FromError(err)
   314  	assert.Equal(t, yarpcerrors.CodeInvalidArgument, yarpcStatus.Code(), "unexpected error code")
   315  	assert.Equal(t, "error message", yarpcStatus.Message(), "unexpected error message")
   316  
   317  	var rpcStatus rpc.Status
   318  	proto.Unmarshal(yarpcStatus.Details(), &rpcStatus)
   319  	status := status.FromProto(&rpcStatus)
   320  	expectedDetails := []interface{}{
   321  		&types.StringValue{Value: "string value"},
   322  		&types.Int32Value{Value: 100},
   323  	}
   324  	assert.Equal(t, expectedDetails, status.Details(), "unexpected error details")
   325  
   326  }