Adding upstream version 0.0~git20250501.cd50c6a.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
947813c282
commit
d8f2a7c92a
16 changed files with 3363 additions and 0 deletions
16
.build.yml
Normal file
16
.build.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
image: archlinux
|
||||
packages:
|
||||
- go
|
||||
sources:
|
||||
- https://github.com/go-ap/errors
|
||||
environment:
|
||||
GO111MODULE: 'on'
|
||||
tasks:
|
||||
- tests: |
|
||||
cd errors
|
||||
make test
|
||||
- coverage: |
|
||||
set -a +x
|
||||
cd errors && make coverage
|
||||
GIT_SHA=$(git rev-parse --verify HEAD)
|
||||
GIT_BRANCH=$(git name-rev --name-only HEAD)
|
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Gogland
|
||||
.idea/
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.so
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tools
|
||||
*.out
|
||||
*.coverprofile
|
||||
|
||||
*pkg
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Golang ActitvityPub
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
22
Makefile
Normal file
22
Makefile
Normal file
|
@ -0,0 +1,22 @@
|
|||
GO ?= go
|
||||
TEST := $(GO) test
|
||||
TEST_FLAGS ?= -v
|
||||
TEST_TARGET ?= ./...
|
||||
GO111MODULE=on
|
||||
PROJECT_NAME := $(shell basename $(PWD))
|
||||
|
||||
.PHONY: test coverage clean download
|
||||
|
||||
download:
|
||||
$(GO) mod download all
|
||||
|
||||
test: download
|
||||
$(TEST) $(TEST_FLAGS) $(TEST_TARGET)
|
||||
|
||||
coverage: TEST_TARGET := .
|
||||
coverage: TEST_FLAGS += -covermode=count -coverprofile $(PROJECT_NAME).coverprofile
|
||||
coverage: test
|
||||
|
||||
clean:
|
||||
$(RM) -v *.coverprofile
|
||||
|
6
README.md
Normal file
6
README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
[](https://raw.githubusercontent.com/go-ap/errors/master/LICENSE)
|
||||
[](https://builds.sr.ht/~mariusor/errors)
|
||||
[](https://codecov.io/gh/go-ap/errors)
|
||||
[](https://goreportcard.com/report/github.com/go-ap/errors)
|
||||
<!--[](https://www.codacy.com/app/go-ap/errors/dashboard)-->
|
85
decoding.go
Normal file
85
decoding.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"github.com/valyala/fastjson"
|
||||
)
|
||||
|
||||
func UnmarshalJSON(data []byte) ([]error, error) {
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
p := fastjson.Parser{}
|
||||
val, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := val.Get("errors")
|
||||
if v == nil {
|
||||
return nil, wrap(nil, "invalid errors array")
|
||||
}
|
||||
items := make([]error, 0)
|
||||
switch v.Type() {
|
||||
case fastjson.TypeArray:
|
||||
for _, v := range v.GetArray() {
|
||||
status := v.GetInt("status")
|
||||
localErr := errorFromStatus(status)
|
||||
if err := localErr.UnmarshalJSON([]byte(v.String())); err == nil {
|
||||
items = append(items, localErr)
|
||||
}
|
||||
}
|
||||
return items, err
|
||||
case fastjson.TypeObject:
|
||||
status := v.GetInt("status")
|
||||
localErr := errorFromStatus(status)
|
||||
if err := localErr.UnmarshalJSON([]byte(v.String())); err == nil {
|
||||
items = append(items, localErr)
|
||||
}
|
||||
case fastjson.TypeString:
|
||||
it := new(Err)
|
||||
it.m = string(data)
|
||||
items = append(items, it)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (e *Err) UnmarshalJSON(data []byte) error {
|
||||
if m := fastjson.GetString(data, "message"); len(m) > 0 {
|
||||
e.m = m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *notFound) UnmarshalJSON(data []byte) error {
|
||||
return n.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (m *methodNotAllowed) UnmarshalJSON(data []byte) error {
|
||||
return m.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (n *notValid) UnmarshalJSON(data []byte) error {
|
||||
return n.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (f *forbidden) UnmarshalJSON(data []byte) error {
|
||||
return f.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (n *notImplemented) UnmarshalJSON(data []byte) error {
|
||||
return n.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (b *badRequest) UnmarshalJSON(data []byte) error {
|
||||
return b.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (u *unauthorized) UnmarshalJSON(data []byte) error {
|
||||
return u.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (n *notSupported) UnmarshalJSON(data []byte) error {
|
||||
return n.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (t *timeout) UnmarshalJSON(data []byte) error {
|
||||
return t.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (b *badGateway) UnmarshalJSON(data []byte) error {
|
||||
return b.Err.UnmarshalJSON(data)
|
||||
}
|
||||
func (s *serviceUnavailable) UnmarshalJSON(data []byte) error {
|
||||
return s.Err.UnmarshalJSON(data)
|
||||
}
|
99
decoding_test.go
Normal file
99
decoding_test.go
Normal file
File diff suppressed because one or more lines are too long
166
errors.go
Normal file
166
errors.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Export a number of functions or variables from package errors.
|
||||
var (
|
||||
As = errors.As
|
||||
Is = errors.Is
|
||||
Unwrap = errors.Unwrap
|
||||
//Join = errors.Join
|
||||
)
|
||||
|
||||
// IncludeBacktrace is a static variable that decides if when creating an error we store the backtrace with it.
|
||||
var IncludeBacktrace = true
|
||||
|
||||
// Err is our custom error type that can store backtrace, file and line number
|
||||
type Err struct {
|
||||
m string
|
||||
c error
|
||||
t stack
|
||||
}
|
||||
|
||||
func (e Err) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
io.WriteString(s, e.m)
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
if e.c != nil {
|
||||
io.WriteString(s, ": ")
|
||||
io.WriteString(s, fmt.Sprintf("%+s", e.c))
|
||||
}
|
||||
}
|
||||
case 'v':
|
||||
e.Format(s, 's')
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
if e.t != nil {
|
||||
io.WriteString(s, "\n\t")
|
||||
e.t.Format(s, 'v')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e Err) Error() string {
|
||||
if IncludeBacktrace {
|
||||
return e.m
|
||||
}
|
||||
s := strings.Builder{}
|
||||
s.WriteString(e.m)
|
||||
if ch := errors.Unwrap(e); ch != nil {
|
||||
s.WriteString(": ")
|
||||
s.WriteString(ch.Error())
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// Unwrap implements the errors.Wrapper interface
|
||||
func (e Err) Unwrap() error {
|
||||
return e.c
|
||||
}
|
||||
|
||||
// StackTrace returns the stack trace as returned by the debug.Stack function
|
||||
func (e Err) StackTrace() StackTrace {
|
||||
return e.t.StackTrace()
|
||||
}
|
||||
|
||||
// Annotatef wraps an error with new message
|
||||
func Annotatef(e error, s string, args ...interface{}) *Err {
|
||||
err := wrap(e, s, args...)
|
||||
return &err
|
||||
}
|
||||
|
||||
// Newf creaates a new error
|
||||
func Newf(s string, args ...interface{}) *Err {
|
||||
err := wrap(nil, s, args...)
|
||||
return &err
|
||||
}
|
||||
|
||||
// Errorf is an alias for Newf
|
||||
func Errorf(s string, args ...interface{}) error {
|
||||
err := wrap(nil, s, args...)
|
||||
return &err
|
||||
}
|
||||
|
||||
// As implements support for errors.As
|
||||
func (e *Err) As(err interface{}) bool {
|
||||
switch x := err.(type) {
|
||||
case **Err:
|
||||
*x = e
|
||||
case *Err:
|
||||
*x = *e
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type StackTracer interface {
|
||||
StackTrace() StackTrace
|
||||
}
|
||||
|
||||
// ancestorOfCause returns true if the caller looks to be an ancestor of the given stack
|
||||
// trace. We check this by seeing whether our stack prefix-matches the cause stack, which
|
||||
// should imply the error was generated directly from our goroutine.
|
||||
func ancestorOfCause(ourStack stack, causeStack StackTrace) bool {
|
||||
// Stack traces are ordered such that the deepest frame is first. We'll want to check
|
||||
// for prefix matching in reverse.
|
||||
//
|
||||
// As an example, imagine we have a prefix-matching stack for ourselves:
|
||||
// [
|
||||
// "github.com/go-ap/processing/processing.Validate",
|
||||
// "testing.tRunner",
|
||||
// "runtime.goexit"
|
||||
// ]
|
||||
//
|
||||
// We'll want to compare this against an error cause that will have happened further
|
||||
// down the stack. An example stack trace from such an error might be:
|
||||
// [
|
||||
// "github.com/go-ap/errors/errors.New",
|
||||
// "testing.tRunner",
|
||||
// "runtime.goexit"
|
||||
// ]
|
||||
//
|
||||
// Their prefix matches, but we'll have to handle the match carefully as we need to match
|
||||
// from back to forward.
|
||||
|
||||
// We can't possibly prefix match if our stack is larger than the cause stack.
|
||||
if len(ourStack) > len(causeStack) {
|
||||
return false
|
||||
}
|
||||
|
||||
// We know the sizes are compatible, so compare program counters from back to front.
|
||||
for idx := 0; idx < len(ourStack); idx++ {
|
||||
if ourStack[len(ourStack)-1] != (uintptr)(causeStack[len(causeStack)-1]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func wrap(e error, s string, args ...interface{}) Err {
|
||||
err := Err{
|
||||
c: e,
|
||||
m: fmt.Sprintf(s, args...),
|
||||
}
|
||||
if IncludeBacktrace {
|
||||
causeStackTracer := new(StackTracer)
|
||||
// If our cause has set a stack trace, and that trace is a child of our own function
|
||||
// as inferred by prefix matching our current program counter stack, then we only want
|
||||
// to decorate the error message rather than add a redundant stack trace.
|
||||
stack := callers(2)
|
||||
if !(As(e, causeStackTracer) && ancestorOfCause(*stack, (*causeStackTracer).StackTrace())) {
|
||||
err.t = *stack
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
238
errors_test.go
Normal file
238
errors_test.go
Normal file
|
@ -0,0 +1,238 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var err = fmt.Errorf("test error")
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
IncludeBacktrace = false
|
||||
// %+s check for unwrapped error
|
||||
e1 := Newf("test")
|
||||
str := fmt.Sprintf("%+s", e1)
|
||||
if str != e1.m {
|
||||
t.Errorf("Error message invalid %s, expected %s", str, e1.m)
|
||||
}
|
||||
|
||||
// %+s check for wrapped error
|
||||
e2 := Annotatef(e1, "another")
|
||||
str = fmt.Sprintf("%+s", e2)
|
||||
val := e2.m + ": " + e2.c.Error()
|
||||
if str != val {
|
||||
t.Errorf("Error message invalid %s, expected %s", str, val)
|
||||
}
|
||||
|
||||
// %v check for unwrapped error with trace
|
||||
IncludeBacktrace = true
|
||||
e3 := Newf("test1")
|
||||
str = fmt.Sprintf("%+v", e3)
|
||||
if !strings.Contains(str, e3.m) {
|
||||
t.Errorf("Error message %s\n should contain %s", str, e3.m)
|
||||
}
|
||||
if !strings.Contains(str, fmt.Sprintf("%+v", e3.t.StackTrace())) {
|
||||
t.Errorf("Error message %s\n should contain %+v", str, e3.t.StackTrace())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalJSON(t *testing.T) {
|
||||
IncludeBacktrace = true
|
||||
e := Newf("test")
|
||||
|
||||
b, err := e.t.StackTrace().MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("MarshalJSON failed with error: %+s", err)
|
||||
}
|
||||
stack := make([]json.RawMessage, 0)
|
||||
err = json.Unmarshal(b, &stack)
|
||||
if err != nil {
|
||||
t.Errorf("JSON message could not be unmarshaled: %+s", err)
|
||||
}
|
||||
if len(stack) != len(e.t) {
|
||||
t.Errorf("Count of stack frames different after marshaling, expected %d got %d", len(e.t), len(stack))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotatef(t *testing.T) {
|
||||
testStr := "Annotatef string"
|
||||
te := Annotatef(err, testStr)
|
||||
if te.c != err {
|
||||
t.Errorf("Invalid parent error %T:%s, expected %T:%s", te.c, te.c, err, err)
|
||||
}
|
||||
if te.m != testStr {
|
||||
t.Errorf("Invalid error message %s, expected %s", te.m, testStr)
|
||||
}
|
||||
}
|
||||
|
||||
var homeVal = "$HOME"
|
||||
|
||||
func TestNewf(t *testing.T) {
|
||||
testStr := "Newf string"
|
||||
te := Newf(testStr)
|
||||
if te.c != nil {
|
||||
t.Errorf("Invalid parent error %T:%s, expected nil", te.c, te.c)
|
||||
}
|
||||
if te.m != testStr {
|
||||
t.Errorf("Invalid error message %s, expected %s", te.m, testStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorf(t *testing.T) {
|
||||
testStr := "Errorf string"
|
||||
err := Errorf(testStr)
|
||||
if te, ok := err.(*Err); ok {
|
||||
if te.c != nil {
|
||||
t.Errorf("Invalid parent error %T:%s, expected nil", te.c, te.c)
|
||||
}
|
||||
if te.m != testStr {
|
||||
t.Errorf("Invalid error message %s, expected %s", te.m, testStr)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("Invalid error type returned %T, expected type %T", err, &Err{})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func TestErr_As(t *testing.T) {
|
||||
e := Err{m: "test", l: 11, f: "random", t: []uintptr{0x6, 0x6, 0x6}, c: fmt.Errorf("ttt")}
|
||||
if e.As(&err) {
|
||||
t.Errorf("%T should not be assertable as %T", err, e)
|
||||
}
|
||||
type clone = Err
|
||||
e1 := clone{}
|
||||
if !e.As(&e1) {
|
||||
t.Errorf("%T should be assertable as %T", e, e1)
|
||||
}
|
||||
if e1.m != e.m {
|
||||
t.Errorf("%T message should equal %T's, received %s, expected %s", e1, e, e1.m, e.m)
|
||||
}
|
||||
if e1.l != e.l {
|
||||
t.Errorf("%T line should equal %T's, received %d, expected %d", e1, e, e1.l, e.l)
|
||||
}
|
||||
if e1.f != e.f {
|
||||
t.Errorf("%T file should equal %T's, received %s, expected %s", e1, e, e1.f, e.f)
|
||||
}
|
||||
if !bytes.Equal(e1.t, e.t) {
|
||||
t.Errorf("%T trace should equal %T's, received %2x, expected %2x", e1, e, e1.t, e.t)
|
||||
}
|
||||
if e1.c != e.c {
|
||||
t.Errorf("%T parent error should equal %T's, received %T[%s], expected %T[%s]", e1, e, e1.c, e1.c, e.c, e.c)
|
||||
}
|
||||
e2 := &e
|
||||
if !e.As(&e2) {
|
||||
t.Errorf("%T should be assertable as %T", e, e2)
|
||||
}
|
||||
if e2.m != e.m {
|
||||
t.Errorf("%T message should equal %T's, received %s, expected %s", e2, e, e2.m, e.m)
|
||||
}
|
||||
if e2.l != e.l {
|
||||
t.Errorf("%T line should equal %T's, received %d, expected %d", e2, e, e2.l, e.l)
|
||||
}
|
||||
if e2.f != e.f {
|
||||
t.Errorf("%T file should equal %T's, received %s, expected %s", e2, e, e2.f, e.f)
|
||||
}
|
||||
if !bytes.Equal(e2.t, e.t) {
|
||||
t.Errorf("%T trace should equal %T's, received %2x, expected %2x", e2, e, e2.t, e.t)
|
||||
}
|
||||
if e2.c != e.c {
|
||||
t.Errorf("%T parent error should equal %T's, received %T[%s], expected %T[%s]", e2, e, e2.c, e2.c, e.c, e.c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErr_Error(t *testing.T) {
|
||||
e := Err{m: "test"}
|
||||
if e.Error() != e.m {
|
||||
t.Errorf("Error() returned %s, expected %s", e.Error(), e.m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErr_Location(t *testing.T) {
|
||||
e := Err{l: 11, f: "random"}
|
||||
if f, l := e.Location(); l != e.l || f != e.f {
|
||||
t.Errorf("Location() returned: %s:%d, expected: %s:%d", f, l, e.f, e.l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErr_StackTrace(t *testing.T) {
|
||||
e := Err{t: []byte{0x6, 0x6, 0x6}}
|
||||
if !bytes.Equal(e.StackTrace(), e.t) {
|
||||
t.Errorf("StackTrace() returned: %2x, expected: %2x", e.StackTrace(), e.t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErr_Unwrap(t *testing.T) {
|
||||
e := Err{c: fmt.Errorf("ttt")}
|
||||
w := e.Unwrap()
|
||||
if w != e.c {
|
||||
t.Errorf("Unwrap() returned: %T[%s], expected: %T[%s]", w, w, e.c, e.c)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func TestErr_Error(t *testing.T) {
|
||||
type fields struct {
|
||||
m string
|
||||
c error
|
||||
t stack
|
||||
}
|
||||
tests := []struct {
|
||||
quiet bool
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
fields: fields{},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "just text",
|
||||
fields: fields{m: "test"},
|
||||
want: "test",
|
||||
},
|
||||
{
|
||||
name: "text with single wrapped error",
|
||||
fields: fields{m: "test", c: fmt.Errorf("error")},
|
||||
want: "test: error",
|
||||
},
|
||||
{
|
||||
name: "text with two wrapped errors",
|
||||
fields: fields{m: "test", c: fmt.Errorf("error: %w", Newf("some error"))},
|
||||
want: "test: error: some error",
|
||||
},
|
||||
{
|
||||
name: "text with two wrapped errors, but no unwrapping",
|
||||
quiet: true,
|
||||
fields: fields{m: "test", c: fmt.Errorf("error: %w", Newf("some error"))},
|
||||
want: "test",
|
||||
},
|
||||
{
|
||||
name: "text with two wrapped Err errors",
|
||||
fields: fields{m: "test", c: &Err{m: "error", c: &Err{m: "another error"}}},
|
||||
want: "test: error: another error",
|
||||
},
|
||||
{
|
||||
name: "text with two wrapped Err errors, without unwrapping",
|
||||
quiet: true,
|
||||
fields: fields{m: "test", c: &Err{m: "error", c: &Err{m: "another error"}}},
|
||||
want: "test",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
IncludeBacktrace = tt.quiet
|
||||
e := Err{
|
||||
m: tt.fields.m,
|
||||
c: tt.fields.c,
|
||||
t: tt.fields.t,
|
||||
}
|
||||
if got := e.Error(); got != tt.want {
|
||||
t.Errorf("Error() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
8
go.mod
Normal file
8
go.mod
Normal file
|
@ -0,0 +1,8 @@
|
|||
module github.com/go-ap/errors
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||
github.com/valyala/fastjson v1.6.4
|
||||
)
|
1063
http_test.go
Normal file
1063
http_test.go
Normal file
File diff suppressed because it is too large
Load diff
44
join.go
Normal file
44
join.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package errors
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func Join(errs ...error) error {
|
||||
n := 0
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
n++
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
e := &joinError{
|
||||
errs: make([]error, 0, n),
|
||||
}
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
type joinError struct {
|
||||
errs []error
|
||||
}
|
||||
|
||||
func (e *joinError) Error() string {
|
||||
// Since Join returns nil if every value in errs is nil,
|
||||
// e.errs cannot be empty.
|
||||
if len(e.errs) == 1 {
|
||||
return e.errs[0].Error()
|
||||
}
|
||||
|
||||
b := []byte(e.errs[0].Error())
|
||||
for _, err := range e.errs[1:] {
|
||||
b = append(b, ':', ' ')
|
||||
b = append(b, err.Error()...)
|
||||
}
|
||||
// At this point, b has at least one byte '\n'.
|
||||
return unsafe.String(&b[0], len(b))
|
||||
}
|
98
redirects.go
Normal file
98
redirects.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func SeeOther(u string) *redirect {
|
||||
return &redirect{s: http.StatusSeeOther, u: u}
|
||||
}
|
||||
func NewSeeOther(e error, u string) *redirect {
|
||||
return &redirect{c: e, s: http.StatusSeeOther, u: u}
|
||||
}
|
||||
func Found(u string) *redirect {
|
||||
return &redirect{s: http.StatusFound, u: u}
|
||||
}
|
||||
func NewFound(e error, u string) *redirect {
|
||||
return &redirect{c: e, s: http.StatusFound, u: u}
|
||||
}
|
||||
func MovedPermanently(u string) *redirect {
|
||||
return &redirect{s: http.StatusMovedPermanently, u: u}
|
||||
}
|
||||
func NewMovedPermanently(e error, u string) *redirect {
|
||||
return &redirect{c: e, s: http.StatusMovedPermanently, u: u}
|
||||
}
|
||||
func NotModified(u string) *redirect {
|
||||
return &redirect{s: http.StatusNotModified, u: u}
|
||||
}
|
||||
func NewNotModified(e error, u string) *redirect {
|
||||
return &redirect{c: e, s: http.StatusNotModified, u: u}
|
||||
}
|
||||
func TemporaryRedirect(u string) *redirect {
|
||||
return &redirect{s: http.StatusTemporaryRedirect, u: u}
|
||||
}
|
||||
func NewTemporaryRedirect(e error, u string) *redirect {
|
||||
return &redirect{c: e, s: http.StatusTemporaryRedirect, u: u}
|
||||
}
|
||||
func PermanentRedirect(u string) *redirect {
|
||||
return &redirect{s: http.StatusPermanentRedirect, u: u}
|
||||
}
|
||||
func NewPermanentRedirect(e error, u string) *redirect {
|
||||
return &redirect{c: e, s: http.StatusPermanentRedirect, u: u}
|
||||
}
|
||||
|
||||
type redirect struct {
|
||||
c error
|
||||
u string
|
||||
s int
|
||||
}
|
||||
|
||||
func (r redirect) Error() string {
|
||||
if r.c == nil {
|
||||
return fmt.Sprintf("Redirect %d to %s", r.s, r.u)
|
||||
}
|
||||
return fmt.Sprintf("Redirect %d to %s: %s", r.s, r.u, r.c)
|
||||
}
|
||||
|
||||
// As is used by the errors.As() function to coerce the method's parameter to the one of the receiver
|
||||
//
|
||||
// if the underlying logic of the receiver's type can understand it.
|
||||
//
|
||||
// In this case we're converting a forbidden to its underlying type Err.
|
||||
func (r *redirect) As(err interface{}) bool {
|
||||
switch x := err.(type) {
|
||||
case **redirect:
|
||||
*x = r
|
||||
case *redirect:
|
||||
*x = *r
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r redirect) Is(e error) bool {
|
||||
rr := redirect{}
|
||||
return As(e, &rr) && r.s == rr.s
|
||||
}
|
||||
|
||||
func IsRedirect(e error) bool {
|
||||
_, okp := e.(*redirect)
|
||||
_, oks := e.(redirect)
|
||||
return okp || oks || As(e, &redirect{})
|
||||
}
|
||||
|
||||
func IsNotModified(e error) bool {
|
||||
ep, okp := e.(*redirect)
|
||||
es, oks := e.(redirect)
|
||||
|
||||
ae := redirect{}
|
||||
return (okp && ep.s == http.StatusNotModified) ||
|
||||
(oks && es.s == http.StatusNotModified) ||
|
||||
(As(e, &ae) && ae.s == http.StatusNotModified)
|
||||
}
|
||||
|
||||
func (r redirect) Unwrap() error {
|
||||
return r.Unwrap()
|
||||
}
|
219
stack.go
Normal file
219
stack.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
// For historical reasons if Frame is interpreted as a uintptr
|
||||
// its value represents the program counter + 1.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// name returns the name of this function, if known.
|
||||
func (f Frame) name() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return fn.Name()
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s function name and path of source file relative to the compile time
|
||||
// GOPATH separated by \n\t (<funcname>\n\t<path>)
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
io.WriteString(s, f.name())
|
||||
io.WriteString(s, "\n\t")
|
||||
io.WriteString(s, f.file())
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
io.WriteString(s, strconv.Itoa(f.line()))
|
||||
case 'n':
|
||||
io.WriteString(s, funcname(f.name()))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText formats a stacktrace Frame as a text string. The output is the
|
||||
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
|
||||
func (f Frame) MarshalText() ([]byte, error) {
|
||||
name := f.name()
|
||||
if name == "unknown" {
|
||||
return []byte(name), nil
|
||||
}
|
||||
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
|
||||
}
|
||||
|
||||
func (f Frame) MarshalJSON() ([]byte, error) {
|
||||
name := f.name()
|
||||
w := bytes.NewBuffer(nil)
|
||||
if name == "unknown" {
|
||||
w.WriteByte('"')
|
||||
w.WriteString(name)
|
||||
w.WriteByte('"')
|
||||
return w.Bytes(), nil
|
||||
}
|
||||
w.WriteByte('{')
|
||||
w.WriteString("\"function\": ")
|
||||
w.WriteByte('"')
|
||||
w.WriteString(funcname(name))
|
||||
w.WriteByte('"')
|
||||
w.WriteByte(',')
|
||||
w.WriteString("\"file\": ")
|
||||
w.WriteByte('"')
|
||||
w.WriteString(f.file())
|
||||
w.WriteByte('"')
|
||||
w.WriteByte(',')
|
||||
w.WriteString("\"line\": ")
|
||||
w.WriteString(fmt.Sprintf("%d", f.line()))
|
||||
w.WriteByte('}')
|
||||
return w.Bytes(), nil
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
// Format formats the stack of Frames according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s lists source files for each Frame in the stack
|
||||
// %v lists the source file and line number for each Frame in the stack
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+v Prints filename, function, and line number for each Frame in the stack.
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
io.WriteString(s, "\n")
|
||||
f.Format(s, verb)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
case 's':
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
}
|
||||
|
||||
func (st StackTrace) MarshalJSON() ([]byte, error) {
|
||||
w := bytes.NewBuffer(nil)
|
||||
w.WriteByte('[')
|
||||
for i, f := range st {
|
||||
b, _ := f.MarshalJSON()
|
||||
w.Write(b)
|
||||
if i == len(st)-1 {
|
||||
break
|
||||
}
|
||||
w.WriteByte(',')
|
||||
}
|
||||
w.WriteByte(']')
|
||||
return w.Bytes(), nil
|
||||
}
|
||||
|
||||
// formatSlice will format this StackTrace into the given buffer as a slice of
|
||||
// Frame, only valid when called with '%s' or '%v'.
|
||||
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
|
||||
io.WriteString(s, "[")
|
||||
for i, f := range st {
|
||||
if i > 0 {
|
||||
io.WriteString(s, " ")
|
||||
}
|
||||
f.Format(s, verb)
|
||||
}
|
||||
io.WriteString(s, "]")
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers(skip int) *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(skip+3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
263
stack_test.go
Normal file
263
stack_test.go
Normal file
|
@ -0,0 +1,263 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var initpc = caller()
|
||||
|
||||
func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
|
||||
t.Helper()
|
||||
got := fmt.Sprintf(format, arg)
|
||||
gotLines := strings.SplitN(got, "\n", -1)
|
||||
wantLines := strings.SplitN(want, "\n", -1)
|
||||
|
||||
if len(wantLines) > len(gotLines) {
|
||||
t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
|
||||
return
|
||||
}
|
||||
|
||||
for i, w := range wantLines {
|
||||
match, err := regexp.MatchString(w, gotLines[i])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !match {
|
||||
t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type X struct{}
|
||||
|
||||
// val returns a Frame pointing to itself.
|
||||
func (x X) val() Frame {
|
||||
return caller()
|
||||
}
|
||||
|
||||
// ptr returns a Frame pointing to itself.
|
||||
func (x *X) ptr() Frame {
|
||||
return caller()
|
||||
}
|
||||
|
||||
func TestFrameFormat(t *testing.T) {
|
||||
var tests = []struct {
|
||||
Frame
|
||||
format string
|
||||
want string
|
||||
}{{
|
||||
initpc,
|
||||
"%s",
|
||||
"stack_test.go",
|
||||
}, {
|
||||
initpc,
|
||||
"%+s",
|
||||
"github.com/go-ap/errors.init\n" +
|
||||
"\t.+/errors/stack_test.go",
|
||||
}, {
|
||||
0,
|
||||
"%s",
|
||||
"unknown",
|
||||
}, {
|
||||
0,
|
||||
"%+s",
|
||||
"unknown",
|
||||
}, {
|
||||
initpc,
|
||||
"%d",
|
||||
"11",
|
||||
}, {
|
||||
0,
|
||||
"%d",
|
||||
"0",
|
||||
}, {
|
||||
initpc,
|
||||
"%n",
|
||||
"init",
|
||||
}, {
|
||||
func() Frame {
|
||||
var x X
|
||||
return x.ptr()
|
||||
}(),
|
||||
"%n",
|
||||
`\(\*X\).ptr`,
|
||||
}, {
|
||||
func() Frame {
|
||||
var x X
|
||||
return x.val()
|
||||
}(),
|
||||
"%n",
|
||||
"X.val",
|
||||
}, {
|
||||
0,
|
||||
"%n",
|
||||
"",
|
||||
}, {
|
||||
initpc,
|
||||
"%v",
|
||||
"stack_test.go:11",
|
||||
}, {
|
||||
initpc,
|
||||
"%+v",
|
||||
"github.com/go-ap/errors.init\n" +
|
||||
"\t.+/errors/stack_test.go:11",
|
||||
}, {
|
||||
0,
|
||||
"%v",
|
||||
"unknown:0",
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncname(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, want string
|
||||
}{
|
||||
{"", ""},
|
||||
{"runtime.main", "main"},
|
||||
{"github.com/go-ap/errors.funcname", "funcname"},
|
||||
{"funcname", "funcname"},
|
||||
{"io.copyBuffer", "copyBuffer"},
|
||||
{"main.(*R).Write", "(*R).Write"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := funcname(tt.name)
|
||||
want := tt.want
|
||||
if got != want {
|
||||
t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackTrace(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
want []string
|
||||
}{{
|
||||
Newf("ooh"), []string{
|
||||
"github.com/go-ap/errors.TestStackTrace\n" +
|
||||
"\t.+/errors/stack_test.go:145",
|
||||
},
|
||||
}, {
|
||||
wrap(Newf("ooh"), "ahh"), []string{
|
||||
"github.com/go-ap/errors.TestStackTrace\n" +
|
||||
"\t.+/errors/stack_test.go:150", // this is the stack of Wrap, not New
|
||||
},
|
||||
}, {
|
||||
func() error { return Newf("ooh") }(), []string{
|
||||
`github.com/go-ap/errors.TestStackTrace.func1` +
|
||||
"\n\t.+/errors/stack_test.go:155", // this is the stack of New
|
||||
"github.com/go-ap/errors.TestStackTrace\n" +
|
||||
"\t.+/errors/stack_test.go:155", // this is the stack of New's caller
|
||||
},
|
||||
}}
|
||||
t.Skipf(`TODO(marius): This needs some more work
|
||||
As going one level up the stack in stack.callers() removes meaningful information from the tests`)
|
||||
for i, tt := range tests {
|
||||
x, ok := tt.err.(interface {
|
||||
StackTrace() StackTrace
|
||||
})
|
||||
if !ok {
|
||||
t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err)
|
||||
continue
|
||||
}
|
||||
st := x.StackTrace()
|
||||
if len(st) == 0 {
|
||||
continue
|
||||
}
|
||||
for j, want := range tt.want {
|
||||
testFormatRegexp(t, i, st[j], "%+v", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stackTrace() StackTrace {
|
||||
const depth = 8
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(1, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return st.StackTrace()
|
||||
}
|
||||
|
||||
func TestStackTraceFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
StackTrace
|
||||
format string
|
||||
want string
|
||||
}{{
|
||||
nil,
|
||||
"%s",
|
||||
`\[\]`,
|
||||
}, {
|
||||
nil,
|
||||
"%v",
|
||||
`\[\]`,
|
||||
}, {
|
||||
nil,
|
||||
"%+v",
|
||||
"",
|
||||
}, {
|
||||
nil,
|
||||
"%#v",
|
||||
`\[\]errors.Frame\(nil\)`,
|
||||
}, {
|
||||
make(StackTrace, 0),
|
||||
"%s",
|
||||
`\[\]`,
|
||||
}, {
|
||||
make(StackTrace, 0),
|
||||
"%v",
|
||||
`\[\]`,
|
||||
}, {
|
||||
make(StackTrace, 0),
|
||||
"%+v",
|
||||
"",
|
||||
}, {
|
||||
make(StackTrace, 0),
|
||||
"%#v",
|
||||
`\[\]errors.Frame{}`,
|
||||
}, {
|
||||
stackTrace()[:2],
|
||||
"%s",
|
||||
`\[stack_test.go stack_test.go\]`,
|
||||
}, {
|
||||
stackTrace()[:2],
|
||||
"%v",
|
||||
`\[stack_test.go:183 stack_test.go:230\]`,
|
||||
}, {
|
||||
stackTrace()[:2],
|
||||
"%+v",
|
||||
"\n" +
|
||||
"github.com/go-ap/errors.stackTrace\n" +
|
||||
"\t.+/errors/stack_test.go:183\n" +
|
||||
"github.com/go-ap/errors.TestStackTraceFormat\n" +
|
||||
"\t.+/errors/stack_test.go:234",
|
||||
}, {
|
||||
stackTrace()[:2],
|
||||
"%#v",
|
||||
`\[\]errors.Frame{stack_test.go:183, stack_test.go:242}`,
|
||||
}}
|
||||
|
||||
t.Skipf(`TODO(marius): This needs some more work
|
||||
As going one level up the stack in stack.callers() removes meaningful information from the tests`)
|
||||
for i, tt := range tests {
|
||||
testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
// a version of runtime.Caller that returns a Frame, not a uintptr.
|
||||
func caller() Frame {
|
||||
var pcs [3]uintptr
|
||||
n := runtime.Callers(2, pcs[:])
|
||||
frames := runtime.CallersFrames(pcs[:n])
|
||||
frame, _ := frames.Next()
|
||||
return Frame(frame.PC)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue