Skip to content

Commit

Permalink
More readme & gofmt
Browse files Browse the repository at this point in the history
  • Loading branch information
bastianrob committed Jul 14, 2019
1 parent 9b9af2c commit 9037595
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 190 deletions.
97 changes: 84 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,85 @@

With this package, you can chain a series of functions to build a pipeline

### Example 1
## Using thennable for error handling / control flow

Consider the following example

```go
func main() {
resultOfDoingThis, err := DoThis()
if err != nil {
//log the error
return
}

resultOfDoingThat err := DoThat(resultOfDoingThis)
if err != nil {
//log the error
return
}

resultOfDoingAnother err := DoAnother(resultOfDoingThat)
if err != nil {
//log the error
return
}
}
```

The above example is the idiomatic way of error handling in go.
Some like it, but most hate it because we need to manually check and return from error one by one.

Now consider the following example using thennable

```go
func main() {
result, err := thennable.Start().
Then(DoThis).
Then(DoThat).
Then(DoAnother).
End()
}
```

In the above example, `err` will either be from `DoThis`, `DoThat`, or `DoAnother` function.

* `DoThat` and `DoAnother` won't be executed if `DoThis` produce an error.
* `DoAnother` won't be executed if `DoThat` produce an error.
* Or you can bypass the error and keep running next function by setting `BreakOnError(true)` in the thennable pipeline. ()

To handle error can either write extra lines like:

```go
if err != nil {
//log the error
return
}
```

or better yet, you can use the built in `Handle` function so the code will looks like:

```go
func GlobalErrorHandler(err error) {
log.Println(err)
}

func main() {
result, err := thennable.Start().
Then(DoThis).
Then(DoThat).
Then(DoAnother).
Handle(GlobalErrorHandler)
End()
}
```

## Example 1

```go
import (
"fmt"
"thennable"
thennable "github.com/bastianrob/go-thennable"
)

func AddOne(num int) (int, error) {
Expand All @@ -16,9 +89,9 @@ func AddOne(num int) (int, error) {

func Decide(num int) (string, error) {
if num == 1 {
return "one"
return "one", nil
else {
return "not one"
return "not one", nil
}
}

Expand All @@ -33,12 +106,12 @@ func main() {
}
```
### Example 2: Inlining
## Example 2: Inlining
```go
import (
"fmt"
"thennable"
thennable "github.com/bastianrob/go-thennable"
)

func main() {
Expand All @@ -55,13 +128,13 @@ func main() {
}
```
### Example 3: Error Occurred
## Example 3: Error Occurred
```go
import (
"fmt"
"errors"
"thennable"
thennable "github.com/bastianrob/go-thennable"
)

func AddOne(num int) (int, error) {
Expand All @@ -83,14 +156,13 @@ func main() {
}
```
### Example 4: Recover from error
## Example 4: Recover from error
```go
import (
"fmt"
"errors"
"thennable"
thennable "github.com/bastianrob/go-thennable"
)

func AddOne(num int) (int, error) {
Expand All @@ -108,9 +180,8 @@ func main() {
Then(AddOne). //8 + 1
Then(AddOne). //9 + 1
End()

fmt.Printf("Res: %v, Str: %s, Err: %v", result, str, err)
//Res: [10], Err: nil
}
```
138 changes: 69 additions & 69 deletions thennable.go
Original file line number Diff line number Diff line change
@@ -1,116 +1,116 @@
package thennable

import (
"errors"
"reflect"
"errors"
"reflect"
)

//Error collection
var (
ErrNotFunction = errors.New("Argument supplied must be a function")
ErrNoErrorHandling = errors.New("Function last return value must be an error")
ErrNotFunction = errors.New("Argument supplied must be a function")
ErrNoErrorHandling = errors.New("Function last return value must be an error")
)

/*
IThennable is an interface for thennable
The main purpose to to be able to chain execution of functions
*/
type IThennable interface {
BreakOnError(bool) IThennable
Supply(...interface{}) IThennable
Then(interface{}) IThennable
Handle(FErrorHandler) IThennable
End() ([]interface{}, error)
BreakOnError(bool) IThennable
Supply(...interface{}) IThennable
Then(interface{}) IThennable
Handle(FErrorHandler) IThennable
End() ([]interface{}, error)
}

//FErrorHandler function
type FErrorHandler func(error)

//private implementation of IThennable
type thennable struct {
throw error
state []reflect.Value
runnable reflect.Value
breakOnError bool
throw error
state []reflect.Value
runnable reflect.Value
breakOnError bool
}

//Start creating a new IThennable with and initial value/state
func Start(params ...interface{}) IThennable {
state := []reflect.Value{}
for _, param := range params {
state = append(state, reflect.ValueOf(param))
}
return &thennable{state: state, breakOnError: true}
state := []reflect.Value{}
for _, param := range params {
state = append(state, reflect.ValueOf(param))
}
return &thennable{state: state, breakOnError: true}
}

//Runnable function is a function that have at least error return value
func newRunnable(fn interface{}) (runnable reflect.Value, err error) {
t := reflect.TypeOf(fn)
if t.Kind() != reflect.Func {
return runnable, ErrNotFunction
}

out := t.NumOut()
if out <= 0 || t.Out(out-1).Name() != "error" {
return runnable, ErrNoErrorHandling
}

return reflect.ValueOf(fn), nil
}
t := reflect.TypeOf(fn)
if t.Kind() != reflect.Func {
return runnable, ErrNotFunction
}

out := t.NumOut()
if out <= 0 || t.Out(out-1).Name() != "error" {
return runnable, ErrNoErrorHandling
}

return reflect.ValueOf(fn), nil
}

//BreakOnError stops function chaining when error occured
func (tnb *thennable) BreakOnError(breakOnError bool) IThennable {
tnb.breakOnError = breakOnError
return tnb
tnb.breakOnError = breakOnError
return tnb
}

//Supply next runnable function with a value
func (tnb *thennable) Supply(params ...interface{}) IThennable {
state := []reflect.Value{}
for _, param := range params {
state = append(state, reflect.ValueOf(param))
}
return &thennable{state: state, breakOnError: tnb.breakOnError}
state := []reflect.Value{}
for _, param := range params {
state = append(state, reflect.ValueOf(param))
}

return &thennable{state: state, breakOnError: tnb.breakOnError}
}

func (tnb *thennable) Then(next interface{}) IThennable {
if tnb.breakOnError && tnb.throw != nil {
return tnb
}
//if not runnable func
runnable, err := newRunnable(next)
if err != nil {
tnb.state, tnb.throw = make([]reflect.Value, 0), err
return tnb
}
//else
result := runnable.Call(tnb.state)
retCount := len(result)
errIdx := retCount - 1

tnb.state, tnb.throw = result[0:errIdx], nil
lastOutput := result[errIdx].Interface()
if lastOutput != nil {
tnb.throw = lastOutput.(error)
}
return tnb
if tnb.breakOnError && tnb.throw != nil {
return tnb
}

//if not runnable func
runnable, err := newRunnable(next)
if err != nil {
tnb.state, tnb.throw = make([]reflect.Value, 0), err
return tnb
}

//else
result := runnable.Call(tnb.state)
retCount := len(result)
errIdx := retCount - 1

tnb.state, tnb.throw = result[0:errIdx], nil
lastOutput := result[errIdx].Interface()
if lastOutput != nil {
tnb.throw = lastOutput.(error)
}

return tnb
}

//Handle error
func (tnb *thennable) Handle(handle FErrorHandler) IThennable {
handle(tnb.throw)
return tnb
handle(tnb.throw)
return tnb
}

//End execution and collect the result
func (tnb *thennable) End() ([]interface{}, error) {
end := make([]interface{}, 0)
for _, state := range tnb.state {
end = append(end, state.Interface())
}
return end, tnb.throw
}
end := make([]interface{}, 0)
for _, state := range tnb.state {
end = append(end, state.Interface())
}
return end, tnb.throw
}
Loading

0 comments on commit 9037595

Please sign in to comment.