diff --git a/go.mod b/go.mod index be1ccac..f42ea4c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/ChristophBe/grud -go 1.15 +go 1.18 diff --git a/handlers/create.go b/handlers/create.go index b1f1da0..f19a690 100644 --- a/handlers/create.go +++ b/handlers/create.go @@ -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) @@ -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 } diff --git a/handlers/create_test.go b/handlers/create_test.go index c6d1516..a4b7b65 100644 --- a/handlers/create_test.go +++ b/handlers/create_test.go @@ -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 } @@ -26,8 +29,17 @@ 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 } @@ -35,12 +47,16 @@ 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", @@ -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"), }, }, @@ -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"), }, }, @@ -79,16 +95,15 @@ 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"), }, @@ -96,15 +111,9 @@ func TestCrudHandlersImpl_Create(t *testing.T) { 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, }, }, }, @@ -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, }, } @@ -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)) @@ -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 { diff --git a/handlers/crud-handlers.go b/handlers/crud-handlers.go index f36f285..09b64f3 100644 --- a/handlers/crud-handlers.go +++ b/handlers/crud-handlers.go @@ -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) } diff --git a/handlers/delete.go b/handlers/delete.go index 39ad335..37accaf 100644 --- a/handlers/delete.go +++ b/handlers/delete.go @@ -6,7 +6,7 @@ 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 { @@ -14,7 +14,7 @@ func NewDeleteHandler(service types.GetOneService, responseWriter types.Response return } - if err = model.Delete(request.Context()); err != nil { + if err = service.DeleteModel(request.Context(), model); err != nil { errorWriter(err, writer, request) return } diff --git a/handlers/delete_test.go b/handlers/delete_test.go index 3a3dbef..ceb6e68 100644 --- a/handlers/delete_test.go +++ b/handlers/delete_test.go @@ -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"), }, { @@ -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)) diff --git a/handlers/function.go b/handlers/function.go index 140d500..668e0b7 100644 --- a/handlers/function.go +++ b/handlers/function.go @@ -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) diff --git a/handlers/function_test.go b/handlers/function_test.go index 43a2cf9..cec02e7 100644 --- a/handlers/function_test.go +++ b/handlers/function_test.go @@ -25,7 +25,7 @@ func TestCrudHandlersImpl_Function(t *testing.T) { { name: "dto invalid", service: functionServiceMock{ - dto: dtoMock{ + dto: dtoMock[any]{ validationError: errors.New("test"), }, }, @@ -34,7 +34,7 @@ func TestCrudHandlersImpl_Function(t *testing.T) { { name: "function returns error invalid", service: functionServiceMock{ - dto: dtoMock{}, + dto: dtoMock[any]{}, functionErr: errors.New("test"), }, expectedError: errors.New("test"), @@ -42,7 +42,7 @@ func TestCrudHandlersImpl_Function(t *testing.T) { { name: "function succeeded invalid", service: functionServiceMock{ - dto: dtoMock{}, + dto: dtoMock[any]{}, responseStatus: http.StatusOK, }, expectedError: nil, @@ -50,7 +50,7 @@ func TestCrudHandlersImpl_Function(t *testing.T) { { name: "response writer returns error", service: functionServiceMock{ - dto: dtoMock{}, + dto: dtoMock[any]{}, responseStatus: http.StatusOK, }, responseWriterError: errors.New("test-error"), @@ -68,7 +68,7 @@ func TestCrudHandlersImpl_Function(t *testing.T) { errorWriter := newMockErrorWriter(errorRecorder) - handler := NewFunctionHandler(tc.service, responseWriter, errorWriter) + handler := NewFunctionHandler[dtoMock[any], any](tc.service, responseWriter, errorWriter) w := httptest.ResponseRecorder{} handler.ServeHTTP(&w, new(http.Request)) diff --git a/handlers/get-all.go b/handlers/get-all.go index 4a16e05..ca8b345 100644 --- a/handlers/get-all.go +++ b/handlers/get-all.go @@ -6,7 +6,7 @@ import ( ) // NewGetAllHandler returns a http.Handler for handling requests a list of model. -func NewGetAllHandler(service types.GetAllService, responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc { +func NewGetAllHandler[M types.ModelTypeInterface](service types.GetAllService[M], responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { models, err := service.GetAll(request) if err != nil { diff --git a/handlers/get-all_test.go b/handlers/get-all_test.go index da93f33..627bb35 100644 --- a/handlers/get-all_test.go +++ b/handlers/get-all_test.go @@ -34,17 +34,17 @@ func TestCrudHandlersImpl_GetAll(t *testing.T) { { name: "getAll returns a empty list", getAllServiceMock: getAllServiceMock{ - models: make([]types.Model, 0), + models: make([]testModel, 0), }, expectedError: nil, }, { name: "getAll returns a list of models", getAllServiceMock: getAllServiceMock{ - models: []types.Model{ - modelMock{value: "a"}, - modelMock{value: "b"}, - modelMock{value: "c"}, + models: []testModel{ + {Value: "a"}, + {Value: "b"}, + {Value: "c"}, }, }, expectedError: nil, @@ -61,7 +61,7 @@ func TestCrudHandlersImpl_GetAll(t *testing.T) { errorWriter := newMockErrorWriter(errorRecorder) - handler := NewGetAllHandler(tc.getAllServiceMock, responseWriter, errorWriter) + handler := NewGetAllHandler[testModel](tc.getAllServiceMock, responseWriter, errorWriter) w := httptest.ResponseRecorder{} handler.ServeHTTP(&w, new(http.Request)) diff --git a/handlers/get-one.go b/handlers/get-one.go index 15694be..2d2c743 100644 --- a/handlers/get-one.go +++ b/handlers/get-one.go @@ -6,7 +6,7 @@ import ( ) // NewGetOneHandler returns a http handler for handling requests one specific model. -func NewGetOneHandler(service types.GetOneService, responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc { +func NewGetOneHandler[M types.ModelTypeInterface](service types.GetOneService[M], responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { model, err := service.GetOne(r) if err != nil { diff --git a/handlers/get-one_test.go b/handlers/get-one_test.go index bf02523..512c381 100644 --- a/handlers/get-one_test.go +++ b/handlers/get-one_test.go @@ -33,7 +33,7 @@ func TestCrudHandlersImpl_GetOne(t *testing.T) { { name: "getOne returns model", getOneServiceMock: getOneServiceMock{ - model: modelMock{value: "testValue"}, + model: testModel{Value: "testValue"}, }, expectedError: nil, }, @@ -49,7 +49,7 @@ func TestCrudHandlersImpl_GetOne(t *testing.T) { errorWriter := newMockErrorWriter(errorRecorder) - handler := NewGetOneHandler(tc.getOneServiceMock, responseWriter, errorWriter) + handler := NewGetOneHandler[testModel](tc.getOneServiceMock, responseWriter, errorWriter) w := httptest.ResponseRecorder{} handler.ServeHTTP(&w, new(http.Request)) @@ -71,13 +71,13 @@ func TestCrudHandlersImpl_GetOne(t *testing.T) { if responseRecorder.status != expectedResponseStatus { t.Errorf("expected response status to be %v, got %v", expectedResponseStatus, responseRecorder.status) } - resultingModel, ok := responseRecorder.body.(modelMock) + resultingModel, ok := responseRecorder.body.(testModel) if !ok { t.Fatal("failed to cast model") } - if resultingModel.value != tc.model.value { - t.Errorf("expected model model to be %v, got %v", tc.model.value, resultingModel.value) + if resultingModel.Value != tc.model.Value { + t.Errorf("expected model model to be %v, got %v", tc.model.Value, resultingModel.Value) } } else { diff --git a/handlers/mocks_test.go b/handlers/mocks_test.go index ecf3f34..df85a4b 100644 --- a/handlers/mocks_test.go +++ b/handlers/mocks_test.go @@ -7,50 +7,56 @@ import ( ) type getOneServiceMock struct { - model modelMock + model testModel err error } -func (m getOneServiceMock) GetOne(_ *http.Request) (types.Model, error) { +func (m getOneServiceMock) GetOne(_ *http.Request) (testModel, error) { return m.model, m.err } type getAllServiceMock struct { - models []types.Model + models []testModel err error } -func (m getAllServiceMock) GetAll(_ *http.Request) ([]types.Model, error) { +func (m getAllServiceMock) GetAll(_ *http.Request) ([]testModel, error) { return m.models, m.err } type functionServiceMock struct { functionErr error dtoErr error - result interface{} + result any responseStatus int - dto types.Validatable + dto dtoMock[any] } -func (f functionServiceMock) Function(_ context.Context, _ types.Validatable) (interface{}, int, error) { +func (f functionServiceMock) Function(_ context.Context, _ dtoMock[any]) (any, int, error) { return f.result, f.responseStatus, f.functionErr } -func (f functionServiceMock) ParseValidatableFromRequest(_ *http.Request) (types.Validatable, error) { +func (f functionServiceMock) ParseValidatableFromRequest(_ *http.Request) (dtoMock[any], error) { return f.dto, f.dtoErr } -type modelErrorHolder struct { - model types.Model +type modelErrorHolder[T any] struct { + model T err error } -type dtoErrorHolder struct { - dto types.Dto - err error + +type updateModelServiceMock struct { + model testModel + err error } + +func (u updateModelServiceMock) UpdateModel(_ context.Context, _ testModel) (testModel, error) { + return u.model, u.err +} + type modelMock struct { value string - createResult modelErrorHolder - updateResult modelErrorHolder + createResult modelErrorHolder[types.Model] + updateResult modelErrorHolder[types.Model] deleteResult error } @@ -93,15 +99,15 @@ func newMockResponseWriter(recorder *responseWriterRecorder, err error) types.Re } } -type dtoMock struct { +type dtoMock[T any] struct { validationError error - assignModelResult modelErrorHolder + assignModelResult modelErrorHolder[T] } -func (d dtoMock) IsValid(_ context.Context, _ bool) error { +func (d dtoMock[T]) IsValid(_ context.Context, _ bool) error { return d.validationError } -func (d dtoMock) AssignToModel(_ context.Context, _ types.Model) (types.Model, error) { +func (d dtoMock[T]) AssignToModel(_ context.Context, _ T) (T, error) { return d.assignModelResult.model, d.assignModelResult.err } diff --git a/handlers/replace.go b/handlers/replace.go index 355e75d..c82866d 100644 --- a/handlers/replace.go +++ b/handlers/replace.go @@ -6,13 +6,13 @@ import ( ) // NewReplaceHandler creates a http.Handler that handles replacing an exing model. -func NewReplaceHandler(service types.ReplaceService, responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc { +func NewReplaceHandler[M types.ModelTypeInterface](service types.ReplaceService[M], responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { ctx := request.Context() var ( - dto types.Dto - model types.Model + dto types.Dto[M] + model M err error ) if dto, err = service.ParseDtoFromRequest(request); err != nil { @@ -35,7 +35,7 @@ func NewReplaceHandler(service types.ReplaceService, responseWriter types.Respon return } - if model, err = model.Update(ctx); err != nil { + if model, err = service.UpdateModel(ctx, model); err != nil { errorWriter(err, writer, request) return } diff --git a/handlers/replace_test.go b/handlers/replace_test.go index 42bb01a..16480f1 100644 --- a/handlers/replace_test.go +++ b/handlers/replace_test.go @@ -10,6 +10,7 @@ import ( type replaceServiceMock struct { getOneServiceMock + updateModelServiceMock createEmptyModelServiceMock parseDtoFromRequestServiceMock } @@ -20,10 +21,10 @@ func TestCrudHandlersImpl_Replace(t *testing.T) { tt := []struct { name string - service types.ReplaceService + service types.ReplaceService[testModel] responseWriterError error expectedError error - resultModel modelMock + resultModel testModel }{ { name: "parse dto form request turns error", @@ -39,7 +40,7 @@ func TestCrudHandlersImpl_Replace(t *testing.T) { name: "dto is invalid", service: replaceServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ + dto: dtoMock[testModel]{ validationError: errors.New("test"), }, }, @@ -50,7 +51,7 @@ func TestCrudHandlersImpl_Replace(t *testing.T) { name: "getting exiting model failed", service: replaceServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ + dto: dtoMock[testModel]{ validationError: nil, }, }, @@ -64,8 +65,8 @@ func TestCrudHandlersImpl_Replace(t *testing.T) { name: "dto assign to model failed", service: replaceServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ - assignModelResult: modelErrorHolder{ + dto: dtoMock[testModel]{ + assignModelResult: modelErrorHolder[testModel]{ err: errors.New("test"), }, }, @@ -77,18 +78,19 @@ func TestCrudHandlersImpl_Replace(t *testing.T) { name: "model save model failed", service: replaceServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ - assignModelResult: modelErrorHolder{ - model: modelMock{ - updateResult: modelErrorHolder{ - err: errors.New("test"), - }, + dto: dtoMock[testModel]{ + assignModelResult: modelErrorHolder[testModel]{ + model: testModel{ + Value: "test", }, }, }, }, getOneServiceMock: getOneServiceMock{ - model: modelMock{}, + model: testModel{}, + }, + updateModelServiceMock: updateModelServiceMock{ + err: errors.New("test"), }, }, expectedError: errors.New("test"), @@ -97,18 +99,17 @@ func TestCrudHandlersImpl_Replace(t *testing.T) { name: "response writer returns error", service: replaceServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ - assignModelResult: modelErrorHolder{ - model: modelMock{ - createResult: modelErrorHolder{ - model: modelMock{ - value: "test-value", - }, - }, + dto: dtoMock[testModel]{ + assignModelResult: modelErrorHolder[testModel]{ + model: testModel{ + Value: "test-value", }, }, }, }, + updateModelServiceMock: updateModelServiceMock{ + model: testModel{Value: "test-value"}, + }, }, responseWriterError: errors.New("test-error"), expectedError: errors.New("test-error"), @@ -117,22 +118,23 @@ func TestCrudHandlersImpl_Replace(t *testing.T) { name: "success", service: replaceServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ - assignModelResult: modelErrorHolder{ - model: modelMock{ - updateResult: modelErrorHolder{ - model: modelMock{ - value: "test-value", - }, - }, + dto: dtoMock[testModel]{ + assignModelResult: modelErrorHolder[testModel]{ + model: testModel{ + Value: "test-value", }, }, }, }, + updateModelServiceMock: updateModelServiceMock{ + model: testModel{ + Value: "test-value", + }, + }, }, expectedError: nil, - resultModel: modelMock{ - value: "test-value", + resultModel: testModel{ + Value: "test-value", }, }, } @@ -169,13 +171,13 @@ func TestCrudHandlersImpl_Replace(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 { diff --git a/handlers/update.go b/handlers/update.go index 39c1049..9b31c03 100644 --- a/handlers/update.go +++ b/handlers/update.go @@ -6,7 +6,7 @@ import ( ) // NewUpdateHandler creates a http.Handler that handles partial updates for existing models. -func NewUpdateHandler(service types.UpdateService, responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc { +func NewUpdateHandler[M types.ModelTypeInterface](service types.UpdateService[M], responseWriter types.ResponseWriter, errorWriter types.ErrorResponseWriter) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { ctx := request.Context() dto, err := service.ParseDtoFromRequest(request) @@ -20,7 +20,7 @@ func NewUpdateHandler(service types.UpdateService, responseWriter types.Response return } - var model types.Model + var model M if model, err = service.GetOne(request); err != nil { errorWriter(err, writer, request) return @@ -32,7 +32,7 @@ func NewUpdateHandler(service types.UpdateService, responseWriter types.Response return } - if model, err = model.Update(ctx); err != nil { + if model, err = service.UpdateModel(ctx, model); err != nil { errorWriter(err, writer, request) return } diff --git a/handlers/update_test.go b/handlers/update_test.go index 0913ce3..a9e4119 100644 --- a/handlers/update_test.go +++ b/handlers/update_test.go @@ -10,6 +10,7 @@ import ( type updateServiceMock struct { getOneServiceMock + updateModelServiceMock parseDtoFromRequestServiceMock } @@ -19,10 +20,10 @@ func TestCrudHandlersImpl_Update(t *testing.T) { tt := []struct { name string - service types.UpdateService + service types.UpdateService[testModel] responseWriterError error expectedError error - resultModel modelMock + resultModel testModel }{ { name: "parse dto form request turns error", @@ -38,7 +39,7 @@ func TestCrudHandlersImpl_Update(t *testing.T) { name: "dto is invalid", service: updateServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ + dto: dtoMock[testModel]{ validationError: errors.New("test"), }, }, @@ -49,7 +50,7 @@ func TestCrudHandlersImpl_Update(t *testing.T) { name: "getting exiting to model failed", service: updateServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ + dto: dtoMock[testModel]{ validationError: nil, }, }, @@ -63,8 +64,8 @@ func TestCrudHandlersImpl_Update(t *testing.T) { name: "dto assign to model failed", service: updateServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ - assignModelResult: modelErrorHolder{ + dto: dtoMock[testModel]{ + assignModelResult: modelErrorHolder[testModel]{ err: errors.New("test"), }, }, @@ -76,18 +77,21 @@ func TestCrudHandlersImpl_Update(t *testing.T) { name: "model save model failed", service: updateServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ - assignModelResult: modelErrorHolder{ - model: modelMock{ - updateResult: modelErrorHolder{ - err: errors.New("test"), - }, + dto: dtoMock[testModel]{ + assignModelResult: modelErrorHolder[testModel]{ + model: testModel{ + Value: "test-value", }, }, }, }, + updateModelServiceMock: updateModelServiceMock{ + err: errors.New("test"), + }, getOneServiceMock: getOneServiceMock{ - model: modelMock{}, + model: testModel{ + Value: "test-value", + }, }, }, expectedError: errors.New("test"), @@ -96,18 +100,17 @@ func TestCrudHandlersImpl_Update(t *testing.T) { name: "response writer returns error", service: updateServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ - assignModelResult: modelErrorHolder{ - model: modelMock{ - createResult: modelErrorHolder{ - model: modelMock{ - value: "test-value", - }, - }, + dto: dtoMock[testModel]{ + assignModelResult: modelErrorHolder[testModel]{ + model: testModel{ + Value: "test-value", }, }, }, }, + updateModelServiceMock: updateModelServiceMock{ + model: testModel{Value: "test-value"}, + }, }, responseWriterError: errors.New("test-error"), expectedError: errors.New("test-error"), @@ -116,22 +119,24 @@ func TestCrudHandlersImpl_Update(t *testing.T) { name: "success", service: updateServiceMock{ parseDtoFromRequestServiceMock: parseDtoFromRequestServiceMock{ - dto: dtoMock{ - assignModelResult: modelErrorHolder{ - model: modelMock{ - updateResult: modelErrorHolder{ - model: modelMock{ - value: "test-value", - }, - }, + dto: dtoMock[testModel]{ + assignModelResult: modelErrorHolder[testModel]{ + model: testModel{ + + Value: "test-value", }, }, }, }, + updateModelServiceMock: updateModelServiceMock{ + model: testModel{ + Value: "test-value", + }, + }, }, expectedError: nil, - resultModel: modelMock{ - value: "test-value", + resultModel: testModel{ + Value: "test-value", }, }, } @@ -168,13 +173,13 @@ func TestCrudHandlersImpl_Update(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 { diff --git a/types/dto.go b/types/dto.go index 22727fb..d7f4aac 100644 --- a/types/dto.go +++ b/types/dto.go @@ -12,7 +12,7 @@ type Validatable interface { // Dto is the type that contains the structure of the data that your api expect to receive. // It contains a method to validate itself and to convert it to its corresponding model object. -type Dto interface { +type Dto[Model any] interface { Validatable // AssignToModel assigns the value of the dto to a Model. diff --git a/types/service.go b/types/service.go index e326fd8..39ffa76 100644 --- a/types/service.go +++ b/types/service.go @@ -5,16 +5,18 @@ import ( "net/http" ) +type ModelTypeInterface any + // GetOneService defines functions that are needed for GetOne. -type GetOneService interface { +type GetOneService[M ModelTypeInterface] interface { // GetOne returns one Model based on a request. - GetOne(request *http.Request) (Model, error) + GetOne(request *http.Request) (M, error) } // GetAllService defines functions that are needed for GetAll. -type GetAllService interface { +type GetAllService[M ModelTypeInterface] interface { // GetAll returns a slice of Model based on a request. - GetAll(request *http.Request) ([]Model, error) + GetAll(request *http.Request) ([]M, error) } // CreateEmptyModelService defines the CreateEmptyModel function that is used in multiple handlers. @@ -23,43 +25,62 @@ type CreateEmptyModelService interface { CreateEmptyModel(ctx context.Context) Model } +type CreateModelService[M ModelTypeInterface] interface { + CreateModel(ctx context.Context, model M) (M, error) +} + +type UpdateModelService[M ModelTypeInterface] interface { + UpdateModel(ctx context.Context, model M) (M, error) +} +type DeleteModelService[M ModelTypeInterface] interface { + DeleteModel(ctx context.Context, model M) error +} + // ParseDtoFromRequestService defines the ParseDtoFromRequest function that is used in multiple handlers. -type ParseDtoFromRequestService interface { +type ParseDtoFromRequestService[M ModelTypeInterface] interface { // ParseDtoFromRequest creates a dto instance based on a request - ParseDtoFromRequest(request *http.Request) (Dto, error) + ParseDtoFromRequest(request *http.Request) (Dto[M], error) } // FunctionHandlerService defines a service to handle a request by a Function -type FunctionHandlerService interface { +type FunctionHandlerService[Dto Validatable, Result any] interface { // ParseValidatableFromRequest parses a Validatable for the request - ParseValidatableFromRequest(request *http.Request) (Validatable, error) + ParseValidatableFromRequest(request *http.Request) (Dto, error) // Function a function generates a response based on a Validatable - Function(ctx context.Context, dto Validatable) (interface{}, int, error) + Function(ctx context.Context, dto Dto) (Result, int, error) +} + +type DeleteService[M ModelTypeInterface] interface { + DeleteModelService[M] + GetOneService[M] } // CreateService defines functions that are need for the create model handler -type CreateService interface { - CreateEmptyModelService - ParseDtoFromRequestService +type CreateService[M ModelTypeInterface] interface { + CreateModelService[M] + ParseDtoFromRequestService[M] } // UpdateService defines functions that are need for the update model handler -type UpdateService interface { - GetOneService - ParseDtoFromRequestService +type UpdateService[M ModelTypeInterface] interface { + UpdateModelService[M] + GetOneService[M] + ParseDtoFromRequestService[M] } // ReplaceService defines functions that are need for the replace model handler -type ReplaceService interface { - GetOneService - CreateEmptyModelService - ParseDtoFromRequestService +type ReplaceService[M ModelTypeInterface] interface { + UpdateModelService[M] + GetOneService[M] + ParseDtoFromRequestService[M] } // Service holds functions to retrieve Model instances or create Dto objects. -type Service interface { - ParseDtoFromRequestService - CreateEmptyModelService - GetOneService - GetAllService +type Service[M ModelTypeInterface] interface { + ParseDtoFromRequestService[M] + CreateModelService[M] + UpdateModelService[M] + GetOneService[M] + GetAllService[M] + DeleteService[M] }