-
Notifications
You must be signed in to change notification settings - Fork 6
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
[dyncall] added dyncall module #93
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
|
||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably check if map key exists first to avoid any potential panics
The current implementation might still work correctly in practice though, even without any changes
In the case of the key not existing in the modules map,
nil
will be sent togetCallMod.respChan
so as long as it's properly handled, it shouldn't cause major issuesA proposed alternative that feels more safe in terms of potential panics
Even though it might not make much of a difference in this use case, if
CallModule
was a struct with 2 fields, of type int and string, you wouldn't get anil
if the key is missing in the map with the unsafe usage, and you would get {0,"") as this would be corresponding to the default values of int and string type.It's just that this time the default value of
CallModule
isnil
because its an interface