Caller is moved into its own repo
This commit is contained in:
parent
98b341ec02
commit
8c73487c08
|
@ -1,27 +0,0 @@
|
||||||
# Caller
|
|
||||||
|
|
||||||
Package caller is used to dynamically call functions with data unmarshalled
|
|
||||||
into the functions' first argument. Its main purpose is to hide common
|
|
||||||
unmarshalling code from each function implementation thus reducing
|
|
||||||
boilerplate and making package interaction code sexier.
|
|
||||||
|
|
||||||
[Documentation](https://godoc.org/github.com/localhots/shezmu/caller)
|
|
||||||
|
|
||||||
Caller abstracts away the process of unmarshaling data before processing.
|
|
||||||
|
|
||||||
```go
|
|
||||||
type PriceUpdate struct {
|
|
||||||
Product string `json:"product"`
|
|
||||||
Amount float32 `json:"amount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func PriceUpdatePrinter(p PriceUpdate) {
|
|
||||||
log.Printf("Price for %q is now $%.2f", p.Product, p.Amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error handling is skipped for clarity
|
|
||||||
func main() {
|
|
||||||
printer, _ := caller.New(PriceUpdatePrinter)
|
|
||||||
_ = printer.Call([]byte(`{"product": "Paperclip", "amount": 0.01}`))
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,84 +0,0 @@
|
||||||
// Package caller is used to dynamically call functions with data unmarshalled
|
|
||||||
// into the functions' first argument. Its main purpose is to hide common
|
|
||||||
// unmarshalling code from each function implementation thus reducing
|
|
||||||
// boilerplate and making package interaction code sexier.
|
|
||||||
package caller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Caller wraps a function and makes it ready to be dynamically called.
|
|
||||||
type Caller struct {
|
|
||||||
// Unmarshaller is a BYOB unmarshaller function. By default it uses JSON.
|
|
||||||
Unmarshaller func(data []byte, v interface{}) error
|
|
||||||
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{
|
|
||||||
Unmarshaller: json.Unmarshal,
|
|
||||||
fun: fval,
|
|
||||||
argtyp: ftyp.In(0),
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call creates an instance of the Caller function's argument type, unmarshalls
|
|
||||||
// the 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 = c.Unmarshaller(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)
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
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, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
os.Stdout = origStdout
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
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