diff --git a/dyncall/call.go b/dyncall/call.go new file mode 100644 index 0000000..ef749ac --- /dev/null +++ b/dyncall/call.go @@ -0,0 +1,11 @@ +package dyncall + +type CallModule interface { + // the call id this module relates to + // e.g. "Messages:Fetch" + CallID() string + // validate the given parameters - might return an error when received invalid parameters + Validate(map[string]interface{}) error + // will handle the call and return the result + Handle(map[string]interface{}) (map[string]interface{}, error) +} diff --git a/dyncall/dyncall_registry.go b/dyncall/dyncall_registry.go new file mode 100644 index 0000000..aeaa680 --- /dev/null +++ b/dyncall/dyncall_registry.go @@ -0,0 +1,86 @@ +package dyncall + +import "fmt" + +type getCallModule struct { + respChan chan CallModule + id string +} + +type Registry struct { + addModuleChan chan CallModule + getModuleChan chan getCallModule + closeChan chan struct{} +} + +func New() *Registry { + + r := &Registry{ + addModuleChan: make(chan CallModule), + getModuleChan: make(chan getCallModule), + closeChan: make(chan struct{}), + } + + // state + go func() { + modules := map[string]CallModule{} + + for { + select { + case <-r.closeChan: + return + case m := <-r.addModuleChan: + modules[m.CallID()] = m + case getCallMod := <-r.getModuleChan: + mod := modules[getCallMod.id] + getCallMod.respChan <- mod + } + } + + }() + + return r + +} + +func (r *Registry) Register(m CallModule) error { + + // exist if already registered + respChan := make(chan CallModule) + r.getModuleChan <- getCallModule{ + id: m.CallID(), + respChan: respChan, + } + if nil != <-respChan { + return fmt.Errorf("a call modules with id %s has already been registered", m.CallID()) + } + + // add module + r.addModuleChan <- m + + return nil + +} + +func (r *Registry) Call(callID string, payload map[string]interface{}) (map[string]interface{}, error) { + + // get call module + respChan := make(chan CallModule) + r.getModuleChan <- getCallModule{ + id: callID, + respChan: respChan, + } + callModule := <-respChan + if nil == callModule { + return map[string]interface{}{}, fmt.Errorf("a call module with call id: %s does not exist", callID) + } + + // validate the payload + if err := callModule.Validate(payload); err != nil { + return map[string]interface{}{}, err + } + + // handle the payload + return callModule.Handle(payload) + +} diff --git a/dyncall/dyncall_registry_test.go b/dyncall/dyncall_registry_test.go new file mode 100644 index 0000000..acd451c --- /dev/null +++ b/dyncall/dyncall_registry_test.go @@ -0,0 +1,84 @@ +package dyncall + +import ( + "github.com/kataras/iris/core/errors" + "github.com/stretchr/testify/require" + "testing" +) + +type testCallModule struct { + callID string + validate func(map[string]interface{}) error + handle func(map[string]interface{}) (map[string]interface{}, error) +} + +func (m *testCallModule) CallID() string { + return m.callID +} + +func (m *testCallModule) Validate(payload map[string]interface{}) error { + return m.validate(payload) +} + +func (m *testCallModule) Handle(payload map[string]interface{}) (map[string]interface{}, error) { + return m.handle(payload) +} + +func TestRegistry_Register(t *testing.T) { + + callModule := testCallModule{ + callID: "MY:MODULE", + } + + reg := New() + + // should register without an error + require.Nil(t, reg.Register(&callModule)) + + // should fail since the same module can't be registered twice + require.EqualError(t, reg.Register(&callModule), "a call modules with id MY:MODULE has already been registered") + +} + +// make sure call gets validated +func TestRegistry_CallValidate(t *testing.T) { + + callModule := testCallModule{ + callID: "MY:MODULE", + validate: func(payload map[string]interface{}) error { + return errors.New("invalid payload") + }, + } + + reg := New() + require.Nil(t, reg.Register(&callModule)) + + // call should be validated + _, err := reg.Call("MY:MODULE", map[string]interface{}{}) + require.EqualError(t, err, "invalid payload") + +} + +func TestRegistry_Call(t *testing.T) { + + callModule := testCallModule{ + callID: "MY:MODULE", + validate: func(payload map[string]interface{}) error { + return nil + }, + handle: func(payload map[string]interface{}) (map[string]interface{}, error) { + return map[string]interface{}{ + "key": "value", + }, nil + }, + } + + reg := New() + require.Nil(t, reg.Register(&callModule)) + + // call should be validated + result, err := reg.Call("MY:MODULE", map[string]interface{}{}) + require.Nil(t, err) + require.Equal(t, map[string]interface{}{"key": "value"}, result) + +}