From 90267f8d76e81cf8572d61e4444c2d500954c499 Mon Sep 17 00:00:00 2001
From: Jan Semmelink <jan@uafrica.com>
Date: Fri, 17 Sep 2021 13:42:24 +0200
Subject: [PATCH] Added HTTP error

---
 errors/error.go           | 26 ++++++++++++++++++++
 errors/errors.go          | 16 ++++++++++++
 errors/http-error_test.go | 51 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 93 insertions(+)
 create mode 100644 errors/http-error_test.go

diff --git a/errors/error.go b/errors/error.go
index f78c694..53c7c7e 100644
--- a/errors/error.go
+++ b/errors/error.go
@@ -2,6 +2,7 @@ package errors
 
 import (
 	"fmt"
+	"net/http"
 	"path"
 	"strconv"
 	"strings"
@@ -11,6 +12,7 @@ import (
 //	error
 //	github.com/pkg/errors: Cause
 type CustomError struct {
+	code    int
 	message string
 	caller  Caller
 	cause   error
@@ -26,6 +28,27 @@ func (err CustomError) Cause() error {
 	return err.cause
 }
 
+func HTTPCode(err error) int {
+	if errWithCode, ok := err.(ErrorWithCause); ok {
+		return errWithCode.Code()
+	}
+	return 0
+}
+
+func (err CustomError) Code() int {
+	//find http error code - returning the smallest code in the stack of causes (excluding code==0)
+	code := err.code
+	if err.cause != nil {
+		if causeWithCode, ok := err.cause.(ErrorWithCause); ok {
+			causeCode := causeWithCode.Code()
+			if code == 0 || (causeCode != 0 && causeCode < code) {
+				code = causeCode
+			}
+		}
+	}
+	return code
+}
+
 func (err CustomError) Description() Description {
 	info := err.caller.Info()
 	desc := &Description{
@@ -77,6 +100,9 @@ func (err CustomError) Formatted(opts FormattingOptions) string {
 		)
 	}
 	thisError += err.message
+	if err.code != 0 {
+		thisError += fmt.Sprintf(" HTTP(%d:%s)", err.code, http.StatusText(err.code))
+	}
 	if !opts.Causes {
 		return thisError
 	}
diff --git a/errors/errors.go b/errors/errors.go
index 0288721..f81e17b 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -10,6 +10,7 @@ import (
 type ErrorWithCause interface {
 	error
 	Cause() error
+	Code() int
 }
 
 func New(message string) error {
@@ -67,6 +68,21 @@ func Wrap(err error, msg string) error {
 	return wrappedErr
 }
 
+func HTTP(code int, err error, format string, args ...interface{}) error {
+	if err == nil {
+		return nil
+	}
+
+	wrappedErr := &CustomError{
+		code:    code,
+		message: fmt.Sprintf(format, args...),
+		caller:  GetCaller(2),
+		cause:   err,
+	}
+
+	return wrappedErr
+}
+
 type Description struct {
 	Message string       `json:"error"`
 	Source  *CallerInfo  `json:"source,omitempty"`
diff --git a/errors/http-error_test.go b/errors/http-error_test.go
new file mode 100644
index 0000000..609d47a
--- /dev/null
+++ b/errors/http-error_test.go
@@ -0,0 +1,51 @@
+package errors_test
+
+import (
+	"net/http"
+	"testing"
+
+	"gitlab.com/uafrica/go-utils/errors"
+)
+
+func TestHTTPError(t *testing.T) {
+	var err error
+
+	//you can wrap any error with an HTTP code:
+	err = errors.Errorf("failed to connect to db")
+	//err = errors.HTTP(http.StatusInternalServerError, err)
+
+	//or if you know you are creating the HTTP error, do it as one statement
+	err = errors.HTTP(http.StatusBadRequest, err, "failed to get user")
+
+	//and one more to give a lower code again
+	err = errors.HTTP(http.StatusInsufficientStorage, err, "jissis this is bad!")
+
+	//and higher again -... many layers...
+	err = errors.HTTP(http.StatusNotFound, err, "terrible mistake")
+
+	//now log:
+	t.Logf("failed:\n\t%+v", err)
+	t.Logf("HTTP Code: %d", errors.HTTPCode(err)) //will return smallest code in the stack, 0 if none.
+
+	//you can wrap any error with an HTTP code:
+	err = errors.Errorf("failed to connect to db")
+	//err = errors.HTTP(http.StatusInternalServerError, err)
+
+	//and one more to give a lower code again
+	err = errors.HTTP(http.StatusInsufficientStorage, err, "jissis this is bad!")
+
+	//and higher again -... many layers...
+	err = errors.HTTP(http.StatusNotFound, err, "terrible mistake")
+
+	//or if you know you are creating the HTTP error, do it as one statement
+	err = errors.HTTP(http.StatusBadRequest, err, "failed to get user")
+
+	//now log:
+	t.Logf("failed:\n\t%+v", err)
+	t.Logf("HTTP Code: %d", errors.HTTPCode(err)) //will return smallest code in the stack, 0 if none.
+
+	err = errors.Errorf("failed to connect to db")
+	err = errors.Wrapf(err, "failed to connect to db")
+	t.Logf("failed:\n\t%+v", err)
+	t.Logf("HTTP Code: %d", errors.HTTPCode(err)) //will return smallest code in the stack, 0 if none.
+}
-- 
GitLab