Skip to content

Commit

Permalink
(rs#470) Localize github.com/pkg/errors
Browse files Browse the repository at this point in the history
Signed-off-by: Bhargav Ravuri <vaguecoder0to.n@gmail.com>
  • Loading branch information
vaguecoder committed Sep 6, 2022
1 parent c2b9d0e commit 9a7fb84
Show file tree
Hide file tree
Showing 6 changed files with 557 additions and 2 deletions.
27 changes: 27 additions & 0 deletions internal/errors/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package errors

// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {
return &fundamental{
msg: message,
Stack: callers(),
}
}

// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: message,
}
return &withStack{
err,
callers(),
}
}
57 changes: 57 additions & 0 deletions internal/errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package errors_test

import (
"errors"
"fmt"
"io"
"testing"

internalErrors "github.com/rs/zerolog/internal/errors"
)

func TestNew(t *testing.T) {
tests := []struct {
err string
want error
}{
{"", fmt.Errorf("")},
{"foo", fmt.Errorf("foo")},
{"foo", internalErrors.New("foo")},
{"string with format specifiers: %v", errors.New("string with format specifiers: %v")},
}

for _, tt := range tests {
got := internalErrors.New(tt.err)
if got.Error() != tt.want.Error() {
t.Errorf("New.Error(): got: %q, want %q", got, tt.want)
}
}
}

func TestWrapNil(t *testing.T) {
got := internalErrors.Wrap(nil, "no error")
if got != nil {
t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got)
}
}

func TestWrap(t *testing.T) {
tests := []struct {
err error
message string
want string
}{
{io.EOF, "read error", "read error: EOF"},
{internalErrors.Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"},
}

for _, tt := range tests {
var got string
if tt.err != nil {
got = internalErrors.Wrap(tt.err, tt.message).Error()
}
if got != tt.want {
t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
}
}
}
146 changes: 146 additions & 0 deletions internal/errors/stack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package errors

import (
"fmt"
"io"
"path"
"runtime"
"strconv"
"strings"
)

// Stack represents a stack of program counters.
type Stack []uintptr

// StackTrace prints the stack trace of program.
func (s *Stack) StackTrace() StackTrace {
f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ {
f[i] = Frame((*s)[i])
}
return f
}

// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*Stack
}

// Error prints the error message string
func (f *fundamental) Error() string {
return f.msg
}

// withStack is error with caller stack
type withStack struct {
error
*Stack
}

// withMessage is error with added message
type withMessage struct {
cause error
msg string
}

// Error prints the error message string with cause
func (w *withMessage) Error() string {
return w.msg + ": " + w.cause.Error()
}

// 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')
}
}

// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame

// callers gives caller function's stack
func callers() *Stack {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(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:]
}
Loading

0 comments on commit 9a7fb84

Please sign in to comment.