Sequitur is a simple way of handling errors in Go.
- Simple syntax, no more
if value, err = action(); err != nil {. - Each action must have a description which makes for user-friendly error messages.
- Automatic panic handling. If any
Actionpanics, it is treated as an error of typesequitur.ErrorPanic. - Logging can be done fairly easily using Logrus (more mechanisms to be added).
- Context handling. If context expires, sequence stops.
- Grouping of code into logical sequences
A Sequence is a series of Action that are executed.
Each Action is just a func() error
If any Action returns an error, the Sequence is stopped.
The Catch function of Sequence is called.
If there are no errors, the Then function of the Sequence is called.
import "github.com/fluxynet/sequitur"
//...
seq := sequitur.Linear() //currently only Linear sequence is supported
seq.Do("short action1 name", func() error {
//...
})
seq.Do("short action2 name", func() error {
//...
})
//executed if any action returned error
//name is one of the names above, e.g. short action1 name
//it could be used to provide a user friendly description of the error to the user
//it could even be passed via an i18n lib
//err can be used for logging or for logical branching
seq.Catch(func(name string, err error) {
//...
})
seq.Then(func() { //executed if no action returned error
//...
})import (
"fmt"
"github.com/fluxynet/sequitur"
"io/ioutil"
"log"
)
func main() {
var data []byte
seq := sequitur.Linear()
seq.Do("reading file from disk", func() error {
var err error
data, err = ioutil.ReadFile("foo.bar")
return err
})
seq.Do("writing file to disk", func() error {
return ioutil.WriteFile("bar.foo", data, 0644)
})
seq.Catch(func(name string, err error) {
fmt.Printf("An error occurred during %s.", name)
log.Println(err)
})
seq.Then(func() {
fmt.Println("Wrote %d bytes", len(data))
})
}import (
"errors"
"fmt"
"github.com/fluxynet/sequitur"
"net/http"
)
func myHandler(w http.ResponseWriter, r *http.Request) {
var input struct {
X int `json:"x"`
Y int `json:"y"`
}
var output struct {
Sum int `json:"sum"`
}
seq := sequitur.Linear()
var b []byte
sequence.Do("reading request body", func() error {
b, err = ioutil.ReadAll(r.Body)
return err
})
sequence.Do("decoding request body", func() error {
return json.Unmarshal(b, &input)
})
output.Sum = input.X + input.Y //no need to use action, does not yield error
var j []byte
sequence.Do("writing response", func() error {
j, err = json.Marshal(response)
return err
})
sequence.Then(func() {
w.Header().Set("Content-type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(j)
})
seq.Catch(func(name string, err error) {
msg := fmt.Sprintf("An error occurred during %s.", name, err.String())
log.Println(err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":"` + msg + `"}`))
})
}import (
"errors"
"fmt"
"github.com/fluxynet/sequitur"
"net/http"
)
func myHandler(w http.ResponseWriter, r *http.Request) {
var input struct {
X int `json:"x"`
Y int `json:"y"`
}
var output struct {
Sum int `json:"sum"`
}
seq := sequitur.Linear()
defer sequence.Catch(catchError(w, r)) //all my errors go here
var b []byte
sequence.Do("reading request body", func() error {
b, err = ioutil.ReadAll(r.Body)
return err
})
sequence.Do("decoding request body", func() error {
return json.Unmarshal(b, &input)
})
output.Sum = input.X + input.Y //no need to use action, does not yield error
var j []byte
sequence.Do("writing response", func() error {
j, err = json.Marshal(response)
return err
})
sequence.Then(func() {
w.Header().Set("Content-type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(j)
})
}
//some more 'elaborate' error handling
func catchError(w http.ResponseWriter, r *http.Request) sequitur.Consequence {
return func(name string, err error) {
var (
msg string
status int
)
log.Println(err)
switch err {
default:
msg = "Error when " + name
if strings.HasPrefix(err.Error(), "invalid") {
status = http.StatusBadRequest
} else {
status = http.StatusInternalServerError
}
case ErrorInvalidToken:
msg = "Invalid Token"
status = http.StatusForbidden
case ErrorNoToken:
msg = "You must login to proceed"
status = http.StatusForbidden
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
w.Write([]byte(`{"error":"` + msg + `"}`))
}
}var foo Foo
seq := sequitur.Linear()
//you can group logical operations together
seq.Do("reading foo from file", func() error {
data, err := ioutil.ReadFile("foo.bar")
//it is still okay to use normal error handling
if err != nil {
return err
}
return json.Unmarshal(data, &foo)
})
seq.Do("processing foo", fooProcessingFunc)
//...import (
"fmt"
"log"
"github.com/fluxynet/sequitur"
)
func main() {
var result string
seq.Do("letter a", func() error {
result += "a"
return nil
})
seq.Do("misbehave", func() error {
panic("foobar")
})
seq.Do("letter b", func() error {
result += "b"
return nil
})
seq.Catch(func(name string, err error) {
result += "z"
if err == sequitur.ErrPanic {
log.Println("Panic averted during: "+name)
} else {
log.Println(name, err)
}
})
seq.Then(func() {
result += "c"
})
fmt.Println(result) //az
}ctxC, cancel := context.WithCancel(context.Background())
cancel()
//cancel and the sequence will stop
//although an ongoing action will never yield
//think of it as a "critical section"seq := sequitur.WithLogrus(sequitur.Linear())
//or if using logrus instance
seq := sequitur.WithLogrus(sequitur.Linear(), myloggerInstance)This library is heavily inspired by a blog post from Martin Kühl entitled Rob Pike Reinvented Monads.