From a42306d9a0479a06225440917d7c85fed8c0126f Mon Sep 17 00:00:00 2001
From: Jan Semmelink <jan@uafrica.com>
Date: Wed, 8 Sep 2021 14:33:29 +0200
Subject: [PATCH] First commit of errors

---
 errors/README.md             | 153 +++++++++++++++++++
 errors/caller.go             | 186 +++++++++++++++++++++++
 errors/error.go              | 192 ++++++++++++++++++++++++
 errors/error_formats_test.go | 279 +++++++++++++++++++++++++++++++++++
 errors/errors.go             |  82 ++++++++++
 errors/errors_test.go        |  92 ++++++++++++
 go.mod                       |  18 +++
 go.sum                       | 139 +++++++++++++++++
 8 files changed, 1141 insertions(+)
 create mode 100644 errors/README.md
 create mode 100644 errors/caller.go
 create mode 100644 errors/error.go
 create mode 100644 errors/error_formats_test.go
 create mode 100644 errors/errors.go
 create mode 100644 errors/errors_test.go
 create mode 100644 go.mod
 create mode 100644 go.sum

diff --git a/errors/README.md b/errors/README.md
new file mode 100644
index 0000000..3d5830f
--- /dev/null
+++ b/errors/README.md
@@ -0,0 +1,153 @@
+# errors
+
+This is our own implementation of the Golang `error` interface.
+
+## Why
+
+Packages tend to use one of the following to return errors:
+* Standard go ```"errors"``` package with ```errors.New("...")```
+* Standard go ```"fmt"``` package with ```fmt.Errorf("...")```
+* ```github.com/pkg/errors``` package with ```errors.New("...")```
+
+Of those three, the pkg/errors is by far the better option as it includes an error stack.
+
+One could use pkg/errors, but:
+- it does not offer much flexibility in how it displays errors
+- its display is cluttered, with the wrong info (e.g. runtime stack and not the error handling stack)
+- it does not display the up passing of errors in the stack unless you add the stack each time
+- if you do add the stack each time, it dumps many copies of a stack and becomes cluttered
+
+We write our own version of errors, to:
+- always add the stack
+- add the correct stack of where we raised/handled the error
+- make error handling simpler (Wrapf(err) vs WithMessage(WithStack(err)))
+- give us all the flexibility we need
+    - e.g. give us option to output JSON structured stack
+
+For lots of detail on this, see comments in error_formats_test.go and run that test with ```go test -v error_formats_test.go``` ...
+
+## Usage
+Get the package into your project:
+```
+go get github.com/uafrica/go-utils
+```
+
+## func New()
+Fail with your own simple error message:
+
+```
+if !authenticated {
+    return errors.New("not authenticated")
+}
+```
+
+## func Errorf()
+Fail with your own error using Printf style, e.g. when you detect an invalid value:
+
+```
+if limit < 0 {
+    return errors.Errorf("invalid limit %d<0", limit)
+}
+```
+
+## func Wrapf()
+Fail when a called function returned an error:
+
+```
+if err := db.Select(query); err != nil {
+    return errors.Wrapf(err, "query failed on user=%s", username)
+}
+```
+
+## Refactoring exiting code
+Replace all other errors package imports with this package:
+```
+import (
+    "github.com/uafrica/go-utils/errors"
+)
+```
+
+Refactor from standad go "errors" package:
+
+- No change: ```errors.New()``` is still supported in this package
+
+Refactor from "fmt" package:
+
+- Replace `errors.Errorf("my message: %v", err)` with `errors.Wrapf(err, "my message")` so that the layers are preserved and not merged into a single text string.
+
+Refactor from "github.com/pkg/errors":
+
+- Replace ```errors.WithStack(err)``` with ```errors.New(err)```.
+- Replace ```return err``` with ```return errors.Wrapf(err, "some message")``` saying what failed as result of the err
+- Replace ```errors.WithMessagef(err, "...", ...)``` with ```return errors.Wrapf(err, "...", ...)```
+- Replace ```errors.WithMessage(err, "...")``` with ```return errors.Wrap(err, "...")```
+
+## Formatting
+
+Report an error with:
+
+```
+user,err := getUser(userName)
+if err != nil {
+    log.Errorf("failed to get user %s: %+v", username, err)
+    ...
+}
+```
+
+Select the appropriate format:
+
+- `%+c` in most cases to write the full compact error with file names and line numbers all on one line.
+- `%+v` is the same when you write to console over multipole lines
+- `%c` is full error on one line, without reference to source code, i.e. concatenate the error message of all levels e.g. ```A, because B, because C```, which you also get from ```err.Error()```.
+- `%v` is full error over multipole lines, without references to source code, e.g.
+```
+    A, because
+    B, because
+    C
+```
+- `%s` is just the last error message.
+
+## JSON error:
+
+Call ```err.Description()``` to get a struct that can marshal to JSON for a complete dump, e.g.:
+
+```
+    {
+        "error": "login failed",
+        "source": {
+            "package": "github.com/uafrica/go-utils/errors/errors_test",
+            "file": "errors_test.go",
+            "line": 18,
+            "function": "TestErrorFormatting"
+        },
+        "cause": {
+            "error": "failed to find account",
+            "source": {
+                "package": "github.com/uafrica/go-utils/errors/errors_test",
+                "file": "errors_test.go",
+                "line": 17,
+                "function": "TestErrorFormatting"
+            },
+            "cause": {
+                "error": "query failed",
+                "source": {
+                    "package": "github.com/uafrica/go-utils/errors/errors_test",
+                    "file": "errors_test.go",
+                    "line": 16,
+                    "function": "TestErrorFormatting"
+                },
+                "cause": {
+                    "error": "you have problem in your SQL near xxx"
+                }
+            }
+        }
+    }
+```
+
+## func Caller()
+
+This function can be used also for logging to determine the caller from the runtime stack. It takes a skip value to skip a few levels of the stack, making it flexible to be called in various wrapper functions, without reporting the wrappers.
+
+## func Stack()
+
+This function is similar to Caller() but reports an array of callers, not only one.
diff --git a/errors/caller.go b/errors/caller.go
new file mode 100644
index 0000000..3e29bf3
--- /dev/null
+++ b/errors/caller.go
@@ -0,0 +1,186 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+	"path"
+	"runtime"
+	"strings"
+)
+
+type ICaller interface {
+	fmt.Stringer
+	Package() string
+	Function() string
+	File() string
+	Line() int
+}
+
+//e.g.:
+type Caller struct {
+	file       string
+	line       int
+	pkgDotFunc string
+}
+
+func (c Caller) Info() CallerInfo {
+	return CallerInfo{
+		Package:  c.Package(),
+		File:     path.Base(c.File()),
+		Line:     c.Line(),
+		Function: c.Function(),
+	}
+}
+
+type CallerInfo struct {
+	Package  string `json:"package"`
+	File     string `json:"file"`
+	Line     int    `json:"line"`
+	Function string `json:"function"`
+}
+
+func GetCaller(skip int) Caller {
+	c := Caller{
+		file:       "",
+		line:       -1,
+		pkgDotFunc: "",
+	}
+
+	var pc uintptr
+	var ok bool
+	if pc, c.file, c.line, ok = runtime.Caller(skip); !ok {
+		return c
+	}
+
+	if fn := runtime.FuncForPC(pc); fn != nil {
+		c.pkgDotFunc = fn.Name()
+	}
+	return c
+} //GetCaller()
+
+func Stack(skip int) []CallerInfo {
+	if skip < 0 {
+		skip = 0
+	}
+	if skip >= 20 {
+		return nil
+	}
+	rpc := make([]uintptr, 20-skip)
+	n := runtime.Callers(skip, rpc[:])
+	if n < 1 {
+		return nil
+	}
+
+	callers := []CallerInfo{}
+	i := 0
+	for {
+		frame, ok := runtime.CallersFrames(rpc[i:]).Next()
+		if !ok {
+			break
+		}
+		fn := runtime.FuncForPC(frame.PC)
+		c := Caller{
+			pkgDotFunc: fn.Name(),
+			file:       frame.File, //path on local file system has no significance over package name
+			line:       frame.Line,
+		}
+		callers = append(callers, c.Info())
+		i++
+	}
+
+	return callers
+}
+
+func (c Caller) String() string {
+	return fmt.Sprintf("%s(%d)", path.Base(c.file), c.line)
+}
+
+//with Function: "github.com/go-msvc/ms_test.TestCaller"
+//return "github.com/go-msvc/ms_test"
+func (c Caller) Package() string {
+	if i := strings.LastIndex(c.pkgDotFunc, "."); i >= 0 {
+		return c.pkgDotFunc[:i]
+	}
+	return ""
+}
+
+//return "github.com/go-msvc/ms_test/my_test.go"
+func (c Caller) PackageFile() string {
+	if i := strings.LastIndex(c.pkgDotFunc, "."); i >= 0 {
+		return c.pkgDotFunc[:i] + "/" + path.Base(c.file)
+	}
+	return ""
+}
+
+//with Function: "github.com/go-msvc/ms_test.TestCaller"
+//return "TestCaller"
+func (c Caller) Function() string {
+	if i := strings.LastIndex(c.pkgDotFunc, "."); i >= 0 {
+		return c.pkgDotFunc[i+1:]
+	}
+	return ""
+}
+
+//return full file name on system where code is built...
+func (c Caller) File() string {
+	return c.file
+}
+
+func (c Caller) Line() int {
+	return c.line
+}
+
+func (caller Caller) Format(f fmt.State, c rune) {
+	var s string
+	switch c {
+	case 's', 'v':
+		if p, ok := f.Precision(); ok {
+			s = fmt.Sprintf("%s(%*d)", path.Base(caller.file), p, caller.line)
+		} else {
+			s = fmt.Sprintf("%s(%d)", path.Base(caller.file), caller.line)
+		}
+	case 'S', 'V':
+		if p, ok := f.Precision(); ok {
+			s = fmt.Sprintf("%s/%s(%*d)", caller.Package(), path.Base(caller.file), p, caller.line)
+		} else {
+			s = fmt.Sprintf("%s/%s(%d)", caller.Package(), path.Base(caller.file), caller.line)
+		}
+	case 'f':
+		if p, ok := f.Precision(); ok {
+			s = fmt.Sprintf("%s(%*d)", caller.Function(), p, caller.line)
+		} else {
+			s = fmt.Sprintf("%s(%d)", caller.Function(), caller.line)
+		}
+	case 'F':
+		if p, ok := f.Precision(); ok {
+			s = fmt.Sprintf("%s.%s(%*d)", caller.Package(), caller.Function(), p, caller.line)
+		} else {
+			s = fmt.Sprintf("%s.%s(%d)", caller.Package(), caller.Function(), caller.line)
+		}
+	} //switch(c)
+
+	if w, ok := f.Width(); ok {
+		if w < 0 {
+			s = ""
+		} else if w <= len(s) {
+			//right/left truncate
+			if f.Flag('-') {
+				s = s[:w]
+			} else {
+				s = s[len(s)-w:]
+			}
+		} else if w > len(s) {
+			//pad to fill the space
+			s = fmt.Sprintf("%*s", w, s)
+		}
+	}
+	io.WriteString(f, s)
+} // caller.Format()
+
+//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:]
+// } // funcName()
diff --git a/errors/error.go b/errors/error.go
new file mode 100644
index 0000000..38a9fad
--- /dev/null
+++ b/errors/error.go
@@ -0,0 +1,192 @@
+package errors
+
+import (
+	"fmt"
+	"path"
+	"strconv"
+	"strings"
+)
+
+//Error implements the following interfaces:
+//	error
+//	github.com/pkg/errors: Cause
+type Error struct {
+	message string
+	caller  Caller
+	//stack   []CallerInfo
+	cause error
+}
+
+//implement interface error:
+func (err Error) Error() string {
+	return err.Formatted(FormattingOptions{Causes: true})
+}
+
+//implement github.com/pkg/errors: Cause
+func (err Error) Cause() error {
+	return err.cause
+}
+
+func (err Error) Description() Description {
+	info := err.caller.Info()
+	desc := &Description{
+		Message: err.message,
+		Source:  &info,
+	}
+	if err.cause != nil {
+		causeWithStack, ok := err.cause.(*Error)
+		if !ok {
+			//external cause without our stack
+			//if github.com/pkg/errors, we can still get caller reference
+			desc.Cause = pkgDescription(0, err.cause)
+		} else {
+			//cause has our stack
+			causeDesription := causeWithStack.Description()
+			desc.Cause = &causeDesription
+		}
+	}
+	return *desc
+}
+
+func (err Error) Format(s fmt.State, v rune) {
+	s.Write([]byte(
+		err.Formatted(
+			FormattingOptions{
+				Causes:   (v == 'v' || v == 'c'), //include causes for %c and %v, s is only this error
+				NewLines: v == 'v',               //use newlines only on v, c is more compact on single line
+				Source:   s.Flag('+'),            //include source references when %+v or %+c
+			},
+		),
+	))
+}
+
+type FormattingOptions struct {
+	Causes   bool
+	NewLines bool
+	Source   bool
+}
+
+func (err Error) Formatted(opts FormattingOptions) string {
+	//start with this error
+	thisError := ""
+	if opts.Source {
+		thisError += fmt.Sprintf("%s/%s(%d): %s() ",
+			err.caller.Package(),
+			path.Base(err.caller.File()),
+			err.caller.Line(),
+			err.caller.Function(),
+		)
+	}
+	thisError += err.message
+	if !opts.Causes {
+		return thisError
+	}
+
+	if err.cause == nil {
+		return thisError
+	}
+
+	sep := ", because"
+	if opts.NewLines {
+		sep += "\n\t"
+	} else {
+		sep += " "
+	}
+
+	if causeWithStack, ok := err.cause.(*Error); ok {
+		return thisError + sep + causeWithStack.Formatted(opts)
+	}
+
+	//this level does not have our own stack, but we can detect github.com/pkg/errors stack, then:
+	//note: do not use fmt.Sprintf("%+v", err.cause) because it will repeat the stack if multiple were captured
+	//note: do not use err.cause.Error() because it does not include any stack
+	//instead, get first layer that implements it and log it
+	return thisError + pkgStack(err.cause, opts)
+}
+
+func pkgStack(err error, opts FormattingOptions) string {
+	s := ""
+	e := err
+	for e != nil {
+		if errWithStackTracer, ok := e.(stackTracer); ok {
+			st := errWithStackTracer.StackTrace()
+			for _, f := range st {
+				//source := fmt.Sprintf("%n %s %d", f, f, f) - this shows only package name, not fully qualified package name :-(
+				sources := strings.SplitN(fmt.Sprintf("%+s(%d)\n%n", f, f, f), "\n", 3)
+				//package            <-- sources[0]
+				//full filename:line <-- source[1]
+				//function name      <-- source[2]
+
+				//skip runtime packages
+				if strings.HasPrefix(sources[0], "runtime") {
+					break
+				}
+				if opts.NewLines {
+					s += ", because \n\t"
+				} else {
+					s += ", because "
+				}
+				if opts.Source {
+					s += sources[0] + "/" + path.Base(sources[1]) + ": " + sources[2] + "(): "
+				}
+				s += fmt.Sprintf("%s", e)
+			}
+			return s
+		}
+		errWithCause, ok := e.(ErrorWithCause)
+		if !ok {
+			break
+		}
+		e = errWithCause.Cause()
+	}
+	return s
+}
+
+func pkgDescription(level int, err error) *Description {
+	desc := &Description{
+		Message: fmt.Sprintf("%s", err),
+		Source:  nil,
+		Cause:   nil,
+	}
+
+	//recursively fill causes first
+	if errWithCause, ok := err.(ErrorWithCause); ok {
+		causeErr := errWithCause.Cause()
+		if causeErr != nil {
+			desc.Cause = pkgDescription(level+1, causeErr)
+		}
+	}
+
+	if errWithStackTracer, ok := err.(stackTracer); ok {
+		tempDesc := desc
+		st := errWithStackTracer.StackTrace()
+		for _, f := range st {
+			if tempDesc == nil {
+				break //stop if no more causes populated
+			}
+
+			//source := fmt.Sprintf("%n %s %d", f, f, f) - this shows only package name, not fully qualified package name :-(
+			sources := strings.SplitN(fmt.Sprintf("%+s\n%d\n%n", f, f, f), "\n", 4)
+			//package            <-- sources[0]
+			//full filename      <-- source[1]
+			//line               <-- source[2]
+			//function name      <-- source[3]
+
+			//stop if entering runtime part of the stack
+			if strings.HasPrefix(sources[0], "runtime") {
+				break
+			}
+			tempDesc.Source = &CallerInfo{
+				Package: sources[0],
+				File:    path.Base(sources[1]),
+				//Line:     sources[2],
+				Function: sources[3],
+			}
+			if i64, err := strconv.ParseInt(sources[2], 10, 64); err == nil {
+				tempDesc.Source.Line = int(i64)
+			}
+			tempDesc = tempDesc.Cause
+		} //for each stack level
+	} //if has stack
+	return desc
+}
diff --git a/errors/error_formats_test.go b/errors/error_formats_test.go
new file mode 100644
index 0000000..46ca107
--- /dev/null
+++ b/errors/error_formats_test.go
@@ -0,0 +1,279 @@
+package errors_test
+
+import (
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	go_errors "errors"
+
+	pkg_errors "github.com/pkg/errors"
+
+	uf_errors "github.com/uafrica/go-utils/errors"
+)
+
+//use this struct to define various types of errors to experiment with
+type Test struct {
+	title string
+	err   error
+}
+
+var tests []Test
+
+//define the errors for testing
+//in each case, the main error message is "<this is broken>"
+//in some cases that is passed up and wrapped with more messages
+//the tests use different combinations of go errors, fmt.Errorf(), github.com/pkg/errors and our own go-utils/errors library
+//NOTE: I put all the new calls on new lines to report different line numbers in the stack...
+func init() {
+	tests = []Test{
+		//a very simple go "errors.New()" error - does not have a stack
+		{title: "go.New(...)", err: go_errors.New("<this is broken>")},
+
+		//a fmt.Errorf() error - does not have a stack
+		{title: "fmt.Errorf(...)", err: fmt.Errorf("<this is broken>")},
+
+		//a better github.com/pkg/errors.New() error that includes the call stack
+		{title: "pkg.New(...)", err: pkg_errors.New("<this is broken>")},
+
+		//our own error type that always records the caller:
+		{title: "uafrica.New(...)", err: uf_errors.New("<this is broken>")},
+
+		//one can use pkg WithStack() to add a stack to an error that does not have a stack
+		{title: "pkg.WithStack(go.New(...))", err: pkg_errors.WithStack(
+			go_errors.New("<this is broken>"),
+		)},
+
+		//if you are ok with pkg's way to add a stack manually (I am not),
+		//then you could easily make this mistake to add a stack to an error that already has a stack
+		//by default pkg then prints two stacks and the rest of the error messages are after the last stack
+		//making it very hard to figure out what went wrong and where...
+		{title: "pkg.WithStack(pkg.New())", err: pkg_errors.WithStack(
+			pkg_errors.New("<this is broken>"),
+		)},
+
+		//one can also add a stack with our own errors,
+		//note that we always want an explanation which is just to enforce good practice:
+		{title: "uafrica.Wrapf(go.New(...))", err: uf_errors.Wrapf(
+			go_errors.New("<this is broken>"),
+			"<it is not working>",
+		)},
+
+		//and when we wrap an error that already has a stack, it will not duplicate,
+		//note that we always want an explanation which is just to enforce good practice:
+		{title: "uafrica.Wrapf(pkg.New(...))", err: uf_errors.Wrapf(
+			pkg_errors.New("<this is broken>"),
+			"<it is not working>",
+		)},
+
+		//pkg can also wrap an error with a text explanation,
+		//but the synctax is annoying as one has to add the stack and the message separately
+		//(compared to a simple Wrapf() call to imply a stack as well...)
+
+		//we firstly wrap an error that has NO stack:
+		{title: "pkg.WithMessage(pkg.WithStack(go.New()))", err: pkg_errors.WithMessage(
+			pkg_errors.WithStack(
+				go_errors.New("<this is broken>"),
+			),
+			"<it is not working>",
+		)},
+
+		//we will typically pass an error up several layers before it is reported
+		//and the original error may come without a stack,
+		//and another package might have added a stack using pkg
+		//then we still add our own explanations on top of that
+		//in both the following two examples, we have the following stack:
+		//
+		//		db not open						(without a stack)
+		//		query failed					(without a stack, using fmt.Errorf("query failed: %v", err))
+		//		failed to read list				(stack added with pkg.WithStack(), then message added with pkg.WithMessage())
+		//		failed to read users from db	(demonstrated with pkg.WithMessage() and our own Wrapf())
+		//		cannot display list of users	(demonstrated with pkg.WithMessage() and our own Wrapf())
+		//
+		//first using only pkg:
+		{title: "full stack with pkg", err: pkg_errors.WithMessage(
+			pkg_errors.WithMessage(
+				pkg_errors.WithStack( //assuming this comes from 3rd party lib e.g. sql or pg - we cannot change this or deeper errors
+					pkg_errors.WithMessage(
+						fmt.Errorf(
+							"query failed: %v",
+							go_errors.New("db not open"),
+						),
+						"failed to read list",
+					),
+				),
+				"failed to read users from db",
+			),
+			"cannot display list of users",
+		)},
+
+		//same thing but wrapping at the top with out own messages and stack
+		{title: "full stack with uafrica", err: uf_errors.Wrapf(
+			uf_errors.Wrapf(
+				pkg_errors.WithStack( //assuming this comes from 3rd party lib e.g. sql or pg - we cannot change this or deeper errors
+					pkg_errors.WithMessage(
+						fmt.Errorf(
+							"query failed: %v",
+							go_errors.New("db not open"),
+						),
+						"failed to read list",
+					),
+				),
+				"failed to read users from db",
+			),
+			"cannot display list of users",
+		)},
+
+		//============================================================================================================================================
+		//in the final full stack case, here is the comparison:
+		//============================================================================================================================================
+		//  OUTPUT OF err.Errorf()
+		//  ----------------------
+		//
+		//	pkg:		cannot display list of users: failed to read users from db: failed to read list: query failed: db not open
+		//
+		//	uafrica:	cannot display list of users, because failed to read users from db, because failed to read list: query failed: db not open
+		//
+		//	(at the end, uafrica could not insert ", because ..." because the deepest level was just text from fmt.Errorf(...))
+		//============================================================================================================================================
+		//  OUTPUT OF Printf("%s") for simple error
+		//  ---------------------------------------
+		//
+		//	pkg:		cannot display list of users: failed to read users from db: failed to read list: query failed: db not open
+		//
+		//	uafrica:	cannot display list of users
+		//
+		//	Note that pkg has no simple way to just display the top level error, while we can do it with formatting directive "%s"
+		//============================================================================================================================================
+		//  OUTPUT OF Printf("%+v") for full error stack
+		//  --------------------------------------------
+		//
+		//	pkg:
+		//		query failed: db not open
+		// 		failed to read list
+		// 		command-line-arguments_test.init.0
+		// 			/Users/jansemmelink/uafrica/go-utils/errors/other_test.go:96
+		// 		runtime.doInit
+		// 			/usr/local/go/src/runtime/proc.go:6498
+		// 		runtime.doInit
+		// 			/usr/local/go/src/runtime/proc.go:6475
+		// 		runtime.main
+		// 			/usr/local/go/src/runtime/proc.go:238
+		// 		runtime.goexit
+		// 			/usr/local/go/src/runtime/asm_arm64.s:1133
+		// 		failed to read users from db
+		// 		cannot display list of users
+		//
+		//	uafrica:
+		//		command-line-arguments_test.init/other_test.go(111): 0() cannot display list of users, because
+		//		command-line-arguments_test.init/other_test.go(112): 0() failed to read users from db, because
+		//		command-line-arguments_test.init.0/other_test.go(113): init.0(): failed to read list: query failed: db not open
+		//
+		//	Difference:
+		//		- pkg       starts with the lowest level then what effects it had (opposite order from how it prints err.Error())
+		//		  uafrica   maintains the order of effect then the reason why, which kind of makes more sense
+		//		- pkg       write multiple lines for each stack entry, and does not keep the messages to the stack lines
+		//		  uafrica   put the error message next to the stack line that wrote it
+		//		- pkg       wrapping with text, does not include the stack again, so line 94 and 95 that passed the error on, is not mentioned
+		//        uafrica   mention each line that handled the error: 111, 112, 113 (called libs in line 114..117 did not include the stack)
+		//		- pkg       reports full package and FULL filename (host path where the code was compiled) which is not useful in production
+		//		  uafrica   reports the package and BASE filename and function name instead.
+		//============================================================================================================================================
+		//  Compact Stack:
+		//	--------------
+		//	In streaming log files we do not want a multi-line display of error messages, so uafrica also supports "%c" and "%+c" formatting to print
+		//	on one line with ';' instead of new lines. The '+' adds the source package, file, line and function name.
+		//
+		//	"%c" is same as err.Error():
+		//
+		//		cannot display list of users, because failed to read users from db, because failed to read list: query failed: db not open
+		//
+		//	"%+c" is that plus the source references:
+		//
+		//		command-line-arguments_test.init/error_formats_test.go(111): 0() cannot display list of users, because command-line-arguments_test.init/error_formats_test.go(112): 0() failed to read users from db, because command-line-arguments_test.init.0/error_formats_test.go(113): init.0(): failed to read list: query failed: db not open
+		//
+		//	"%v"  is same as "%c"  over multiple lines:
+		//
+		//		cannot display list of users, because
+		// 		failed to read users from db, because
+		// 		failed to read list: query failed: db not open
+		//
+		//	"%+v" is same as "%+c" over multiple lines: (as in previous section where compared with pkg):
+		//
+		//		command-line-arguments_test.init/error_formats_test.go(111): 0() cannot display list of users, because
+		//		command-line-arguments_test.init/error_formats_test.go(112): 0() failed to read users from db, because
+		//		command-line-arguments_test.init.0/error_formats_test.go(113): init.0(): failed to read list: query failed: db not open
+		//
+		//============================================================================================================================================
+		//	JSON:
+		//	-----
+		//  Having our own errors package allows us also flexibility to do things like creating a JSON error description.
+		//	This is illustrated in the JSON output:
+		//		{"error":"cannot display list of users","source":{"package":"command-line-arguments_test.init","file":"other_test.go","line":111,"function":"0"},
+		//		"cause":{"error":"failed to read users from db","source":{"package":"command-line-arguments_test.init","file":"other_test.go","line":112,
+		//		"function":"0"},"cause":{"error":"failed to read list: query failed: db not open","source":{"package":"command-line-arguments_test.init.0",
+		//		"file":"other_test.go","line":113,"function":"init.0"},"cause":{"error":"failed to read list: query failed: db not open","cause":{
+		//		"error":"query failed: db not open"}}}}}
+		//	Which we can pretty print with jq into a very readable stack:
+		//
+		//  {
+		// 	  "error": "cannot display list of users",
+		// 	  "source": {
+		// 	    "package": "command-line-arguments_test.init",
+		// 	    "file": "other_test.go",
+		// 	    "line": 111,
+		// 	    "function": "0"
+		// 	  },
+		// 	  "cause": {
+		// 	    "error": "failed to read users from db",
+		// 	    "source": {
+		// 	      "package": "command-line-arguments_test.init",
+		// 	      "file": "other_test.go",
+		// 	      "line": 112,
+		// 	      "function": "0"
+		// 	    },
+		// 	    "cause": {
+		// 	      "error": "failed to read list: query failed: db not open",
+		// 	      "source": {
+		// 	        "package": "command-line-arguments_test.init.0",
+		// 	        "file": "other_test.go",
+		// 	        "line": 113,
+		// 	        "function": "init.0"
+		// 	      },
+		// 	      "cause": {
+		// 	        "error": "failed to read list: query failed: db not open",
+		// 	        "cause": {
+		// 	          "error": "query failed: db not open"
+		// 	        }
+		// 	      }
+		// 	    }
+		// 	  }
+		// 	}
+		//============================================================================================================================================
+	}
+}
+
+func TestErrorFormats(t *testing.T) {
+	for _, test := range tests {
+		show(t, test.title, test.err)
+	}
+}
+
+func show(t *testing.T, title string, err error) {
+	t.Logf("------------------------------------------------------------------------------------")
+	t.Logf("-----[ %-70.70s ]-----", title)
+	t.Logf("------------------------------------------------------------------------------------")
+	t.Logf("err.Error():  %s", err.Error())
+	t.Logf("Printf(%%s):  %s", err)
+	if _, ok := err.(*uf_errors.Error); ok { //compact only supported on our own lib:
+		t.Logf("Printf(%%c):  %c", err)
+		t.Logf("Printf(%%+c): %+c", err)
+	}
+	t.Logf("Printf(%%v):  %v", err)
+	t.Logf("Printf(%%+v): %+v", err)
+	if e, ok := err.(*uf_errors.Error); ok { //description only supported on our own lib:
+		desc := e.Description()
+		jsonDesc, _ := json.Marshal(desc)
+		t.Logf("JSON: %s", string(jsonDesc))
+	}
+}
diff --git a/errors/errors.go b/errors/errors.go
new file mode 100644
index 0000000..ca18e57
--- /dev/null
+++ b/errors/errors.go
@@ -0,0 +1,82 @@
+package errors
+
+import (
+	"fmt"
+
+	pkg_errors "github.com/pkg/errors"
+)
+
+//extends default golang error interface
+type ErrorWithCause interface {
+	error
+	Cause() error
+}
+
+func New(message string) error {
+	err := &Error{
+		message: message,
+		caller:  GetCaller(2),
+		//stack:   Stack(3),
+		cause: nil,
+	}
+	return err
+}
+
+func NError(message string) error {
+	err := &Error{
+		message: message,
+		caller:  GetCaller(2),
+		//stack:   Stack(3),
+		cause: nil,
+	}
+	return err
+}
+
+func Errorf(format string, args ...interface{}) error {
+	err := &Error{
+		message: fmt.Sprintf(format, args...),
+		caller:  GetCaller(2),
+		//stack:   Stack(3),
+		cause: nil,
+	}
+	return err
+}
+
+func Wrapf(err error, format string, args ...interface{}) error {
+	if err == nil {
+		return nil
+	}
+
+	wrappedErr := &Error{
+		message: fmt.Sprintf(format, args...),
+		caller:  GetCaller(2),
+		cause:   err,
+	}
+
+	return wrappedErr
+}
+
+func Wrap(err error, msg string) error {
+	if err == nil {
+		return nil
+	}
+
+	wrappedErr := &Error{
+		message: msg,
+		caller:  GetCaller(2),
+		cause:   err,
+	}
+
+	return wrappedErr
+}
+
+type Description struct {
+	Message string       `json:"error"`
+	Source  *CallerInfo  `json:"source,omitempty"`
+	Cause   *Description `json:"cause,omitempty"`
+}
+
+//from github.com/pkg/errors:
+type stackTracer interface {
+	StackTrace() pkg_errors.StackTrace
+}
diff --git a/errors/errors_test.go b/errors/errors_test.go
new file mode 100644
index 0000000..0c40721
--- /dev/null
+++ b/errors/errors_test.go
@@ -0,0 +1,92 @@
+package errors_test
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/uafrica/go-utils/errors"
+)
+
+func TestErrorFormatting(t *testing.T) {
+	//pretend this is your error stack
+	//the original error is created by 3rd party library e.g. pg or mysql and may not have stack info
+	//but from that point on, we use our errors package which includes stack info...
+	e1 := errors.Errorf("you have problem in your SQL near xxx")
+	e2 := errors.Wrapf(e1, "query failed")
+	e3 := errors.Wrapf(e2, "failed to find account")
+	e4 := errors.Wrapf(e3, "login failed").(*errors.Error)
+
+	//we can log the error in different ways:
+
+	//use %s for a simple message
+	//-> "login failed"
+	t.Logf("%%s: %s", e4)
+
+	//use either Error() or "%v" for concatenated message:
+	//-> "login failed, because failed to find account, because query failed, because you have problem in your SQL near xxx"
+	t.Logf("Error(): %s", e4.Error())
+	t.Logf("%%v: %v", e4)
+
+	//use %+v for complete error with stack:
+	//-> "github.com/uafrica/go-utils/errors_test/TestErrorFormatting():errors_test.go(17): login failed, because github.com/uafrica/go-utils/errors_test/TestErrorFormatting():errors_test.go(16): failed to find account, because github.com/uafrica/go-utils/errors_test/TestErrorFormatting():errors_test.go(15): query failed, because you have problem in your SQL near xxx
+	t.Logf("%%+v: %+v", e4)
+
+	//you may also JSON marshal the error
+	//->
+	jsonError, _ := json.Marshal(e4.Description())
+	t.Logf("json: %s", string(jsonError))
+
+	//using the Description(), one can also verify that the correct
+	//source references are reported
+	desc := e4.Description()
+	if desc.Source == nil || desc.Source.Line != 18 {
+		t.Fatalf("failed not on line 18")
+	}
+	desc = *desc.Cause
+	if desc.Source == nil || desc.Source.Line != 17 {
+		t.Fatalf("failed not on line 17")
+	}
+	desc = *desc.Cause
+	if desc.Source == nil || desc.Source.Line != 16 {
+		t.Fatalf("failed not on line 16")
+	}
+}
+
+func TestErrorStack(t *testing.T) {
+	//if some function (e.g. 3rd party) fail without stack, then store the calling stack and append as it is wrapped from there...
+	err := a(1)
+	t.Logf("err: %+v", err)
+}
+
+func a(i int) error {
+	return errors.Wrapf(b(i), "b failed")
+}
+
+func b(i int) error {
+	return errors.Wrapf(c(i), "c failed")
+}
+
+func c(i int) error {
+	return errors.Wrapf(d(i), "d failed")
+}
+
+func d(i int) error {
+	if err := e(i); err != nil {
+		return errors.Wrapf(err, "e(%d) failed", i)
+	}
+	return nil
+}
+
+func e(i int) error {
+	if err := f(i); err != nil {
+		return errors.Wrapf(err, "f(%d) failed", i)
+	}
+	return nil
+}
+
+func f(i int) error {
+	if i%2 == 0 {
+		return nil
+	}
+	return errors.Errorf("i=%d is odd", i)
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..162ef1d
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,18 @@
+module github.com/uafrica/go-utils
+
+go 1.17
+
+require (
+	github.com/go-pg/pg/v10 v10.10.5 // indirect
+	github.com/go-pg/zerochecker v0.2.0 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/pkg/errors v0.9.1 // indirect
+	github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
+	github.com/vmihailenco/bufpool v0.1.11 // indirect
+	github.com/vmihailenco/msgpack/v5 v5.3.1 // indirect
+	github.com/vmihailenco/tagparser v0.1.2 // indirect
+	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
+	golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
+	golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect
+	mellium.im/sasl v0.2.1 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..48695eb
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,139 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/go-pg/pg/v10 v10.10.5 h1:RRW8NqxVu4vgzN9k05TT9rM5X+2VQHcIBRLeK9djMBE=
+github.com/go-pg/pg/v10 v10.10.5/go.mod h1:EmoJGYErc+stNN/1Jf+o4csXuprjxcRztBnn6cHe38E=
+github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
+github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
+github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
+github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
+github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
+github.com/vmihailenco/msgpack/v5 v5.3.1 h1:0i85a4dsZh8mC//wmyyTEzidDLPQfQAxZIOLtafGbFY=
+github.com/vmihailenco/msgpack/v5 v5.3.1/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
+github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
+github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
+github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
+github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
+golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w=
+mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ=
-- 
GitLab