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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user