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 }