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