summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--builds/roadrunner-1.8.4-darwin-amd64.zipbin0 -> 9304639 bytes
-rw-r--r--builds/roadrunner-1.8.4-freebsd-amd64.zipbin0 -> 5079233 bytes
-rw-r--r--builds/roadrunner-1.8.4-linux-amd64.tar.gzbin0 -> 5103405 bytes
-rw-r--r--builds/roadrunner-1.8.4-unknown-musl-amd64.zipbin0 -> 5127095 bytes
-rw-r--r--builds/roadrunner-1.8.4-windows-amd64.zipbin0 -> 5106658 bytes
-rwxr-xr-xerrors/debug_cap.go10
-rwxr-xr-xerrors/debug_stack.go116
-rwxr-xr-xerrors/debug_test.go74
-rwxr-xr-xerrors/errors.go219
-rwxr-xr-xerrors/errors_test.go177
-rwxr-xr-xerrors/go.mod1
-rwxr-xr-xerrors/marshal.go103
12 files changed, 700 insertions, 0 deletions
diff --git a/builds/roadrunner-1.8.4-darwin-amd64.zip b/builds/roadrunner-1.8.4-darwin-amd64.zip
new file mode 100644
index 00000000..64d49772
--- /dev/null
+++ b/builds/roadrunner-1.8.4-darwin-amd64.zip
Binary files differ
diff --git a/builds/roadrunner-1.8.4-freebsd-amd64.zip b/builds/roadrunner-1.8.4-freebsd-amd64.zip
new file mode 100644
index 00000000..4f53b56d
--- /dev/null
+++ b/builds/roadrunner-1.8.4-freebsd-amd64.zip
Binary files differ
diff --git a/builds/roadrunner-1.8.4-linux-amd64.tar.gz b/builds/roadrunner-1.8.4-linux-amd64.tar.gz
new file mode 100644
index 00000000..24ba9249
--- /dev/null
+++ b/builds/roadrunner-1.8.4-linux-amd64.tar.gz
Binary files differ
diff --git a/builds/roadrunner-1.8.4-unknown-musl-amd64.zip b/builds/roadrunner-1.8.4-unknown-musl-amd64.zip
new file mode 100644
index 00000000..c203106e
--- /dev/null
+++ b/builds/roadrunner-1.8.4-unknown-musl-amd64.zip
Binary files differ
diff --git a/builds/roadrunner-1.8.4-windows-amd64.zip b/builds/roadrunner-1.8.4-windows-amd64.zip
new file mode 100644
index 00000000..3996d36b
--- /dev/null
+++ b/builds/roadrunner-1.8.4-windows-amd64.zip
Binary files differ
diff --git a/errors/debug_cap.go b/errors/debug_cap.go
new file mode 100755
index 00000000..2c8a2f78
--- /dev/null
+++ b/errors/debug_cap.go
@@ -0,0 +1,10 @@
+// +build !debug
+
+package errors
+
+import "bytes"
+
+type stack struct{}
+
+func (e *Error) populateStack() {}
+func (e *Error) printStack(*bytes.Buffer) {}
diff --git a/errors/debug_stack.go b/errors/debug_stack.go
new file mode 100755
index 00000000..fa77ddc8
--- /dev/null
+++ b/errors/debug_stack.go
@@ -0,0 +1,116 @@
+// +build debug
+
+package errors
+
+import (
+ "bytes"
+ "fmt"
+ "runtime"
+ "strings"
+)
+
+type stack struct {
+ callers []uintptr
+ // TODO(adg): add time of creation
+}
+
+func (e *Error) populateStack() {
+ e.callers = callers()
+
+ e2, ok := e.Err.(*Error)
+ if !ok {
+ return
+ }
+
+ i := 0
+
+ ok = false
+ for ; i < len(e.callers) && i < len(e2.callers); i++ {
+ // check for similar
+ if e.callers[len(e.callers)-1-i] != e2.callers[len(e2.callers)-1-i] {
+ break
+ }
+ ok = true
+ }
+
+ if ok { //we have common PCs
+ e2Head := e2.callers[:len(e2.callers)-i]
+ eTail := e.callers
+
+ e.callers = make([]uintptr, len(e2Head)+len(eTail))
+
+ copy(e.callers, e2Head)
+ copy(e.callers[len(e2Head):], eTail)
+
+ e2.callers = nil
+ }
+}
+
+// frame returns the nth frame, with the frame at top of stack being 0.
+func frame(callers []uintptr, n int) runtime.Frame {
+ frames := runtime.CallersFrames(callers)
+ var f runtime.Frame
+ for i := len(callers) - 1; i >= n; i-- {
+ var ok bool
+ f, ok = frames.Next()
+ if !ok {
+ break
+ }
+ }
+ return f
+}
+
+func (e *Error) printStack(b *bytes.Buffer) {
+ c := callers()
+
+ var prev string
+ var diff bool
+ for i := 0; i < len(e.callers); i++ {
+ pc := e.callers[len(e.callers)-i-1] // get current PC
+ fn := runtime.FuncForPC(pc) // get function by pc
+ name := fn.Name()
+
+ if !diff && i < len(c) {
+ ppc := c[len(c)-i-1]
+ pname := runtime.FuncForPC(ppc).Name()
+ if name == pname {
+ continue
+ }
+ diff = true
+ }
+
+ if name == prev {
+ continue
+ }
+
+ trim := 0
+ for {
+ j := strings.IndexAny(name[trim:], "./")
+ if j < 0 {
+ break
+ }
+ if !strings.HasPrefix(prev, name[:j+trim]) {
+ break
+ }
+ trim += j + 1 // skip over the separator
+ }
+
+ // Do the printing.
+ appendStrToBuf(b, Separator)
+ file, line := fn.FileLine(pc)
+ fmt.Fprintf(b, "%v:%d: ", file, line)
+ if trim > 0 {
+ b.WriteString("...")
+ }
+ b.WriteString(name[trim:])
+
+ prev = name
+ }
+}
+
+func callers() []uintptr {
+ var stk [64]uintptr
+ const skip = 4
+ n := runtime.Callers(skip, stk[:])
+ return stk[:n]
+}
diff --git a/errors/debug_test.go b/errors/debug_test.go
new file mode 100755
index 00000000..bc866bc8
--- /dev/null
+++ b/errors/debug_test.go
@@ -0,0 +1,74 @@
+// +build debug
+
+package errors
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+var errorLines = strings.Split(strings.TrimSpace(`
+ .*/errors/debug_test.go:\d+: github.com/ValeryPiashchynski/errors.func1:
+ .*/errors/debug_test.go:\d+: ...T.func2:
+ .*/errors/debug_test.go:\d+: ...func3:
+ .*/errors/debug_test.go:\d+: ...func4: func2 invoke func3: Network error:
+ func4 operation: error in action
+`), "\n")
+
+var errorLineREs = make([]*regexp.Regexp, len(errorLines))
+
+func init() {
+ for i, s := range errorLines {
+ errorLineREs[i] = regexp.MustCompile(fmt.Sprintf("^%s", s))
+ }
+}
+
+func TestsDebug(t *testing.T) {
+ got := printErr(t, func1())
+ lines := strings.Split(got, "\n")
+ for i, re := range errorLineREs {
+ if i >= len(lines) {
+ // Handled by line number check.
+ break
+ }
+ if !re.MatchString(lines[i]) {
+ t.Errorf("error does not match at line %v, got:\n\t%q\nwant:\n\t%q", i, lines[i], re)
+ }
+ }
+ if got, want := len(lines), len(errorLines); got != want {
+ t.Errorf("got %v lines of errors, want %v", got, want)
+ }
+}
+
+type T struct{}
+
+func printErr(t *testing.T, err error) string {
+ return err.Error()
+}
+
+func func1() error {
+ var t T
+ return t.func2()
+}
+
+func (T) func2() error {
+ o := Op("func2 invoke func3")
+ return E(o, func3())
+}
+
+func func3() error {
+ return func4()
+}
+
+func func4() error {
+ o := Op("func4 operation")
+ return E(o, Network, Str("error in action"))
+}
+
+///Users/0xdev/Projects/repo/errors/debug_test.go:53: github.com/ValeryPiashchynski/errors.func1:
+///Users/0xdev/Projects/repo/errors/debug_test.go:58: ...T.func2:
+///Users/0xdev/Projects/repo/errors/debug_test.go:62: ...func3:
+///Users/0xdev/Projects/repo/errors/debug_test.go:67: ...func4: func2 invoke func3: Network error:
+//func4 operation: error in action
diff --git a/errors/errors.go b/errors/errors.go
new file mode 100755
index 00000000..def408d8
--- /dev/null
+++ b/errors/errors.go
@@ -0,0 +1,219 @@
+package errors
+
+import (
+ "bytes"
+ "encoding"
+ "errors"
+ "fmt"
+ "log"
+ "runtime"
+)
+
+type Error struct {
+ Op Op
+ Kind Kind
+ Err error
+
+ // Stack information
+ stack
+}
+
+func (e *Error) isZero() bool {
+ return e.Op == "" && e.Kind == 0 && e.Err == nil
+}
+
+var (
+ _ error = (*Error)(nil)
+ _ encoding.BinaryUnmarshaler = (*Error)(nil)
+ _ encoding.BinaryMarshaler = (*Error)(nil)
+)
+
+// Op describes an operation
+type Op string
+
+// separator -> new line plus tabulator to intend error if previuos not nil
+var Separator = ":\n\t"
+
+type Kind uint8
+
+// Kinds of errors.
+const (
+ Undefined Kind = iota // Undefined error.
+ Network
+ Other
+ Test
+)
+
+func (k Kind) String() string {
+ switch k {
+ case Undefined:
+ return "UNDEF"
+ case Network:
+ return "Network error"
+ case Other:
+ return "Other"
+ case Test:
+ return "Test"
+
+ }
+ return "unknown error kind"
+}
+
+// E builds an error value from its arguments.
+func E(args ...interface{}) error {
+ e := &Error{}
+ if len(args) == 0 {
+ msg := "errors.E called with 0 args"
+ _, file, line, ok := runtime.Caller(1)
+ if ok {
+ msg = fmt.Sprintf("%v - %v:%v", msg, file, line)
+ }
+ e.Err = errors.New(msg)
+ }
+
+ for _, arg := range args {
+ switch arg := arg.(type) {
+ case Op:
+ e.Op = arg
+ case string:
+ e.Err = Str(arg)
+ case Kind:
+ e.Kind = arg
+ case *Error:
+ // Make a copy
+ eCopy := *arg
+ e.Err = &eCopy
+ case error:
+ e.Err = arg
+ // add map map[string]string
+ default:
+ _, file, line, _ := runtime.Caller(1)
+ log.Printf("errors.E: bad call from %s:%d: %v", file, line, args)
+ return Errorf("unknown type %T, value %v in error call", arg, arg)
+ }
+ }
+
+ // Populate stack information
+ e.populateStack()
+
+ prev, ok := e.Err.(*Error)
+ if !ok {
+ return e
+ }
+
+ if prev.Kind == e.Kind {
+ prev.Kind = Undefined
+ }
+
+ if e.Kind == Undefined {
+ e.Kind = prev.Kind
+ prev.Kind = Undefined
+ }
+ return e
+}
+
+func (e *Error) Error() string {
+ b := new(bytes.Buffer)
+ e.printStack(b)
+ if e.Op != "" {
+ appendStrToBuf(b, ": ")
+ b.WriteString(string(e.Op))
+ }
+
+ if e.Kind != 0 {
+ appendStrToBuf(b, ": ")
+ b.WriteString(e.Kind.String())
+ }
+ if e.Err != nil {
+ if prevErr, ok := e.Err.(*Error); ok {
+ if !prevErr.isZero() {
+ // indent - separator
+ appendStrToBuf(b, Separator)
+ b.WriteString(e.Err.Error())
+ }
+ } else {
+ appendStrToBuf(b, ": ")
+ b.WriteString(e.Err.Error())
+ }
+ }
+ if b.Len() == 0 {
+ return "no error"
+ }
+ return b.String()
+}
+
+// errors.New
+func Str(text string) error {
+ return &errorString{text}
+}
+
+type errorString struct {
+ s string
+}
+
+func (e *errorString) Error() string {
+ return e.s
+}
+
+func Errorf(format string, args ...interface{}) error {
+ return &errorString{fmt.Sprintf(format, args...)}
+}
+
+func Match(err1, err2 error) bool {
+ e1, ok := err1.(*Error)
+ if !ok {
+ return false
+ }
+ e2, ok := err2.(*Error)
+ if !ok {
+ return false
+ }
+ if e1.Op != "" && e2.Op != e1.Op {
+ return false
+ }
+ if e1.Kind != Undefined && e2.Kind != e1.Kind {
+ return false
+ }
+ if e1.Err != nil {
+ if _, ok := e1.Err.(*Error); ok {
+ return Match(e1.Err, e2.Err)
+ }
+ if e2.Err == nil || e2.Err.Error() != e1.Err.Error() {
+ return false
+ }
+ }
+ return true
+}
+
+// Is reports whether err is an *Error of the given Kind
+func Is(kind Kind, err error) bool {
+ e, ok := err.(*Error)
+ if !ok {
+ return false
+ }
+ if e.Kind != Undefined {
+ return e.Kind == kind
+ }
+ if e.Err != nil {
+ return Is(kind, e.Err)
+ }
+ return false
+}
+
+// Do smt with no care about result (and panics)
+func SafelyDo(work func()) {
+ defer func() {
+ if err := recover(); err != nil {
+ log.Printf("work failed: %s", err)
+ }
+ }()
+
+ work()
+}
+
+func appendStrToBuf(b *bytes.Buffer, str string) {
+ if b.Len() == 0 {
+ return
+ }
+ b.WriteString(str)
+}
diff --git a/errors/errors_test.go b/errors/errors_test.go
new file mode 100755
index 00000000..50d3d422
--- /dev/null
+++ b/errors/errors_test.go
@@ -0,0 +1,177 @@
+// +build !debug
+
+package errors
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "testing"
+)
+
+func TestDebug(t *testing.T) {
+ // Test with -tags debug to run the tests in debug_test.go
+ cmd := exec.Command("go", "test", "-tags", "prod")
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ t.Fatalf("external go test failed: %v", err)
+ }
+}
+
+func TestMarshal(t *testing.T) {
+ // Single error. No user is set, so we will have a zero-length field inside.
+ e1 := E(Op("Get"), Network, "caching in progress")
+
+ // Nested error.
+ e2 := E(Op("Read"), Undefined, e1)
+
+ b := MarshalError(e2)
+ e3 := UnmarshalError(b)
+
+ in := e2.(*Error)
+ out := e3.(*Error)
+
+ // Compare elementwise.
+ if in.Op != out.Op {
+ t.Errorf("expected Op %q; got %q", in.Op, out.Op)
+ }
+ if in.Kind != out.Kind {
+ t.Errorf("expected kind %d; got %d", in.Kind, out.Kind)
+ }
+ // Note that error will have lost type information, so just check its Error string.
+ if in.Err.Error() != out.Err.Error() {
+ t.Errorf("expected Err %q; got %q", in.Err, out.Err)
+ }
+}
+
+func TestSeparator(t *testing.T) {
+ defer func(prev string) {
+ Separator = prev
+ }(Separator)
+ Separator = ":: "
+
+ // Single error. No user is set, so we will have a zero-length field inside.
+ e1 := E(Op("Get"), Network, "network error")
+
+ // Nested error.
+ e2 := E(Op("Get"), Network, e1)
+
+ want := "Get: Network error:: Get: network error"
+ if errorAsString(e2) != want {
+ t.Errorf("expected %q; got %q", want, e2)
+ }
+}
+
+func TestDoesNotChangePreviousError(t *testing.T) {
+ err := E(Network)
+ err2 := E(Op("I will NOT modify err"), err)
+
+ expected := "I will NOT modify err: Network error"
+ if errorAsString(err2) != expected {
+ t.Fatalf("Expected %q, got %q", expected, err2)
+ }
+ kind := err.(*Error).Kind
+ if kind != Network {
+ t.Fatalf("Expected kind %v, got %v", Network, kind)
+ }
+}
+
+//func TestNoArgs(t *testing.T) {
+// defer func() {
+// err := recover()
+// if err == nil {
+// t.Fatal("E() did not panic")
+// }
+// }()
+// _ = E()
+//}
+
+type matchTest struct {
+ err1, err2 error
+ matched bool
+}
+
+const (
+ op = Op("Op")
+ op1 = Op("Op1")
+ op2 = Op("Op2")
+)
+
+var matchTests = []matchTest{
+ // Errors not of type *Error fail outright.
+ {nil, nil, false},
+ {io.EOF, io.EOF, false},
+ {E(io.EOF), io.EOF, false},
+ {io.EOF, E(io.EOF), false},
+ // Success. We can drop fields from the first argument and still match.
+ {E(io.EOF), E(io.EOF), true},
+ {E(op, Other, io.EOF), E(op, Other, io.EOF), true},
+ {E(op, Other, io.EOF, "test"), E(op, Other, io.EOF, "test", "test"), true},
+ {E(op, Other), E(op, Other, io.EOF, "test", "test"), true},
+ {E(op), E(op, Other, io.EOF, "test", "test"), true},
+ // Failure.
+ {E(io.EOF), E(io.ErrClosedPipe), false},
+ {E(op1), E(op2), false},
+ {E(Other), E(Network), false},
+ {E("test"), E("test1"), false},
+ {E(fmt.Errorf("error")), E(fmt.Errorf("error1")), false},
+ {E(op, Other, io.EOF, "test", "test1"), E(op, Other, io.EOF, "test", "test"), false},
+ {E("test", Str("something")), E("test"), false}, // Test nil error on rhs.
+ // Nested *Errors.
+ {E(op1, E("test")), E(op1, "1", E(op2, "2", "test")), true},
+ {E(op1, "test"), E(op1, "1", E(op2, "2", "test")), false},
+ {E(op1, E("test")), E(op1, "1", Str(E(op2, "2", "test").Error())), false},
+}
+
+func TestMatch(t *testing.T) {
+ for _, test := range matchTests {
+ matched := Match(test.err1, test.err2)
+ if matched != test.matched {
+ t.Errorf("Match(%q, %q)=%t; want %t", test.err1, test.err2, matched, test.matched)
+ }
+ }
+}
+
+type kindTest struct {
+ err error
+ kind Kind
+ want bool
+}
+
+var kindTests = []kindTest{
+ //Non-Error errors.
+ {nil, Network, false},
+ {Str("not an *Error"), Network, false},
+
+ // Basic comparisons.
+ {E(Network), Network, true},
+ {E(Test), Network, false},
+ {E("no kind"), Network, false},
+ {E("no kind"), Other, false},
+
+ // Nested *Error values.
+ {E("Nesting", E(Network)), Network, true},
+ {E("Nesting", E(Test)), Network, false},
+ {E("Nesting", E("no kind")), Network, false},
+ {E("Nesting", E("no kind")), Other, false},
+}
+
+func TestKind(t *testing.T) {
+ for _, test := range kindTests {
+ got := Is(test.kind, test.err)
+ if got != test.want {
+ t.Errorf("Is(%q, %q)=%t; want %t", test.kind, test.err, got, test.want)
+ }
+ }
+}
+
+func errorAsString(err error) string {
+ if e, ok := err.(*Error); ok {
+ e2 := *e
+ e2.stack = stack{}
+ return e2.Error()
+ }
+ return err.Error()
+}
diff --git a/errors/go.mod b/errors/go.mod
new file mode 100755
index 00000000..60dac691
--- /dev/null
+++ b/errors/go.mod
@@ -0,0 +1 @@
+module github.com/48d90782/errors
diff --git a/errors/marshal.go b/errors/marshal.go
new file mode 100755
index 00000000..a13ec01f
--- /dev/null
+++ b/errors/marshal.go
@@ -0,0 +1,103 @@
+package errors
+
+import (
+ "encoding/binary"
+ "log"
+)
+
+func (e *Error) MarshalAppend(b []byte) []byte {
+ if e == nil {
+ return b
+ }
+
+ b = appendString(b, string(e.Op))
+
+ var tmp [16]byte
+ N := binary.PutVarint(tmp[:], int64(e.Kind))
+ b = append(b, tmp[:N]...)
+ b = MarshalErrorAppend(e.Err, b)
+ return b
+}
+
+func (e *Error) MarshalBinary() ([]byte, error) {
+ return e.MarshalAppend(nil), nil
+}
+
+func MarshalErrorAppend(err error, b []byte) []byte {
+ if err == nil {
+ return b
+ }
+ if e, ok := err.(*Error); ok {
+ b = append(b, 'E')
+ return e.MarshalAppend(b)
+ }
+ // Ordinary error.
+ b = append(b, 'e')
+ b = appendString(b, err.Error())
+ return b
+
+}
+
+func MarshalError(err error) []byte {
+ return MarshalErrorAppend(err, nil)
+}
+
+func (e *Error) UnmarshalBinary(b []byte) error {
+ if len(b) == 0 {
+ return nil
+ }
+ data, b := getBytes(b)
+ if data != nil {
+ e.Op = Op(data)
+ }
+ k, N := binary.Varint(b)
+ e.Kind = Kind(k)
+ b = b[N:]
+ e.Err = UnmarshalError(b)
+ return nil
+}
+
+func UnmarshalError(b []byte) error {
+ if len(b) == 0 {
+ return nil
+ }
+ code := b[0]
+ b = b[1:]
+ switch code {
+ case 'e':
+ var data []byte
+ data, b = getBytes(b)
+ if len(b) != 0 {
+ log.Printf("Unmarshal error: trailing bytes")
+ }
+ return Str(string(data))
+ case 'E':
+ var err Error
+ err.UnmarshalBinary(b)
+ return &err
+ default:
+ log.Printf("Unmarshal error: corrupt data %q", b)
+ return Str(string(b))
+ }
+}
+
+func appendString(b []byte, str string) []byte {
+ var tmp [16]byte
+ N := binary.PutUvarint(tmp[:], uint64(len(str)))
+ b = append(b, tmp[:N]...)
+ b = append(b, str...)
+ return b
+}
+
+func getBytes(b []byte) (data, remaining []byte) {
+ u, N := binary.Uvarint(b)
+ if len(b) < N+int(u) {
+ log.Printf("Unmarshal error: bad encoding")
+ return nil, nil
+ }
+ if N == 0 {
+ log.Printf("Unmarshal error: bad encoding")
+ return nil, b
+ }
+ return b[N : N+int(u)], b[N+int(u):]
+}