// TestErrorResponse tests that errors are serialised and returned to callers appropriately (as we are using the // transport directly here, we actually get a response containing an error, not a transport error) func (suite *serverSuite) TestErrorResponse() { srv := suite.server srv.AddEndpoints(Endpoint{ Name: "err", Request: new(testproto.DummyRequest), Response: new(testproto.DummyResponse), Handler: func(req mercury.Request) (mercury.Response, error) { request := req.Body().(*testproto.DummyRequest) return nil, terrors.NotFound("", request.Ping, nil) }}) req := mercury.NewRequest() req.SetService(testServiceName) req.SetEndpoint("err") req.SetBody(&testproto.DummyRequest{ Ping: "Foo bar baz"}) suite.Assert().NoError(tmsg.JSONMarshaler().MarshalBody(req)) rsp_, err := suite.trans.Send(req, time.Second) suite.Assert().NoError(err) suite.Assert().NotNil(rsp_) rsp := mercury.FromTyphonResponse(rsp_) suite.Assert().True(rsp.IsError()) errResponse := &pe.Error{} suite.Assert().NoError(json.Unmarshal(rsp.Payload(), errResponse)) terr := terrors.Unmarshal(errResponse) suite.Require().NotNil(terr) suite.Assert().Equal("Foo bar baz", terr.Message, string(rsp.Payload())) suite.Assert().Equal(terrors.ErrNotFound, terr.Code) }
// TestEndpointNotFound tests that a Bad Request error is correctly returned on receiving an event for an unknown // endpoing func (suite *serverSuite) TestEndpointNotFound() { req := mercury.NewRequest() req.SetService(testServiceName) req.SetEndpoint("dummy") req.SetBody(&testproto.DummyRequest{ Ping: "routing"}) suite.Assert().NoError(tmsg.JSONMarshaler().MarshalBody(req)) rsp_, err := suite.trans.Send(req, time.Second) rsp := mercury.FromTyphonResponse(rsp_) suite.Require().NoError(err) suite.Require().NotNil(rsp) suite.Assert().True(rsp.IsError()) suite.Assert().NoError(tmsg.JSONUnmarshaler(new(pe.Error)).UnmarshalPayload(rsp)) suite.Assert().IsType(new(pe.Error), rsp.Body()) terr := terrors.Unmarshal(rsp.Body().(*pe.Error)) suite.Assert().Equal(terrors.ErrBadRequest+".endpoint_not_found", terr.Code) suite.Assert().Contains(terr.Error(), "Endpoint not found") }
// TestErrors verifies that an error sent from a handler is correctly returned by a client func (suite *clientServerSuite) TestErrors() { suite.server.AddEndpoints(server.Endpoint{ Name: "error", Request: new(testproto.DummyRequest), Response: new(testproto.DummyResponse), Handler: func(req mercury.Request) (mercury.Response, error) { return nil, terrors.BadRequest("", "naughty naughty", nil) }}) cl := client.NewClient(). SetMiddleware(DefaultClientMiddleware()). Add( client.Call{ Uid: "call", Service: testServiceName, Endpoint: "error", Body: &testproto.DummyRequest{}, Response: &testproto.DummyResponse{}, }). SetTransport(suite.trans). SetTimeout(time.Second). Execute() suite.Assert().True(cl.Errors().Any()) err := cl.Errors().ForUid("call") suite.Require().NotNil(err) suite.Assert().Equal(terrors.ErrBadRequest, err.Code) rsp := mercury.FromTyphonResponse(cl.Response("call").Copy()) rsp.SetBody("FOO") // Deliberately set this to verify it is not mutated while accessing the error suite.Require().NotNil(rsp) suite.Assert().True(rsp.IsError()) suite.Assert().NotNil(rsp.Error()) suite.Assert().IsType(&terrors.Error{}, rsp.Error()) err = rsp.Error().(*terrors.Error) suite.Assert().Equal(terrors.ErrBadRequest, err.Code) suite.Assert().Equal("FOO", rsp.Body()) }
// performCall executes a single Call, unmarshals the response (if there is a response proto), and pushes the updted // clientCall down the response channel func (c *client) performCall(call clientCall, middleware []ClientMiddleware, trans transport.Transport, timeout time.Duration, completion chan<- clientCall) { req := call.req // Ensure we have a request ID before the request middleware is executed if id := req.Id(); id == "" { _uuid, err := uuid.NewV4() if err != nil { log.Errorf("[Mercury:Client] Failed to generate request uuid: %v", err) call.err = terrors.Wrap(err, nil).(*terrors.Error) completion <- call return } req.SetId(_uuid.String()) } // Apply request middleware for _, md := range middleware { req = md.ProcessClientRequest(req) } log.Debugf("[Mercury:Client] Sending request to %s/%s…", req.Service(), req.Endpoint()) rsp_, err := trans.Send(req, timeout) if err != nil { call.err = terrors.Wrap(err, nil).(*terrors.Error) } else if rsp_ != nil { rsp := mercury.FromTyphonResponse(rsp_) // Servers set header Content-Error: 1 when sending errors. For those requests, unmarshal the error, leaving the // call's response nil if rsp.IsError() { errRsp := rsp.Copy() if unmarshalErr := c.unmarshaler(rsp, &tperrors.Error{}).UnmarshalPayload(errRsp); unmarshalErr != nil { call.err = terrors.WrapWithCode(unmarshalErr, nil, terrors.ErrBadResponse).(*terrors.Error) } else { err := errRsp.Body().(*tperrors.Error) call.err = terrors.Unmarshal(err) } // Set the response Body to a nil – but typed – interface to avoid type conversion panics if Body // properties are accessed in spite of the error // Relevant: http://golang.org/doc/faq#nil_error if call.rspProto != nil { bodyT := reflect.TypeOf(call.rspProto) rsp.SetBody(reflect.New(bodyT.Elem()).Interface()) } } else if call.rspProto != nil { rsp.SetBody(call.rspProto) if err := c.unmarshaler(rsp, call.rspProto).UnmarshalPayload(rsp); err != nil { call.err = terrors.WrapWithCode(err, nil, terrors.ErrBadResponse).(*terrors.Error) } } call.rsp = rsp } // Apply response/error middleware (in reverse order) for i := len(middleware) - 1; i >= 0; i-- { mw := middleware[i] if call.err != nil { mw.ProcessClientError(call.err, call.req) } else { call.rsp = mw.ProcessClientResponse(call.rsp, call.req) } } completion <- call }