GoErrors - Easy error informations and stack traces for Go with rich informations and a Try/Catch/Finally mechanism.
It's a package that's allow you to make Go errors more comprehensive, more featured and easier to use. This project is the realisation of the idea introduced by the GIST try-catch-finally.go.
- Verbose with stack trace
- Ready for Try/Catch/Finally paradigm
- Extensible with your own error
- Error hierarchy
- Entirely customizable
go get github.com/corebreaker/goerrors
A normal error is just an interface. But here we added an extended interface IError which can transport other infomations.
Therefore to add informations, the standard error (error
interface) is «decorated» with informations and so
we obtain a new error value. These informations are:
- stack trace informations (files + lines)
- additionnal customized string
- optionnal error code
When the Error
method is called, all these informations will be formated in a string.
This type of error gives you rich informations on an error without using panic
function (even if with a panic, you
could show your additionnal infomations).
// for example, let's open a file
func OpenMyFile() (*os.File, error) {
file, err := os.Open("MyFile.txt")
if err != nil {
// Here, we decorate the error
return nil, goerrors.DecorateError(err)
}
return file, nil
}
Then, we can panic
this decorated error or simply print it, like this:
func main() {
// First we must enable the debug mode to activate the stacktrace
goerrors.SetDebug(true)
// Use a function that use the `goerrors` package
file, err := OpenMyFile()
// Check the error
if err != nil {
// Show the error
fmt.Println(err)
// Terminate
return
}
// …
}
You will see some thing like this:
github.com/corebreaker/goerrors.tStandardError: open MyFile.txt: no such file or directory
github.com/corebreaker/goerrors.(*GoError).Init (/home/frederic/go/src/github.com/corebreaker/goerrors/errors.go:219)
github.com/corebreaker/goerrors.DecorateError (/home/frederic/go/src/github.com/corebreaker/goerrors/standard.go:51)
main.OpenMyFile (/home/frederic/.local/share/data/liteide/liteide/goplay.go:13)
main.main (/home/frederic/.local/share/data/liteide/liteide/goplay.go:24)
------------------------------------------------------------------------------
Plus, this library uses the panic()
function, the recover()
function and the defer
instruction,
as a Throw and a Try/Catch/Finally mechanisms and can be used like this:
goerrors.Try(func(err goerrors.IError) error {
// Try block
}, func(err goerrors.IError) error {
// Catch block
}, func(err goerrors.IError) error {
// Finally block
})
The error passed in Try
block is the error which called the Try
method:
theError := goerrors.MakeError("the error in `Try` block")
theError.Try(func(err goerrors.IError) error {
// Here err === theError
return nil
})
In the case in using the Try
function in the GoError package, the error passed as argument is an error created by the
call the Try
function. Then, that error can be customized with the GoError API.
Actually, returning an error in the Try
block is a Throw
, and a Go panic
call is too like a throw but there is
a panic
-like function for keeping Try/Catch formalism, the Raise
function, used like that:
goerrors.Try(func(err goerrors.IError) error {
if aCondition {
// `Raise` call
goerrors.Raise("an error")
}
// Do something
return nil
}, func(err goerrors.IError) error {
// Catch block
}, func(err goerrors.IError) error {
// Finally block
})
At last, all errors generated by GoError have a Raise
method. So, you can throw an error like that:
goerrors.Try(func(err goerrors.IError) error {
if aCondition {
// `Raise` method call
goerrors.MakeError("an error").Raise()
}
// Do something
return nil
}, func(err goerrors.IError) error {
// Catch block
}, func(err goerrors.IError) error {
// Finally block
})
package main
import (
"fmt"
gerr "github.com/corebreaker/goerrors"
)
// A function which return checked quotient
func my_func(i, j int) (int, error) {
if j == 0 {
return 0, gerr.MakeError("Division by zero")
}
return i / j, nil
}
// Main function
func main() {
// Activate stack trace
gerr.SetDebug(true)
i, j := 1, 0
// Call checked function
q, err := my_func(i, j)
if err != nil {
fmt.Println(err)
return
}
// Here, in this example, this code won't never be executed
fmt.Print(i, "/", j, "=", q)
}
This will show:
StandardError: Division by zero
main.my_func (/projects/go/prototype/main.go:11)
main.main (/projects/go/prototype/main.go:23)
------------------------------------------------------------------------------
package main
import (
"fmt"
"os"
gerr "github.com/corebreaker/goerrors"
)
// A function which open a file
func openFile(name string) (*os.File, error) {
f, err := os.Open(name)
// Decorate the opening error
if err != nil {
return nil, gerr.DecorateError(err)
}
return f, nil
}
// A function which read one byte in the opened file
func readFile(f *os.File) (byte, error) {
var b [1]byte
n, err := f.Read(b[:])
// Decorate the read error
if err != nil {
return 0, gerr.DecorateError(err)
}
// Return custom error
if n == 0 {
return 0, gerr.MakeError("No data to read")
}
return b[0], nil
}
// Main function
func main() {
// Activate stack trace
gerr.SetDebug(true)
// Call the checked open function
f, err := openFile("a_file.txt")
if err != nil {
fmt.Println(err)
return
}
// Here, in this example, this code won't never be executed if the file can't be opened
defer f.Close()
_, err = readFile(f)
}
This will show:
StandardError: open a_file.txt: no such file or directory
main.open_file (/projects/go/src/github.com/corebreaker/goerrors.go:15)
main.main (/projects/go/src/github.com/corebreaker/goerrors.go:46)
------------------------------------------------------------------------------
package main
import (
"fmt"
gerr "github.com/corebreaker/goerrors"
)
type ErrorBase struct{ gerr.GoError }
type ErrorA struct{ ErrorBase }
type ErrorB struct{ ErrorBase }
type ChildError struct{ ErrorA }
func newErrorBase() gerr.IError {
err := &ErrorBase{}
return err.Init(err, "message from Error base", nil, nil, 0)
}
func newErrorB() gerr.IError {
err := &ErrorB{}
return err.Init(err, "message from Error B", nil, nil, 0)
}
func newChildError() gerr.IError {
err := &ChildError{}
return err.Init(err, "message from Child Error", nil, nil, 0)
}
// A function which raise and try to catch the error which is not in the same hierarchy
func myFunc() () {
_ = newErrorB().Try(func(err gerr.IError) error {
newChildError().Raise()
return nil
}, func(err gerr.IError) error {
// This catch block will not called because ErrorB is not in the same error hierarchy of ChildError
return nil
}, nil)
}
// Main function
func main() {
_ = newErrorBase().Try(func(err gerr.IError) error {
myFunc()
return nil
}, func(err gerr.IError) error {
fmt.Println("Catched error:", err)
return nil
}, nil)
}
This will show:
Catched error: main.ChildError: message from Child Error