Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/use generics in handlers #3

Merged
merged 5 commits into from
Dec 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ on:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ '1.18', '1.19', '1.20', '1.21.x' ]

steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '1.16.2'

- name: Install dependencies
run: |
go version
go get -u golang.org/x/lint/golint
go-version: ${{ matrix.go-version }}

- name: Build
run: |
Expand All @@ -25,7 +23,11 @@ jobs:
- name: Test
run: go test -gcflags=-l -v ./...

- name: Vet & Lint
- name: Vet
run: |
go vet ./...
golint -set_exit_status=1 ./...

- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.54
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ repos:
- id: go-fmt
- id: go-mod-tidy
- id: go-build
- id: go-lint
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: 40 additions & 44 deletions handlers/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,46 @@ 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
}

type createEmptyModelServiceMock struct {
emptyModel types.Model
type createModelServiceMock struct {
createdModel testModel
err error
}

func (c createEmptyModelServiceMock) CreateEmptyModel(_ context.Context) types.Model {
return c.emptyModel
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 +63,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 +74,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 +87,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 +117,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 +142,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 +164,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
Loading
Loading