示例#1
0
// 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)
}
示例#2
0
// 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")
}
示例#3
0
// 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())
}
示例#4
0
// 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
}