Add caller package
This commit is contained in:
commit
3c6ef613f5
|
@ -0,0 +1,22 @@
|
||||||
|
Copyright (c) 2015 Gregory Eremin
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Caller
|
||||||
|
|
||||||
|
Package caller is used to dynamicly call functions with data unmarshalled
|
||||||
|
into the functions' first argument. It's main purpose is to hide common
|
||||||
|
unmarshalling code from each function's implementation thus reducing
|
||||||
|
boilerplate and making the code sexier.
|
||||||
|
|
||||||
|
[Documentation](https://godoc.org/github.com/localhots/uberdaemon/caller)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/localhots/uberdaemon/caller"
|
||||||
|
)
|
||||||
|
|
||||||
|
type message struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func processMessage(m message) {
|
||||||
|
fmt.Printf("Title: %s\nBody: %s\n", m.Title, m.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c, _ := caller.New(processMessage)
|
||||||
|
c.Call(`{"title": "Hello", "body": "World"}`)
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,81 @@
|
||||||
|
// Package caller is used to dynamicly call functions with data unmarshalled
|
||||||
|
// into the functions' first argument. It's main purpose is to hide common
|
||||||
|
// unmarshalling code from each function's implementation thus reducing
|
||||||
|
// boilerplate and making the code sexier.
|
||||||
|
package caller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Caller wraps a function and makes it ready to be dynamically called.
|
||||||
|
type Caller struct {
|
||||||
|
fun reflect.Value
|
||||||
|
argtyp reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidFunctionType is an error that is returned by the New function
|
||||||
|
// when its argument is not a function.
|
||||||
|
ErrInvalidFunctionType = errors.New("argument must be function")
|
||||||
|
// ErrInvalidFunctionInArguments is an error that is returned by the New
|
||||||
|
// function when its argument-function has a number of input arguments other
|
||||||
|
// than 1.
|
||||||
|
ErrInvalidFunctionInArguments = errors.New("function must have only one input argument")
|
||||||
|
// ErrInvalidFunctionOutArguments is an error that is returned by the New
|
||||||
|
// function when its argument-function returs any values.
|
||||||
|
ErrInvalidFunctionOutArguments = errors.New("function must not have output arguments")
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new Caller instance using the function given as an argument.
|
||||||
|
// It returns the Caller instance and an error if something is wrong with the
|
||||||
|
// argument-function.
|
||||||
|
func New(fun interface{}) (c *Caller, err error) {
|
||||||
|
fval := reflect.ValueOf(fun)
|
||||||
|
ftyp := reflect.TypeOf(fun)
|
||||||
|
if ftyp.Kind() != reflect.Func {
|
||||||
|
return nil, ErrInvalidFunctionType
|
||||||
|
}
|
||||||
|
if ftyp.NumIn() != 1 {
|
||||||
|
return nil, ErrInvalidFunctionInArguments
|
||||||
|
}
|
||||||
|
if ftyp.NumOut() != 0 {
|
||||||
|
return nil, ErrInvalidFunctionOutArguments
|
||||||
|
}
|
||||||
|
|
||||||
|
c = &Caller{
|
||||||
|
fun: fval,
|
||||||
|
argtyp: ftyp.In(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call creates an instance of the Caller function's argument type, unmarshalls
|
||||||
|
// the JSON payload into it and dynamically calls the Caller function with this
|
||||||
|
// instance.
|
||||||
|
func (c *Caller) Call(data []byte) error {
|
||||||
|
val, err := c.unmarshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.makeDynamicCall(val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Caller) unmarshal(data []byte) (val reflect.Value, err error) {
|
||||||
|
val = c.newValue()
|
||||||
|
err = json.Unmarshal(data, val.Interface())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Caller) makeDynamicCall(val reflect.Value) {
|
||||||
|
c.fun.Call([]reflect.Value{val.Elem()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Caller) newValue() reflect.Value {
|
||||||
|
return reflect.New(c.argtyp)
|
||||||
|
}
|
|
@ -0,0 +1,198 @@
|
||||||
|
package caller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// Testing targets
|
||||||
|
//
|
||||||
|
|
||||||
|
type testMessage struct {
|
||||||
|
Body string `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const testPayload = `{"body":"Success!"}`
|
||||||
|
|
||||||
|
func testFun(m testMessage) {
|
||||||
|
fmt.Print(m.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFunSilent(_ testMessage) {}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Tests
|
||||||
|
//
|
||||||
|
|
||||||
|
func TestNewCallerSuccess(t *testing.T) {
|
||||||
|
c, err := New(testFun)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
if c == nil {
|
||||||
|
t.Error("Expected an instance of Caller, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCallerWithNonFunc(t *testing.T) {
|
||||||
|
c, err := New(1)
|
||||||
|
if err != ErrInvalidFunctionType {
|
||||||
|
t.Errorf("Expected ErrInvalidFunctionType, got: %v", err)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
t.Error("Expected nil, got an instance of Caller")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCallerWithFuncMultipleArgs(t *testing.T) {
|
||||||
|
fun := func(a, b int) {}
|
||||||
|
c, err := New(fun)
|
||||||
|
if err != ErrInvalidFunctionInArguments {
|
||||||
|
t.Errorf("Expected ErrInvalidFunctionInArguments, got: %v", err)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
t.Error("Expected nil, got an instance of Caller")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCallerWithFuncReturnValue(t *testing.T) {
|
||||||
|
fun := func(a int) int { return 0 }
|
||||||
|
c, err := New(fun)
|
||||||
|
if err != ErrInvalidFunctionOutArguments {
|
||||||
|
t.Errorf("Expected ErrInvalidFunctionOutArguments, got: %v", err)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
t.Error("Expected nil, got an instance of Caller")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallSuccess(t *testing.T) {
|
||||||
|
c, err := New(testFun)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
out := captureStdoutAround(func() {
|
||||||
|
if err := c.Call([]byte(testPayload)); err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if string(out) != "Success!" {
|
||||||
|
t.Errorf("Expected output to be %q, got %q", "Success!", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCallFalure(t *testing.T) {
|
||||||
|
c, _ := New(testFunSilent)
|
||||||
|
|
||||||
|
err := c.Call([]byte("{"))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected unmarshalling error, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalSuccess(t *testing.T) {
|
||||||
|
c, _ := New(testFunSilent)
|
||||||
|
|
||||||
|
_, err := c.unmarshal([]byte(testPayload))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalFailure(t *testing.T) {
|
||||||
|
c, _ := New(testFunSilent)
|
||||||
|
|
||||||
|
_, err := c.unmarshal([]byte("{"))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected unmarshalling error, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func captureStdoutAround(f func()) []byte {
|
||||||
|
origStdout := os.Stdout
|
||||||
|
r, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
f()
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
out, _ := ioutil.ReadAll(r)
|
||||||
|
r.Close()
|
||||||
|
os.Stdout = origStdout
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Benchmarks
|
||||||
|
//
|
||||||
|
|
||||||
|
func BenchmarkCaller(b *testing.B) {
|
||||||
|
c, _ := New(testFunSilent)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
c.Call([]byte(testPayload))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNoCaller(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var msg testMessage
|
||||||
|
json.Unmarshal([]byte(testPayload), &msg)
|
||||||
|
testFunSilent(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDynamicNew(b *testing.B) {
|
||||||
|
c, _ := New(testFunSilent)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = c.newValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStaticNew(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = testMessage{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDynamicCall(b *testing.B) {
|
||||||
|
c, _ := New(testFunSilent)
|
||||||
|
val, _ := c.unmarshal([]byte(testPayload))
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
c.makeDynamicCall(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStaticCall(b *testing.B) {
|
||||||
|
var msg testMessage
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testFunSilent(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalIntoInterface(b *testing.B) {
|
||||||
|
c, _ := New(testFunSilent)
|
||||||
|
val := c.newValue()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
json.Unmarshal([]byte(testPayload), val.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalIntoTypedValue(b *testing.B) {
|
||||||
|
var msg testMessage
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
json.Unmarshal([]byte(testPayload), &msg)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue