Skip to content

Commit b6067b2

Browse files
authored
Merge pull request #367 from maxekman/345-nil-checks
Fix / Add nil checks for commands/events
2 parents bc7e608 + 3d00387 commit b6067b2

11 files changed

Lines changed: 104 additions & 4 deletions

File tree

command_check.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,20 @@
1515
package eventhorizon
1616

1717
import (
18+
"errors"
1819
"reflect"
1920
"time"
2021

2122
"github.com/looplab/eventhorizon/uuid"
2223
)
2324

25+
var (
26+
// ErrMissingCommand is when there is no command to be handled.
27+
ErrMissingCommand = errors.New("missing command")
28+
// ErrMissingAggregateID is when a command is missing an aggregate ID.
29+
ErrMissingAggregateID = errors.New("missing aggregate ID")
30+
)
31+
2432
// IsZeroer is used to check if a type is zero-valued, and in that case
2533
// is not allowed to be used in a command. See CheckCommand.
2634
type IsZeroer interface {
@@ -39,6 +47,14 @@ func (c *CommandFieldError) Error() string {
3947

4048
// CheckCommand checks a command for errors.
4149
func CheckCommand(cmd Command) error {
50+
if cmd == nil {
51+
return ErrMissingCommand
52+
}
53+
54+
if cmd.AggregateID() == uuid.Nil {
55+
return ErrMissingAggregateID
56+
}
57+
4258
rv := reflect.Indirect(reflect.ValueOf(cmd))
4359
rt := rv.Type()
4460

command_check_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package eventhorizon
1616

1717
import (
18+
"errors"
1819
"testing"
1920
"time"
2021

@@ -28,6 +29,18 @@ func TestCheckCommand(t *testing.T) {
2829
t.Error("there should be no error:", err)
2930
}
3031

32+
// Missing command.
33+
err = CheckCommand(nil)
34+
if !errors.Is(err, ErrMissingCommand) {
35+
t.Error("there should be a missing command error:", err)
36+
}
37+
38+
// Missing Aggregate ID.
39+
err = CheckCommand(&TestCommandUUIDValue{})
40+
if !errors.Is(err, ErrMissingAggregateID) {
41+
t.Error("there should be a missing aggregate ID error:", err)
42+
}
43+
3144
// Missing required UUID value.
3245
err = CheckCommand(&TestCommandUUIDValue{TestID: uuid.New()})
3346
if err == nil || err.Error() != "missing field: Content" {

commandhandler/aggregate/commandhandler.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ func NewCommandHandler(t eh.AggregateType, store eh.AggregateStore) (*CommandHan
5555
// HandleCommand handles a command with the registered aggregate.
5656
// Returns ErrAggregateNotFound if no aggregate could be found.
5757
func (h *CommandHandler) HandleCommand(ctx context.Context, cmd eh.Command) error {
58-
err := eh.CheckCommand(cmd)
59-
if err != nil {
58+
if err := eh.CheckCommand(cmd); err != nil {
6059
return err
6160
}
6261

commandhandler/bus/commandhandler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ func NewCommandHandler() *CommandHandler {
4545

4646
// HandleCommand handles a command with a handler capable of handling it.
4747
func (h *CommandHandler) HandleCommand(ctx context.Context, cmd eh.Command) error {
48+
if err := eh.CheckCommand(cmd); err != nil {
49+
return err
50+
}
51+
4852
h.handlersMu.RLock()
4953
defer h.handlersMu.RUnlock()
5054

eventhandler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ package eventhorizon
1616

1717
import (
1818
"context"
19+
"errors"
1920
"reflect"
2021
"runtime"
2122
"strings"
2223
)
2324

25+
// ErrMissingEvent is when there is no event to be handled.
26+
var ErrMissingEvent = errors.New("missing event")
27+
2428
// EventHandlerType is the type of an event handler, used as its unique identifier.
2529
type EventHandlerType string
2630

eventhandler/projector/eventhandler.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ func (h *EventHandler) HandlerType() eh.EventHandlerType {
175175
// It will try to find the correct version of the model, waiting for it the projector
176176
// has the WithWait option set.
177177
func (h *EventHandler) HandleEvent(ctx context.Context, event eh.Event) error {
178+
if event == nil {
179+
return &Error{
180+
Err: eh.ErrMissingEvent,
181+
Projector: h.projector.ProjectorType().String(),
182+
}
183+
}
184+
178185
// Used to retry once in case of a version mismatch.
179186
triedOnce := false
180187
retryOnce:

eventhandler/projector/eventhandler_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,24 @@ func TestEventHandler_UpdateModelAfterDelete(t *testing.T) {
386386
}
387387
}
388388

389+
func TestEventHandler_MissingEventError(t *testing.T) {
390+
repo := &mocks.Repo{}
391+
projector := &TestProjector{}
392+
handler := NewEventHandler(projector, repo)
393+
handler.SetEntityFactory(func() eh.Entity {
394+
return &mocks.SimpleModel{}
395+
})
396+
397+
ctx := context.Background()
398+
399+
err := handler.HandleEvent(ctx, nil)
400+
401+
projectError := &Error{}
402+
if !errors.As(err, &projectError) || !errors.Is(err, eh.ErrMissingEvent) {
403+
t.Error("there should be an error:", err)
404+
}
405+
}
406+
389407
func TestEventHandler_LoadError(t *testing.T) {
390408
repo := &mocks.Repo{}
391409
projector := &TestProjector{}

eventhandler/saga/eventhandler.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ func (h *EventHandler) HandlerType() eh.EventHandlerType {
8787

8888
// HandleEvent implements the HandleEvent method of the eventhorizon.EventHandler interface.
8989
func (h *EventHandler) HandleEvent(ctx context.Context, event eh.Event) error {
90+
if event == nil {
91+
return &Error{
92+
Err: eh.ErrMissingEvent,
93+
Saga: h.saga.SagaType().String(),
94+
}
95+
}
96+
9097
// Run the saga which can issue commands on the provided command handler.
9198
if err := h.saga.RunSaga(ctx, event, h.commandHandler); err != nil {
9299
return &Error{

eventhandler/saga/eventhandler_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package saga
1616

1717
import (
1818
"context"
19+
"errors"
1920
"reflect"
2021
"testing"
2122
"time"
@@ -52,6 +53,23 @@ func TestEventHandler(t *testing.T) {
5253
}
5354
}
5455

56+
func TestEventHandler_MissingEventError(t *testing.T) {
57+
commandHandler := &mocks.CommandHandler{
58+
Commands: []eh.Command{},
59+
}
60+
saga := &TestSaga{}
61+
handler := NewEventHandler(saga, commandHandler)
62+
63+
ctx := context.Background()
64+
65+
err := handler.HandleEvent(ctx, nil)
66+
67+
projectError := &Error{}
68+
if !errors.As(err, &projectError) || !errors.Is(err, eh.ErrMissingEvent) {
69+
t.Error("there should be an error:", err)
70+
}
71+
}
72+
5573
const (
5674
TestSagaType Type = "TestSaga"
5775
)

eventhandler/waiter/eventhandler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ func (h *EventHandler) HandlerType() eh.EventHandlerType {
5151
// HandleEvent implements the HandleEvent method of the eventhorizon.EventHandler interface.
5252
// It forwards events to the waiters so that they can match the events.
5353
func (h *EventHandler) HandleEvent(ctx context.Context, event eh.Event) error {
54+
if event == nil {
55+
return eh.ErrMissingEvent
56+
}
57+
5458
h.inbox <- event
5559

5660
return nil

0 commit comments

Comments
 (0)