diff --git a/examples/cqrs/internal/products/api/products_controller.go b/examples/cqrs/internal/products/api/products_controller.go index 43981d5..8f03a2b 100644 --- a/examples/cqrs/internal/products/api/products_controller.go +++ b/examples/cqrs/internal/products/api/products_controller.go @@ -43,7 +43,7 @@ func (pc *ProductsController) createProduct() echo.HandlerFunc { } command := creatingProduct.NewCreateProductCommand(request.Name, request.Description, request.Price) - result, err := mediatr.Send[*creatingProductsDtos.CreateProductCommandResponse](ctx.Request().Context(), command) + result, err := mediatr.Send[*creatingProduct.CreateProductCommand, *creatingProductsDtos.CreateProductCommandResponse](ctx.Request().Context(), command) if err != nil { return err @@ -76,7 +76,7 @@ func (pc *ProductsController) getProductByID() echo.HandlerFunc { return err } - queryResult, err := mediatr.Send[*gettingProductByIdDtos.GetProductByIdQueryResponse](ctx.Request().Context(), query) + queryResult, err := mediatr.Send[*gettingProductById.GetProductByIdQuery, *gettingProductByIdDtos.GetProductByIdQueryResponse](ctx.Request().Context(), query) if err != nil { return err diff --git a/go.mod b/go.mod index 0df24fd..5ca9094 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/spec v0.20.4 // indirect @@ -27,6 +28,9 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.11 // indirect github.com/mattn/go-isatty v0.0.14 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.4.0 // indirect + github.com/stretchr/testify v1.8.0 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect @@ -37,4 +41,5 @@ require ( golang.org/x/tools v0.1.10 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7f95340..bc688ea 100644 --- a/go.sum +++ b/go.sum @@ -71,10 +71,15 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/swaggo/echo-swagger v1.3.3 h1:Fx8kQ8IcIIEL3ZE20wzvcT8gFnPo/4U+fsnS3I1wvCw= github.com/swaggo/echo-swagger v1.3.3/go.mod h1:vbKcEBeJgOexLuPcsdZhrRAV508fsE79xaKIqmvse98= github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= @@ -156,3 +161,5 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/mediatr.go b/mediatr.go index d1bb706..842b378 100644 --- a/mediatr.go +++ b/mediatr.go @@ -2,7 +2,7 @@ package mediatr import ( "context" - "fmt" + "github.com/pkg/errors" "reflect" ) @@ -14,14 +14,15 @@ var requestHandlersRegistrations = map[reflect.Type]interface{}{} type Unit struct{} -// RegisterRequestHandler register the handler to mediatr registry. +// RegisterRequestHandler register the request handler to mediatr registry. func RegisterRequestHandler[TRequest any, TResponse any](h RequestHandler[TRequest, TResponse]) error { var request TRequest requestType := reflect.TypeOf(request) _, exist := requestHandlersRegistrations[requestType] if exist { - return fmt.Errorf("registerd handler already registered for message %T", requestType) + // each request in request/response strategy should have just one handler + return errors.Errorf("registered handler already exists in the registry for message %s", requestType.String()) } requestHandlersRegistrations[requestType] = h @@ -35,23 +36,23 @@ func RegisterRequestBehavior(b interface{}) error { } // Send the request to its corresponding handler. -func Send[TResponse any, TRequest any](ctx context.Context, request TRequest) (TResponse, error) { +func Send[TRequest any, TResponse any](ctx context.Context, request TRequest) (TResponse, error) { requestType := reflect.TypeOf(request) handler, ok := requestHandlersRegistrations[requestType] if !ok { - return *new(TResponse), fmt.Errorf("no handlers for command %T", request) + return *new(TResponse), errors.Errorf("no handlers for command %T", request) } handlerValue, ok := handler.(RequestHandler[TRequest, TResponse]) if !ok { - return *new(TResponse), fmt.Errorf("handler for command %T is not a Handler", request) + return *new(TResponse), errors.Errorf("handler for command %T is not a Handler", request) } response, err := handlerValue.Handle(ctx, request) if err != nil { - return *new(TResponse), err + return *new(TResponse), errors.Wrap(err, "error handling request") } return response, nil diff --git a/mediatr_test.go b/mediatr_test.go new file mode 100644 index 0000000..223c3a4 --- /dev/null +++ b/mediatr_test.go @@ -0,0 +1,93 @@ +package mediatr + +import ( + "context" + "fmt" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "testing" +) + +func Test_RegisterRequestHandler_Should_Return_Error_If_Handler_Already_Registered(t *testing.T) { + expectedErr := fmt.Sprintf("registered handler already exists in the registry for message %s", "*mediatr.RequestTest") + handler1 := &RequestTestHandler{} + handler2 := &RequestTestHandler{} + err1 := RegisterRequestHandler[*RequestTest, *ResponseTest](handler1) + err2 := RegisterRequestHandler[*RequestTest, *ResponseTest](handler2) + + assert.Nil(t, err1) + assert.Containsf(t, err2.Error(), expectedErr, "expected error containing %q, got %s", expectedErr, err2) +} + +func Test_RegisterRequestHandler_Should_Register_All_Handlers(t *testing.T) { + handler1 := &RequestTestHandler{} + handler2 := &RequestTestHandler2{} + err1 := RegisterRequestHandler[*RequestTest, *ResponseTest](handler1) + err2 := RegisterRequestHandler[*RequestTest2, *ResponseTest2](handler2) + + assert.Nil(t, err1) + assert.Nil(t, err2) +} + +func Test_Send_Should_Throw_Error_If_No_Handler_Registered(t *testing.T) { + expectedErr := fmt.Sprintf("no handlers for command %T", &RequestTest{}) + _, err := Send[*RequestTest, *ResponseTest](context.Background(), &RequestTest{Data: "test"}) + assert.Containsf(t, err.Error(), expectedErr, "expected error containing %q, got %s", expectedErr, err) +} + +func Test_Send_Should_Return_Error_If_Handler_Returns_Error(t *testing.T) { + expectedErr := "error handling request" + handler3 := &RequestTestHandler3{} + _ = RegisterRequestHandler[*RequestTest2, *ResponseTest2](handler3) + _, err := Send[*RequestTest2, *ResponseTest2](context.Background(), &RequestTest2{Data: "test"}) + assert.Containsf(t, err.Error(), expectedErr, "expected error containing %q, got %s", expectedErr, err) +} + +func Test_Send_Should_Return_Response_If_Handler_Returns_Success(t *testing.T) { + handler := &RequestTestHandler{} + _ = RegisterRequestHandler[*RequestTest, *ResponseTest](handler) + response, err := Send[*RequestTest, *ResponseTest](context.Background(), &RequestTest{Data: "test"}) + assert.Nil(t, err) + assert.IsType(t, &ResponseTest{}, response) + assert.Equal(t, "test", response.Data) +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +type RequestTest struct { + Data string +} + +type ResponseTest struct { + Data string +} + +type RequestTestHandler struct { +} + +func (c *RequestTestHandler) Handle(ctx context.Context, request *RequestTest) (*ResponseTest, error) { + return &ResponseTest{Data: request.Data}, nil +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +type RequestTest2 struct { + Data string +} + +type ResponseTest2 struct { + Data string +} + +type RequestTestHandler2 struct { +} + +func (c *RequestTestHandler2) Handle(ctx context.Context, request *RequestTest2) (*ResponseTest2, error) { + return &ResponseTest2{Data: request.Data}, nil +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +type RequestTestHandler3 struct { +} + +func (c *RequestTestHandler3) Handle(ctx context.Context, request *RequestTest2) (*ResponseTest2, error) { + return nil, errors.New("some error") +} diff --git a/readme.md b/readme.md index 13e909a..c916dd3 100644 --- a/readme.md +++ b/readme.md @@ -163,7 +163,7 @@ command := &CreateProductCommand{ CreatedAt: time.Now(), } -mediatr.Send[*creatingProductsDtos.CreateProductCommandResponse](ctx, command) +mediatr.Send[*CreateProductCommand, *creatingProductsDtos.CreateProductCommandResponse](ctx, command) ``` ```go @@ -172,7 +172,7 @@ query := &GetProdctByIdQuery{ ProductID: uuid.NewV4() } -mediatr.Send[*gettingProductsDtos.GetProdctByIdQueryResponse](ctx, query) +mediatr.Send[*GetProdctByIdQuery, *gettingProductsDtos.GetProdctByIdQueryResponse](ctx, query) ```