Skip to content

Commit

Permalink
feat: use generics in handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristophBe committed Dec 23, 2023
1 parent 18d5e80 commit 6112c96
Show file tree
Hide file tree
Showing 19 changed files with 272 additions and 209 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/ChristophBe/grud

go 1.15
go 1.18
9 changes: 5 additions & 4 deletions handlers/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"net/http"
)

// NewCreatHandler creates a http.Handler that handles the creation of a model
func NewCreatHandler(service types.CreateService, responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc {
// NewCreateHandler creates a http.Handler that handles the creation of a model
func NewCreateHandler[Model any](service types.CreateService[Model], responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
ctx := request.Context()
dto, err := service.ParseDtoFromRequest(request)
Expand All @@ -20,13 +20,14 @@ func NewCreatHandler(service types.CreateService, responseWriter types.ResponseW
return
}

model, err := dto.AssignToModel(ctx, service.CreateEmptyModel(ctx))
var model Model
model, err = dto.AssignToModel(ctx, model)
if err != nil {
errorWriter(err, writer, request)
return
}

if model, err = model.Create(ctx); err != nil {
if model, err = service.CreateModel(ctx, model); err != nil {
errorWriter(err, writer, request)
return
}
Expand Down
84 changes: 44 additions & 40 deletions handlers/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import (
"testing"
)

type testModel struct {
Value string
}
type parseDtoFromRequestServiceMock struct {
dto types.Dto
dto types.Dto[testModel]
err error
}

func (p parseDtoFromRequestServiceMock) ParseDtoFromRequest(_ *http.Request) (types.Dto, error) {
func (p parseDtoFromRequestServiceMock) ParseDtoFromRequest(_ *http.Request) (types.Dto[testModel], error) {
return p.dto, p.err
}

Expand All @@ -26,21 +29,34 @@ func (c createEmptyModelServiceMock) CreateEmptyModel(_ context.Context) types.M
return c.emptyModel
}

type createModelServiceMock struct {
createdModel testModel
err error
}

func (c createModelServiceMock) CreateModel(_ context.Context, _ testModel) (testModel, error) {
return c.createdModel, c.err
}

type createServiceMock struct {
createEmptyModelServiceMock
createModelServiceMock
parseDtoFromRequestServiceMock
}

func TestCrudHandlersImpl_Create(t *testing.T) {

expectedResponseStatus := http.StatusAccepted

validModel := testModel{
Value: "value",
}

tt := []struct {
name string
service types.CreateService
service types.CreateService[testModel]
responseWriterError error
expectedError error
resultModel modelMock
resultModel testModel
}{
{
name: "parse dto form request turns error",
Expand All @@ -55,7 +71,7 @@ func TestCrudHandlersImpl_Create(t *testing.T) {
name: "dto is invalid",
service: createServiceMock{
parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{
dto: dtoMock{
dto: dtoMock[testModel]{
validationError: errors.New("test"),
},
},
Expand All @@ -66,8 +82,8 @@ func TestCrudHandlersImpl_Create(t *testing.T) {
name: "dto assign to model failed",
service: createServiceMock{
parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{
dto: dtoMock{
assignModelResult: modelErrorHolder{
dto: dtoMock[testModel]{
assignModelResult: modelErrorHolder[testModel]{
err: errors.New("test"),
},
},
Expand All @@ -79,32 +95,25 @@ func TestCrudHandlersImpl_Create(t *testing.T) {
name: "model save model failed",
service: createServiceMock{
parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{
dto: dtoMock{
assignModelResult: modelErrorHolder{
model: modelMock{
createResult: modelErrorHolder{
err: errors.New("test"),
},
},
dto: dtoMock[testModel]{
assignModelResult: modelErrorHolder[testModel]{
model: testModel{Value: "test"},
},
},
},
createModelServiceMock: createModelServiceMock{
err: errors.New("test"),
},
},
expectedError: errors.New("test"),
},
{
name: "response writer returns error",
service: createServiceMock{
parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{
dto: dtoMock{
assignModelResult: modelErrorHolder{
model: modelMock{
createResult: modelErrorHolder{
model: modelMock{
value: "test-value",
},
},
},
dto: dtoMock[testModel]{
assignModelResult: modelErrorHolder[testModel]{
model: validModel,
},
},
},
Expand All @@ -116,23 +125,18 @@ func TestCrudHandlersImpl_Create(t *testing.T) {
name: "success",
service: createServiceMock{
parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{
dto: dtoMock{
assignModelResult: modelErrorHolder{
model: modelMock{
createResult: modelErrorHolder{
model: modelMock{
value: "test-value",
},
},
},
dto: dtoMock[testModel]{
assignModelResult: modelErrorHolder[testModel]{
model: validModel,
},
},
},
createModelServiceMock: createModelServiceMock{
createdModel: validModel,
},
},
expectedError: nil,
resultModel: modelMock{
value: "test-value",
},
resultModel: validModel,
},
}

Expand All @@ -146,7 +150,7 @@ func TestCrudHandlersImpl_Create(t *testing.T) {

errorWriter := newMockErrorWriter(errorRecorder)

handler := NewCreatHandler(tc.service, responseWriter, errorWriter)
handler := NewCreateHandler[testModel](tc.service, responseWriter, errorWriter)
w := httptest.ResponseRecorder{}
handler.ServeHTTP(&w, new(http.Request))

Expand All @@ -168,13 +172,13 @@ func TestCrudHandlersImpl_Create(t *testing.T) {
if responseRecorder.status != expectedResponseStatus {
t.Errorf("expected response status to be %v, got %v", expectedResponseStatus, responseRecorder.status)
}
result, ok := responseRecorder.body.(modelMock)
result, ok := responseRecorder.body.(testModel)
if !ok {
t.Fatal("failed to cast model")
}

if tc.resultModel.value != result.value {
t.Errorf("expected result value to be %v, got %v", tc.resultModel.value, result.value)
if tc.resultModel.Value != result.Value {
t.Errorf("expected result value to be %v, got %v", tc.resultModel.Value, result.Value)
}

} else {
Expand Down
32 changes: 16 additions & 16 deletions handlers/crud-handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,46 +16,46 @@ type CrudHandlers interface {
}

// NewCrudHandlers creates a instance of CrudHandlers.
func NewCrudHandlers(service types.Service, responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) CrudHandlers {
return crudHandlersImpl{
func NewCrudHandlers[M types.ModelTypeInterface](service types.Service[M], responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) CrudHandlers {
return crudHandlersImpl[M]{
service: service,
responseWriter: responseWriter,
errorWriter: errorWriter,
}
}

type crudHandlersImpl struct {
service types.Service
type crudHandlersImpl[M types.ModelTypeInterface] struct {
service types.Service[M]
responseWriter types.ResponseWriter
errorWriter types.ErrorResponseWriter
}

// Create is a http.Handler that handles the creation of a model
func (c crudHandlersImpl) Create(writer http.ResponseWriter, request *http.Request) {
NewCreatHandler(c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
func (c crudHandlersImpl[M]) Create(writer http.ResponseWriter, request *http.Request) {
NewCreateHandler[M](c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
}

// GetAll is a http.Handler for fetch a list of model.
func (c crudHandlersImpl) GetAll(writer http.ResponseWriter, request *http.Request) {
NewGetAllHandler(c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
func (c crudHandlersImpl[M]) GetAll(writer http.ResponseWriter, request *http.Request) {
NewGetAllHandler[M](c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
}

// GetOne returns a http handler for handling requests one specific model.
func (c crudHandlersImpl) GetOne(w http.ResponseWriter, r *http.Request) {
NewGetOneHandler(c.service, c.responseWriter, c.errorWriter)(w, r)
func (c crudHandlersImpl[M]) GetOne(w http.ResponseWriter, r *http.Request) {
NewGetOneHandler[M](c.service, c.responseWriter, c.errorWriter)(w, r)
}

// Update is a http.Handler that handles partial updates for existing models.
func (c crudHandlersImpl) Update(writer http.ResponseWriter, request *http.Request) {
NewUpdateHandler(c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
func (c crudHandlersImpl[M]) Update(writer http.ResponseWriter, request *http.Request) {
NewUpdateHandler[M](c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
}

// Replace is a http.Handler that handles replacing an exing model.
func (c crudHandlersImpl) Replace(writer http.ResponseWriter, request *http.Request) {
NewReplaceHandler(c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
func (c crudHandlersImpl[M]) Replace(writer http.ResponseWriter, request *http.Request) {
NewReplaceHandler[M](c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
}

// Delete is a http handler for handling the deletion of specific model.
func (c crudHandlersImpl) Delete(writer http.ResponseWriter, request *http.Request) {
NewDeleteHandler(c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
func (c crudHandlersImpl[M]) Delete(writer http.ResponseWriter, request *http.Request) {
NewDeleteHandler[M](c.service, c.responseWriter, c.errorWriter).ServeHTTP(writer, request)
}
4 changes: 2 additions & 2 deletions handlers/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import (
)

// NewDeleteHandler returns a http handler for that handles the deletion of one specific model.
func NewDeleteHandler(service types.GetOneService, responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc {
func NewDeleteHandler[M types.ModelTypeInterface](service types.DeleteService[M], responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
model, err := service.GetOne(request)
if err != nil {
errorWriter(err, writer, request)
return
}

if err = model.Delete(request.Context()); err != nil {
if err = service.DeleteModel(request.Context(), model); err != nil {
errorWriter(err, writer, request)
return
}
Expand Down
40 changes: 32 additions & 8 deletions handlers/delete_test.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,64 @@
package handlers

import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
)

type deleteMockServiceMock struct {
deletionError error
}

func (d deleteMockServiceMock) DeleteModel(_ context.Context, _ testModel) error {
return d.deletionError
}

type deleteServiceMock struct {
getOneServiceMock
deleteMockServiceMock
}

func TestCrudHandlersImpl_Delete(t *testing.T) {

expectedResponseStatus := http.StatusNoContent

tt := []struct {
name string
getOneServiceMock
deleteServiceMock
responseWriterError error
expectedError error
}{
{
name: "service returns error",
getOneServiceMock: getOneServiceMock{
err: errors.New("test"),
deleteServiceMock: deleteServiceMock{
getOneServiceMock: getOneServiceMock{
err: errors.New("test"),
},
},
expectedError: errors.New("test"),
},
{
name: "response writer returns error",
getOneServiceMock: getOneServiceMock{},
name: "response writer returns error",
deleteServiceMock: deleteServiceMock{
getOneServiceMock: getOneServiceMock{},
},
responseWriterError: errors.New("test-error"),
expectedError: errors.New("test-error"),
},
{
name: "delete function returns error",
getOneServiceMock: getOneServiceMock{
model: modelMock{deleteResult: errors.New("test-error")},
deleteServiceMock: deleteServiceMock{
getOneServiceMock: getOneServiceMock{
model: testModel{"test-value"},
},
deleteMockServiceMock: deleteMockServiceMock{
deletionError: errors.New("test-error"),
},
},

expectedError: errors.New("test-error"),
},
{
Expand All @@ -53,7 +77,7 @@ func TestCrudHandlersImpl_Delete(t *testing.T) {

errorWriter := newMockErrorWriter(errorRecorder)

handler := NewDeleteHandler(tc.getOneServiceMock, responseWriter, errorWriter)
handler := NewDeleteHandler[testModel](tc.deleteServiceMock, responseWriter, errorWriter)
w := httptest.ResponseRecorder{}
handler.ServeHTTP(&w, new(http.Request))

Expand Down
2 changes: 1 addition & 1 deletion handlers/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

// NewFunctionHandler creates a http.Handler that handles the creation of a model
func NewFunctionHandler(service types.FunctionHandlerService, responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc {
func NewFunctionHandler[Dto types.Validatable, Result any](service types.FunctionHandlerService[Dto, Result], responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
ctx := request.Context()
dto, err := service.ParseValidatableFromRequest(request)
Expand Down
Loading

0 comments on commit 6112c96

Please sign in to comment.