queue

package module
v0.0.0-...-4fc32c3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 15, 2014 License: MIT Imports: 4 Imported by: 3

README

queue

Streamlined error handling and piping through a queue of go functions

Build Status GoDoc Coverage Status

Status

This API is considered stable.

Go >= 1.1 required

Why

In go, sometimes you need to run a bunch of functions that return errors and/or results. You might end up writing stuff like this

err = fn1(...)

if err != nil {
   // handle error somehow
}

err = fn2(...)

if err != nil {
   // handle error somehow
}

...

a lot of times. This is especially annoying if you want to handle all errors the same way (e.g. return the first error).

queue provides a way to call functions in a queue while collecting the errors via a predefined or custom error handler. The predefined handler returns on the first error and custom error handlers might be used to catch/handle some/all kinds of errors while keeping the queue running.

Examples

(more can be found here )

package main

import (
    "fmt"
    "gopkg.in/go-on/queue.v2"
    "strconv"
)

type Person struct {
    Name string
    Age  int
}

func (p *Person) SetAge(i int) { p.Age = i }
func (p *Person) SetName(n string) error {
    if n == "Peter" {
        return fmt.Errorf("Peter is not allowed")
    }
    p.Name = n
    return nil
}

func get(k string, m map[string]string) string { return m[k] }

func set(p *Person, m map[string]string, handler queue.ErrHandler) {
    // create a new queue with the default error handler
    q := queue.New().
        // get the name from the map
        Add(get, "Name", m).
        // set the name in the struct
        Add(p.SetName, queue.PIPE).
        // get the age from the map
        Add(get, "Age", m).
        // convert the age to int
        Add(strconv.Atoi, queue.PIPE).
        // set the age in the struct
        Add(p.SetAge, queue.PIPE).
        // inspect the struct
        Add(fmt.Printf, "SUCCESS %#v\n\n", p)

    // if a custom error handler is passed, use it,
    // otherwise the default error handler queue.STOP is used
    // which stops on the first error, returning it
    if handler != nil {
        q.OnError(handler)
    }
    // run the whole queue
    err := q.Run()

    // if you want a check for validity of the given functions and
    // parameters before the run, use 
    // err := q.CheckAndRun()

    // report, if there is an unhandled error
    if err != nil {
        fmt.Printf("ERROR %#v: %s\n\n", p, err)
    }
}

var ignoreAge = queue.ErrHandlerFunc(func(err error) error {
    _, ok := err.(*strconv.NumError)
    if ok {
        return nil
    }
    return err
})

func main() {
    var arthur = map[string]string{"Name": "Arthur", "Age": "42"}
    set(&Person{}, arthur, nil)

    var anne = map[string]string{"Name": "Anne", "Age": "4b"}
    // this will report the error of the invalid age that could not be parsed
    set(&Person{}, anne, nil)

    // this will ignore the invalid age, but no other errors
    set(&Person{}, anne, ignoreAge)

    var peter = map[string]string{"Name": "Peter", "Age": "4c"}

    // this will ignore the invalid age, but no other errors, so
    // it should err for the fact that peter is not allowed
    set(&Person{}, peter, ignoreAge)

    // this will ignore any errors and continue the queue run
    set(&Person{}, peter, queue.IGNORE)

}

Shortcuts

A package with shortcuts that has a more compact syntax and is better includable with dot (.) is provided at github.com/go-on/queue/q

It is work in progress and does not have 100% test coverage yet.

Here an example for saving a User object, we got from json (excerpt) with the shortcuts of q. All other features of queue are also available in q.

import "gopkg.in/go-on/queue.v2/q"

func SaveUser(w http.ResponseWriter, rq *http.Request) {
    u := &User{}
    q.Err(ErrorHandler(w))(       // handle all errors with the given handler
        ioutil.ReadAll, rq.Body,  // read json (returns json and error)
    )(
        json.Unmarshal, q.V, u,   // unmarshal json from above (returns error)
    )(
        u.Validate,               // validate the user (returns error)
    )(
        u.Save,                   // save the user (returns error)
    )(
        ok, w,                    // send the "ok" message (returns no error)
    ).Run()
}

ErrorHandler returns a general error handler that does the right thing (i.e. write input errors to the client and store other errors for further investigation). It lets the chain stop on the first error. ok is a function that writes to client that an action was successful, no matter what action it was. And the user struct has specific methods for validation and saving.

Bitdeli Badge

Documentation

Overview

Package queue allows streamlined error handling and piping of returned values.

This package is considered stable and ready for production. It requires Go >= 1.1.

Motivation:

In go, sometimes you need to run a bunch of functions that return errors and/or results. You might end up writing stuff like this

err = fn1(...)

if err != nil {
   // handle error somehow
}

err = fn2(...)

if err != nil {
   // handle error somehow
}

...

a lot of times.

This is especially annoying if you want to handle all errors the same way (e.g. return the first error).

This package provides a way to call functions in a queue while collecting the errors via a predefined or custom error handler. The predefined handler returns on the first error and custom error handlers might be used to catch/handle some/all kinds of errors while keeping the queue running.

Usage:

...
// create a new queue
err := New().
	// add function get to the queue that should be called with "Age" and m
	Add(get, "Age", m).

	// add function strconv.Atoi and pass the value returned from get via PIPE
	Add(strconv.Atoi, PIPE).

	// add method SetAge of p and pass the value returned from strconv.Atoi
	// note that the second return value error is not part of the pipe
	// it will however be sent to the error handler if it is not nil
	Add(p.SetAge, PIPE).
	...
	.OnError(STOP)  // optional custom error handler, STOP is default
	.Run()          // run it, returning unhandled errors.

	- OR -

	.CheckAndRun() // if you want to check for type errors of the functions/arguments before the run

...

The functions in the queue are checked for the type of the last return value. If it is an error, the value will be checked when running the queue and the error handler is invoked if the error is not nil.

The error handler decides, if it can handle the error and the run continues (by returning nil) or if it can't and the run stops (by returning an/the error).

Custom error handlers must fullfill the ErrHandler interface.

When running the queue, the return values of the previous function with be injected into the argument list of the next function at the position of the pseudo argument PIPE. However, if the last return value is an error, it will be omitted.

There is also a different running mode invoked by the method Fallback() that runs the queue until the first function returns no error.

A package with shortcuts that has a more compact syntax and is better includable with dot (.) is provided at github.com/go-on/queue/q

Example
package main

import (
	"fmt"
	"strconv"
)

type person struct {
	Name string
	Age  int
}

func (p *person) SetAge(i int) { p.Age = i }
func (p *person) SetName(n string) error {
	if n == "Peter" {
		return fmt.Errorf("Peter is not allowed")
	}
	p.Name = n
	return nil
}

func getMap(k string, m map[string]string) string { return m[k] }

func setStruct(p *person, m map[string]string, handler ErrHandler) {
	// create a new queue with the default error handler
	q := New().
		// get the name from the map
		Add(getMap, "Name", m).
		// set the name in the struct
		Add(p.SetName, PIPE).
		// get the age from the map
		Add(getMap, "Age", m).
		// convert the age to int
		Add(strconv.Atoi, PIPE).
		// set the age in the struct
		Add(p.SetAge, PIPE).
		// inspect the struct
		Add(fmt.Printf, "SUCCESS %#v\n", p)

	// if a custom error handler is passed, use it,
	// otherwise the default error handler queue.STOP is used
	// which stops on the first error, returning it
	if handler != nil {
		q.OnError(handler)
	}
	// check the whole queue and run it
	err := q.CheckAndRun()

	// report, if there is an unhandled error
	if err != nil {
		fmt.Printf("ERROR %#v: %s\n", p, err)
	}
}

var ignoreAge = ErrHandlerFunc(func(err error) error {
	_, ok := err.(*strconv.NumError)
	if ok {
		return nil
	}
	return err
})

func main() {
	var arthur = map[string]string{"Name": "Arthur", "Age": "42"}
	setStruct(&person{}, arthur, nil)

	var anne = map[string]string{"Name": "Anne", "Age": "4b"}
	// this will report the error of the invalid age that could not be parsed
	setStruct(&person{}, anne, nil)

	// this will ignore the invalid age, but no other errors
	setStruct(&person{}, anne, ignoreAge)

	var peter = map[string]string{"Name": "Peter", "Age": "4c"}

	// this will ignore the invalid age, but no other errors, so
	// it should err for the fact that peter is not allowed
	setStruct(&person{}, peter, ignoreAge)

	// this will ignore any errors and continue the queue run
	setStruct(&person{}, peter, IGNORE)

}
Output:

SUCCESS &queue.person{Name:"Arthur", Age:42}
ERROR &queue.person{Name:"Anne", Age:0}: strconv.ParseInt: parsing "4b": invalid syntax
SUCCESS &queue.person{Name:"Anne", Age:0}
ERROR &queue.person{Name:"", Age:0}: Peter is not allowed
SUCCESS &queue.person{Name:"", Age:0}

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrHandler, stops on the first error
	STOP = ErrHandlerFunc(func(err error) error { return err })
	// ErrHandler, ignores all errors
	IGNORE = ErrHandlerFunc(func(err error) error { return nil })

	// ErrHandler, panics on the first error
	PANIC = ErrHandlerFunc(func(err error) error {
		panic(err.Error())
		return err
	})
)
View Source
var PIPE = pipe{}

PIPE is a pseudo parameter that will be replaced by the returned non error values of the previous function

Functions

func Call

func Call(function interface{}, arguments ...interface{}) *call

func CallNamed

func CallNamed(name string, function interface{}, arguments ...interface{}) *call

func Collect

func Collect(val interface{}, vals ...interface{}) interface{}

collects different values and puts them in an interface interface is a slice of the type of the first given value

func Fallback

func Fallback(qs ...Queuer) callfallback

func Get

func Get(ptr interface{}) interface{}

ptr is a pointer to something that should be get the value to which the pointer points is put into the pipe

func Ok

func Ok()

a simple function at the end that does not return errors or anything

func Run

func Run(qs ...Queuer) callrun

func Set

func Set(ptrToSet interface{}, val interface{})

func Value

func Value(i interface{}) interface{}

returns the given value and "Sets" the value of a pipe

Types

type CallPanic

type CallPanic struct {
	// position of the function in the queue
	Position int

	// type signature of the function
	Type string

	// arguments passed to the function
	Params []interface{}

	// error message
	ErrorMessage string

	// name of the function call, if it is named
	Name string
}

Error returned if a function call triggered a panic

func (CallPanic) Error

func (c CallPanic) Error() string

type ErrHandler

type ErrHandler interface {
	// HandleError receives a non nil error and may handle it.
	// An error is considered not handled, if HandleError() returns the given error.
	// An error is considered handled, if HandleError() returns something other than
	// the given error.
	// An error is considered catched, if HandleError() returns nil.
	// If HandleError() catches an error, the queue run will continue.
	// Otherwise the queue will be stopped and the error is returned.
	// See Run() and Fallback() for more details about returning errors.
	HandleError(error) error
}

Each Queue has an error handler that is called if a function returns an error.

The default error handler when calling Run() is STOP and when calling Fallback() is IGNORE. The error handler PANIC might be chosen to panic on the first error (some kind of "Must" for every function call).

type ErrHandlerFunc

type ErrHandlerFunc func(error) error

shortcut to let a func be an error handler

func (ErrHandlerFunc) HandleError

func (f ErrHandlerFunc) HandleError(err error) error

type InvalidArgument

type InvalidArgument struct {
	// position of the function in the queue
	Position int

	// type signature of the function
	Type string

	// error message
	ErrorMessage string

	// name of the function call, if it is named
	Name string
}

Error returned if a function is not valid

func (InvalidArgument) Error

func (i InvalidArgument) Error() string

type InvalidFunc

type InvalidFunc struct {
	// position of the function in the queue
	Position int

	// type signature of the function
	Type string

	// error message
	ErrorMessage string

	// name of the function call, if it is named
	Name string
}

func (InvalidFunc) Error

func (i InvalidFunc) Error() string

type Queue

type Queue struct {
	// contains filtered or unexported fields
}

func Add

func Add(function interface{}, arguments ...interface{}) *Queue

func AddNamed

func AddNamed(name string, function interface{}, arguments ...interface{}) *Queue

func New

func New() *Queue

New creates a new function queue

Use Add() for adding calls to the Queue.

Use OnError() to set a custom error handler.

The default error handler is set by the runner function, Run() or Fallback().

Use one of these runner calls to run the queue.

func OnError

func OnError(handler ErrHandler) (q *Queue)

OnError returns a new empty *Queue, where the errHandler is set to the given handler

More about adding functions to the Queue: see Add(). More about error handling and running a Queue: see Run() and Fallback().

func (*Queue) Add

func (q *Queue) Add(function interface{}, arguments ...interface{}) *Queue

Add creates a call consisting of the given function and the given arguments and adds it to the call chain.

The special argument PIPE is a placeholder for the return values of the previous call in the chain (minus returned errors).

The number and type signature of the arguments and piped return values must match with the receiving function.

More about valid queues: see Check() More about function calling: see Run() and Fallback()

func (*Queue) AddNamed

func (q *Queue) AddNamed(name string, function interface{}, arguments ...interface{}) *Queue

AddNamed behaves like Add, but names the call with the given name. This is useful for logging and debugging

func (*Queue) Check

func (q *Queue) Check() (err error)

Check checks if the function signatures and argument types match and returns any errors

func (*Queue) CheckAndRun

func (q *Queue) CheckAndRun() (err error)

CheckAndRun first runs Check() to see, if there are any type errors in the function signatures or arguments and returns them. Without such errors, it then calls Run()

func (*Queue) LogDebugTo

func (q *Queue) LogDebugTo(logTarget io.Writer) *Queue

LogDebugTo logs debugging information to the given io.Writer with the given prefix

LogDebugTo() is an alternative to LogErrorsTo() and they should no be called both, because they are both changing the logging target and verbosity.

If more than one logging setter is called, only the last one has any effect.

func (*Queue) LogErrorsTo

func (q *Queue) LogErrorsTo(logTarget io.Writer) *Queue

LogErrorsTo logs errors and panics to the given io.Writer with the given prefix

LogErrorsTo() is an alternative to LogDebugTo() and they should no be called both, because they are both changing the logging target and verbosity.

If more than one logging setter is called, only the last one has any effect.

func (*Queue) Name

func (q *Queue) Name() string

func (*Queue) OnError

func (q *Queue) OnError(handler ErrHandler) *Queue

OnError sets the errHandler and may be chained.

If OnError() is called multiple times, only the last call has any effect.

func (*Queue) Queue

func (q *Queue) Queue() *Queue

func (*Queue) Run

func (q *Queue) Run() (err error)

Run runs the function queue.

In the run, every function in the queue is called with its arguments. If one of the arguments is PIPE, PIPE is replaced by the returned values of previous calls.

If the last return value of a function is of type error, it value is skipped when piping to the next function and the error is checked.

If the error is not nil, the ErrHandler of the Queue is called. If the ErrHandler returns nil, the next function is called. If it returns an error, the queue is stopped and the error is returned.

The default ErrHandler is STOP, which will stop the run on the first error.

If there are any errors with the given function types and arguments, the errors will no be very descriptive. In this cases use CheckAndRun() to see if there are any errors in the function or argument types or use LogDebugTo to get detailed debugging informations.

Since no arguments are saved inside the queue, a queue might be run multiple times.

func (*Queue) SetName

func (q *Queue) SetName(name string) *Queue

func (*Queue) Sub

func (q *Queue) Sub(feededQs ...Queuer) *Queue

func (*Queue) Tee

func (q *Queue) Tee(function interface{}, arguments ...interface{}) *Queue

Tee allows piping of the same return value to different function calls.

The return values from the given function are (apart from errors) discarded. If any of the arguments is the placeholder PIPE, it will be replaced by the return values of the previous regular call.

If the tee call returns an error, it will be passed to the error handler of the queue and may stop the next regular call.

If any check is performed on the queue, the tee calls are checked as well.

To tee into other queues, use TeeAndRun or TeeAndFallback

func (*Queue) TeeAndCheckAndFallback

func (q *Queue) TeeAndCheckAndFallback(feededQs ...Queuer) *Queue

TeeAndCheckAndFallback tees the given queues and in the run checks them before running the Fallback method

func (*Queue) TeeAndCheckAndRun

func (q *Queue) TeeAndCheckAndRun(feededQs ...Queuer) *Queue

TeeAndCheckAndRun tees the given queues and in the run checks them before running the Run method

func (*Queue) TeeAndFallback

func (q *Queue) TeeAndFallback(feededQs ...Queuer) *Queue

TeeAndFallback works like TeeAndRun but runs the target queues via Fallback(). The position returned by the particular Fallback() call on the target queue is discarded.

func (*Queue) TeeAndRun

func (q *Queue) TeeAndRun(feededQs ...Queuer) *Queue

TeeAndRun allows piping of the same return value to different queues.

The first call in each target queue should have the placeholder argument PIPE. When the main queue is run, each target queue is run via Run().

If the Run() call of a target queue returns an error, it will be passed to the error handler of the main queue and immediately stop further processing (of other target queues and the next regular call).

To be chainable, TeeAndRun returns the main queue.

func (*Queue) TeeNamed

func (q *Queue) TeeNamed(name string, function interface{}, arguments ...interface{}) *Queue

TeeNamed is like Tee, but allows a name to be assigned to the call for logging and error handling.

type Queuer

type Queuer interface {
	Queue() *Queue
}

Directories

Path Synopsis
_examples
q
Package q provides shortcuts for the package at http://github.com/go-on/queue It requires Go >= 1.1.
Package q provides shortcuts for the package at http://github.com/go-on/queue It requires Go >= 1.1.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL