From 55de7da46a48287192e7eda95d61326458c67a1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?France=CC=81=20Wilke?= <francewilke@gmail.com>
Date: Tue, 8 Aug 2023 11:01:40 +0200
Subject: [PATCH] Remove errors.New, add Capitalize

---
 LICENSE                                     |  2 +-
 api_responses/api_responses.go              | 10 +--
 audit/audit.go                              |  4 +-
 bank_transactions/absa_bank_transactions.go |  2 +-
 cognito/cognito.go                          |  2 +-
 encryption/encryption.go                    | 18 ++--
 errors/errors.go                            |  9 --
 handler_utils/handler.go                    |  5 --
 logs/logs.go                                |  9 +-
 logs/stack.go                               | 91 ---------------------
 string_utils/string_utils.go                |  7 ++
 string_utils/string_utils_test.go           |  6 +-
 struct_utils/named_values_to_struct.go      | 36 ++++----
 13 files changed, 52 insertions(+), 149 deletions(-)
 delete mode 100644 logs/stack.go

diff --git a/LICENSE b/LICENSE
index 54548ad..550a15e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 BSD 3-Clause License
 
-Copyright (c) 2022, uAfrica Technologies (Pty) Ltd
+Copyright (c) 2023, Bob Group (Pty) Ltd
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/api_responses/api_responses.go b/api_responses/api_responses.go
index 768113b..bb2810a 100644
--- a/api_responses/api_responses.go
+++ b/api_responses/api_responses.go
@@ -2,8 +2,8 @@ package api_responses
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
+	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
 	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/map_utils"
 	"net/http"
 	"regexp"
@@ -58,7 +58,7 @@ func Error(err error, msg string, statusCode int) (events.APIGatewayProxyRespons
 		StatusCode: statusCode,
 		Headers:    map_utils.MergeMaps(utils.CorsHeaders(), responses.ContentTypeJSONHeader),
 		Body:       string(bodyBytes),
-	}, errors.New(msg)
+	}, errors.Error(msg)
 }
 
 func DatabaseServerErrorNew(err error, msg string) error {
@@ -87,7 +87,7 @@ func DatabaseServerErrorNew(err error, msg string) error {
 	}
 
 	return ServerErrorStruct{
-		error:   errors.New(errorString),
+		error:   errors.Error(errorString),
 		Message: msg,
 	}
 }
@@ -221,7 +221,7 @@ func ClientError(status int, message string) (events.APIGatewayProxyResponse, er
 	logs.WarnWithFields(map[string]interface{}{
 		"type": "Client error",
 		"code": status,
-	}, errors.New(message))
+	}, errors.Error(message))
 
 	e := errorMsg{
 		Message: message,
@@ -235,7 +235,7 @@ func ClientError(status int, message string) (events.APIGatewayProxyResponse, er
 		StatusCode: status,
 		Headers:    map_utils.MergeMaps(utils.CorsHeaders(), responses.ContentTypeJSONHeader),
 		Body:       string(b),
-	}, errors.New(message)
+	}, errors.Error(message)
 }
 
 func StatusCodeFromSQLError(err error) int {
diff --git a/audit/audit.go b/audit/audit.go
index 5805135..319083b 100644
--- a/audit/audit.go
+++ b/audit/audit.go
@@ -24,14 +24,14 @@ func VerifyAuditEvents(original interface{}, new interface{}) error {
 	if original != nil {
 		structValue := reflect.ValueOf(original)
 		if structValue.Kind() != reflect.Struct {
-			return errors.New("original object is not of type struct")
+			return errors.Error("original object is not of type struct")
 		}
 	}
 
 	if new != nil {
 		structValue := reflect.ValueOf(new)
 		if structValue.Kind() != reflect.Struct {
-			return errors.New("new object is not of type struct")
+			return errors.Error("new object is not of type struct")
 		}
 	}
 
diff --git a/bank_transactions/absa_bank_transactions.go b/bank_transactions/absa_bank_transactions.go
index 1f2400b..7b22037 100644
--- a/bank_transactions/absa_bank_transactions.go
+++ b/bank_transactions/absa_bank_transactions.go
@@ -219,7 +219,7 @@ func startStatementDownloadProcess(client *resty.Client) error {
 
 	// Ensure we are on the "Statement Enquiry" screen
 	if !strings.Contains(string(responseBytes), "Statement Enquiry") {
-		return errors.New("Could not load 'Statement Enquiry' screen")
+		return errors.Error("Could not load 'Statement Enquiry' screen")
 	}
 	time.Sleep(sleepTime)
 	return nil
diff --git a/cognito/cognito.go b/cognito/cognito.go
index ac79da6..3df6c2d 100644
--- a/cognito/cognito.go
+++ b/cognito/cognito.go
@@ -146,7 +146,7 @@ func ConfirmPasswordReset(appClientID string, username string, password string,
 		return confirmPasswordReset(appClientID, username, password, res)
 	}
 
-	return nil, errors.New("User state not correct for confirmation. Please contact support.")
+	return nil, errors.Error("User state not correct for confirmation. Please contact support.")
 }
 
 // FOR API LOGS
diff --git a/encryption/encryption.go b/encryption/encryption.go
index 8d6c99a..bca0d80 100644
--- a/encryption/encryption.go
+++ b/encryption/encryption.go
@@ -51,7 +51,7 @@ func Md5HashString(bytesToHash []byte) string {
 
 func EncryptStruct(object any, key string) (string, error) {
 	if len(key) != 32 {
-		return "", errors.New("key should be 32 bytes")
+		return "", errors.Error("key should be 32 bytes")
 	}
 
 	block, err := aes.NewCipher([]byte(key))
@@ -80,7 +80,7 @@ func EncryptStruct(object any, key string) (string, error) {
 
 func DecryptStruct(encryptedStruct string, key string, object any) error {
 	if len(key) != 32 {
-		return errors.New("key should be 32 bytes")
+		return errors.Error("key should be 32 bytes")
 	}
 
 	decodedStruct, _ := base64.StdEncoding.DecodeString(encryptedStruct)
@@ -98,7 +98,7 @@ func DecryptStruct(encryptedStruct string, key string, object any) error {
 
 	nonceSize := aesGcm.NonceSize()
 	if len(decodedStructString) < nonceSize {
-		return errors.New("ciphertext too short")
+		return errors.Error("ciphertext too short")
 	}
 
 	nonce, ciphertext := decodedStructString[:nonceSize], decodedStructString[nonceSize:]
@@ -113,7 +113,7 @@ func DecryptStruct(encryptedStruct string, key string, object any) error {
 
 func EncryptByteArray(byteArray []byte, key string) (string, error) {
 	if len(key) != 32 {
-		return "", errors.New("key should be 32 bytes")
+		return "", errors.Error("key should be 32 bytes")
 	}
 
 	block, err := aes.NewCipher([]byte(key))
@@ -137,7 +137,7 @@ func EncryptByteArray(byteArray []byte, key string) (string, error) {
 
 func DecryptByteArray(encryptedByteArray []byte, key string, object any) error {
 	if len(key) != 32 {
-		return errors.New("key should be 32 bytes")
+		return errors.Error("key should be 32 bytes")
 	}
 
 	block, err := aes.NewCipher([]byte(key))
@@ -152,7 +152,7 @@ func DecryptByteArray(encryptedByteArray []byte, key string, object any) error {
 
 	nonceSize := aesGcm.NonceSize()
 	if len(encryptedByteArray) < nonceSize {
-		return errors.New("ciphertext too short")
+		return errors.Error("ciphertext too short")
 	}
 
 	nonce, ciphertext := encryptedByteArray[:nonceSize], encryptedByteArray[nonceSize:]
@@ -167,7 +167,7 @@ func DecryptByteArray(encryptedByteArray []byte, key string, object any) error {
 
 func Encrypt(plaintext string, key string) (string, error) {
 	if len(key) != 32 {
-		return "", errors.New("key should be 32 bytes")
+		return "", errors.Error("key should be 32 bytes")
 	}
 
 	block, err := aes.NewCipher([]byte(key))
@@ -190,7 +190,7 @@ func Encrypt(plaintext string, key string) (string, error) {
 
 func Decrypt(ciphertext string, key string) (string, error) {
 	if len(key) != 32 {
-		return "", errors.New("key should be 32 bytes")
+		return "", errors.Error("key should be 32 bytes")
 	}
 
 	block, err := aes.NewCipher([]byte(key))
@@ -205,7 +205,7 @@ func Decrypt(ciphertext string, key string) (string, error) {
 
 	nonceSize := aesGcm.NonceSize()
 	if len(ciphertext) < nonceSize {
-		return "", errors.New("ciphertext too short")
+		return "", errors.Error("ciphertext too short")
 	}
 
 	nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
diff --git a/errors/errors.go b/errors/errors.go
index bd6c2af..d8245cc 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -18,15 +18,6 @@ type ErrorWithIs interface {
 	Is(specificError error) bool
 }
 
-func New(message string) error {
-	err := &CustomError{
-		message: message,
-		caller:  GetCaller(2),
-		cause:   nil,
-	}
-	return err
-}
-
 func Error(message string) error {
 	err := &CustomError{
 		message: message,
diff --git a/handler_utils/handler.go b/handler_utils/handler.go
index 56a9af0..be466ce 100644
--- a/handler_utils/handler.go
+++ b/handler_utils/handler.go
@@ -49,11 +49,6 @@ func NewHandler(handlerFunction interface{}) (Handler, error) {
 			}
 		}
 
-		// todo: check special fields for claims, and see if also applies to params struct...
-		// AccountID must be int64 or *int64 with tag =???
-		// UserID must be int64 or *int64 with tag =???
-		// Username must be string with tag =???
-
 		h.RequestBodyType = handlerFunctionType.In(1)
 	}
 
diff --git a/logs/logs.go b/logs/logs.go
index f1b4c59..42fa46c 100644
--- a/logs/logs.go
+++ b/logs/logs.go
@@ -1,8 +1,8 @@
 package logs
 
 import (
-	"errors"
 	"fmt"
+	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
 	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils"
 	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/utils"
 	"net/http"
@@ -27,9 +27,6 @@ var isDebug = false
 var build string
 var raygunClient *raygun4go.Client
 
-// TODO
-// Sensitive word filtering
-
 // Password filtering
 var passwordRegex = regexp.MustCompile(`(?i:\\?"password\\?"\s*:\s*\\?"(.*)\\?",).*`)
 
@@ -191,7 +188,7 @@ func ErrorWithFields(fields map[string]interface{}, err error) {
 
 func ErrorWithMsg(message string, err error) {
 	if err == nil {
-		err = errors.New(message)
+		err = errors.Error(message)
 	}
 	ErrorWithFields(map[string]interface{}{
 		"message": message,
@@ -283,7 +280,7 @@ func sendRaygunError(fields map[string]interface{}, errToSend error) {
 	raygunClient.Request(fakeHttpRequest())
 
 	if errToSend == nil {
-		errToSend = errors.New("")
+		errToSend = errors.Error("")
 	}
 	err := raygunClient.SendError(errToSend)
 	if err != nil {
diff --git a/logs/stack.go b/logs/stack.go
deleted file mode 100644
index c64b584..0000000
--- a/logs/stack.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package logs
-
-import (
-	"bufio"
-	"runtime/debug"
-	"strconv"
-	"strings"
-
-	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
-)
-
-type Stack struct {
-	Routine int64 // go routine nr that crashed...
-	Callers []errors.CallerInfo
-}
-
-func CallStack() Stack {
-	stack := Stack{
-		Callers: []errors.CallerInfo{},
-	}
-
-	// get the call stack
-	s := bufio.NewScanner(strings.NewReader(string(debug.Stack())))
-	// expect stack to look like this:
-	//   "goroutine 14 [running]:\nruntime/debug.Stack()\n\t/usr/local/go/src/runtime/debug/stack.go:24 +0x88\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.Handler.func3.1(0x1400009ff60)\n\t/Users/jansemmelink/uafrica/go-utils/api/lambda.go:210 +0x48\npanic({0x100780d20, 0x100b98a50})\n\t/usr/local/go/src/runtime/panic.go:1038 +0x21c\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/examples/core/api/users.Crash({{{0x1008237e0, 0x1400040af30}, {0x4, {0x1008186e0, 0x14000010020}, 0x1400040af90, 0x25, 0x2f}, {0xc04f2cb513fbdc28, 0x103ccb5b4c, ...}}, ...}, ...)\n\t/Users/jansemmelink/uafrica/go-utils/examples/core/api/users/users.go:115 +0x20\nreflect.Value.call({0x10076bba0, 0x10080cdc8, 0x13}, {0x10059bb1c, 0x4}, {0x140000a0730, 0x2, 0x2})\n\t/usr/local/go/src/reflect/value.go:543 +0x584\nreflect.Value.Call({0x10076bba0, 0x10080cdc8, 0x13}, {0x140000a0730, 0x2, 0x2})\n\t/usr/local/go/src/reflect/value.go:339 +0x8c\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.Handler.func3({{0x10082f730, 0x100780960}, {0x0, 0x0}, {0x0, 0x0}, {0x10076bba0, 0x10080cdc8, 0x13}}, {0x140000a0730, ...})\n\t/Users/jansemmelink/uafrica/go-utils/api/lambda.go:214 +0x84\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.Handler({{0x10082b3f0, 0x140003b6480}, {0x10059b80a, 0x3}, {0x140003b6360}, {0x1005a3606, 0x12}, 0x10080cf98, {0x1008190e0, 0x100bdd308}, ...}, ...)\n\t/Users/jansemmelink/uafrica/go-utils/api/lambda.go:216 +0x1238\ngitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.ServeHTTP({{0x10082b3f0, 0x140003b6480}, {0x10059b80a, 0x3}, {0x140003b6360}, {0x1005a3606, 0x12}, 0x10080cf98, {0x1008190e0, 0x100bdd308}, ...}, ...)\n\t/Users/jansemmelink/uafrica/go-utils/api/local.go:81 +0x6ac\nnet/http.serverHandler.ServeHTTP({0x1400015a620}, {0x1008200e8, 0x1400015aa80}, 0x140003c6900)\n\t/usr/local/go/src/net/http/server.go:2878 +0x444\nnet/http.(*conn).serve(0x14000279220, {0x1008237e0, 0x140003b6780})\n\t/usr/local/go/src/net/http/server.go:1929 +0xb6c\ncreated by net/http.(*Server).Serve\n\t/usr/local/go/src/net/http/server.go:3033 +0x4b8\n"
-	// i.e. multiple lines ending with "\n" each, e.g.:
-	// ------------------------------------------------------------------------------------------------------------
-	// goroutine 37 [running]:
-	// runtime/debug.Stack()
-	// 	/usr/local/go/src/runtime/debug/stack.go:24 +0x88
-	// gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/api.Api.Handler.func3.1(0x1400042cb00, 0x14000095f68)
-	// 	/Users/jansemmelink/uafrica/go-utils/api/lambda.go:216 +0x50
-	// panic({0x100c08d20, 0x101020a70})
-	// 	/usr/local/go/src/runtime/panic.go:1038 +0x21c
-	// gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/examples/core/api/users.Crash({{{0x100cab7c0, 0x140004843c0}, {0x4, {0x100ca06c0, 0x14000138010}, 0x14000484420, 0x20, 0x28}, {0xc04f2d6c3023b330, 0x227861e6a, ...}}, ...}, ...)
-	// 	/Users/jansemmelink/uafrica/go-utils/examples/core/api/users/users.go:115 +0x20
-	// ...
-	// ------------------------------------------------------------------------------------------------------------
-
-	// get go routine nr from first line: "gorouting <nr> [running]:"
-	if s.Scan() {
-		p := strings.SplitN(s.Text(), " ", 3)
-		if len(p) >= 2 && p[0] == "goroutine" {
-			routineNr, err := strconv.ParseInt(p[1], 10, 64)
-			if err == nil {
-				stack.Routine = routineNr
-			}
-		}
-	}
-
-	// next expect line pairs for each level of the stack
-	for {
-		// read first line in this pair, expecting <package>.<funcName>(<args>)
-		if !s.Scan() {
-			break
-		}
-		line1 := s.Text()
-
-		if !s.Scan() {
-			break
-		}
-		line2 := s.Text()
-
-		// fmt.Printf("  STACK LINE: %s %s\n", line1, line2)
-
-		// split line 1 on any bracket or comma to get "<package>.<funcName>"["<arg>" ...]
-		// func may have multiple '.', so do not split on that yet!
-		line1Fields := strings.FieldsFunc(line1, func(c rune) bool { return strings.Contains("(), ", string(c)) })
-
-		// split line 2 <file>:<line> +0x##...
-		line2Fields := strings.FieldsFunc(line2, func(c rune) bool { return strings.Contains(": ", string(c)) })
-		lineNr, _ := strconv.ParseInt(line2Fields[1], 10, 64)
-		caller := errors.NewCaller(line1Fields[0], line2Fields[0], int(lineNr))
-
-		// skip first levels that refer to capturing the stack
-		ci := caller.Info()
-		if len(stack.Callers) == 0 {
-			if ci.Package == "runtime/debug" ||
-				ci.Package == "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs" ||
-				ci.Package == "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors" ||
-				ci.Package == "" {
-				continue
-			}
-			if _, err := strconv.ParseInt(ci.Function, 10, 64); err == nil {
-				continue // typical defer function without a name used to catch the crash
-			}
-		}
-		stack.Callers = append(stack.Callers, caller.Info())
-	}
-	return stack
-}
diff --git a/string_utils/string_utils.go b/string_utils/string_utils.go
index 995e94a..9a36862 100644
--- a/string_utils/string_utils.go
+++ b/string_utils/string_utils.go
@@ -200,6 +200,13 @@ func SentenceCase(str string) string {
 	return ""
 }
 
+func Capitalize(s string) string {
+	if len(s) == 0 {
+		return s
+	}
+	return string(unicode.ToUpper(rune(s[0]))) + s[1:]
+}
+
 // SplitString separates a string on any character in the list of sep
 func SplitString(str string, sep []rune) []string {
 	splitStrings := strings.FieldsFunc(str, func(c rune) bool {
diff --git a/string_utils/string_utils_test.go b/string_utils/string_utils_test.go
index 3c52e65..04da567 100644
--- a/string_utils/string_utils_test.go
+++ b/string_utils/string_utils_test.go
@@ -13,17 +13,17 @@ func TestIsValidEmail(t *testing.T) {
 	}{
 		{
 			name: "valid",
-			args: args{email: "johandk@uafrica.com"},
+			args: args{email: "johan@bob.co.za"},
 			want: true,
 		},
 		{
 			name: "invalid",
-			args: args{email: "johandk@@uafrica.com"},
+			args: args{email: "johan@bob.co.za"},
 			want: false,
 		},
 		{
 			name: "invalid",
-			args: args{email: "johandk@uafricacom"},
+			args: args{email: "johan@bob.co.za"},
 			want: false,
 		},
 	}
diff --git a/struct_utils/named_values_to_struct.go b/struct_utils/named_values_to_struct.go
index 3482c22..9786120 100644
--- a/struct_utils/named_values_to_struct.go
+++ b/struct_utils/named_values_to_struct.go
@@ -15,12 +15,15 @@ import (
 )
 
 // Purpose:
+//
 //	Make a list of named values from the env for parsing into a struct
 //
 // Parameters:
+//
 //	prefix should be uppercase (by convention) env prefix like "MY_LIB_CONFIG", without trailing "_"
 //
 // Result:
+//
 //	named values that can be passed into UnmarshalNamedValues()
 //
 // All env starting with "<prefix>_" will be copied without "<prefix>_"
@@ -110,7 +113,8 @@ type nrWithValues struct {
 
 // converts query string params to named values that can be parsed into a struct
 // it support both single/multi-value params, depending how you get them from your HTTP library
-//    (e.g. AWS API Gateway Context returns both but default golang net/http returns only params)
+//
+//	(e.g. AWS API Gateway Context returns both but default golang net/http returns only params)
 func NamedValuesFromURL(params map[string]string, multiValueParams map[string][]string) map[string][]string {
 	result := map[string][]string{}
 	for n, v := range params {
@@ -161,34 +165,36 @@ func NamedValuesFromURL(params map[string]string, multiValueParams map[string][]
 }
 
 // Purpose:
-// 	UnmarshalNamedValues() parses a set of named values into a struct using json tag matching
-//  Unlike json.Unmarshal(), it takes care of converting quoted "true" -> true, "1" -> int(1) etc...
 //
-//	Typically used to parse environment or URL params into a struct
-//	because normal json.Unmarshal() will fail to parse "2" into an integer etc
+//		UnmarshalNamedValues() parses a set of named values into a struct using json tag matching
+//	 Unlike json.Unmarshal(), it takes care of converting quoted "true" -> true, "1" -> int(1) etc...
+//
+//		Typically used to parse environment or URL params into a struct
+//		because normal json.Unmarshal() will fail to parse "2" into an integer etc
 //
-//	By convention, the names should be lowercase to match json tag with "_" delimeters
-//	And also use "_" for nested sub-struct names
-//	  named value "a_b_c_d":5 would be stored in
-//    field with json tag "a_b_c_d" or
-//	  field with json tag "a_b"        which is a struct with a json tagged field "c_d" etc...
+//		By convention, the names should be lowercase to match json tag with "_" delimeters
+//		And also use "_" for nested sub-struct names
+//		  named value "a_b_c_d":5 would be stored in
+//	   field with json tag "a_b_c_d" or
+//		  field with json tag "a_b"        which is a struct with a json tagged field "c_d" etc...
 //
 // Parameters:
-// 	namedValues is name-value pairs, typical from URL params or OS environment
+//
+//	namedValues is name-value pairs, typical from URL params or OS environment
 //		see construction functions for this:
 //			NamedValuesFromEnv()
 //			NamedValuesFromURL()
 //
-// 	structPtr must be ptr to a struct variable
+//	structPtr must be ptr to a struct variable
 //		undefined values will not be changed, so you can call this multiple times on the
 //		same struct to amend a few values, leaving the rest and default values unchanged
 //
 // Return:
+//
 //	unused values
-// 	nil or error if some values could not be used
+//	nil or error if some values could not be used
 //
 //	If all values must be used, check len(unusedValues) when err==nil
-//
 func UnmarshalNamedValues(namedValues map[string][]string, structPtr interface{}) (unusedValues map[string][]string, err error) {
 	if structPtr == nil {
 		return nil, errors.Errorf("cannot unmarshal into nil")
@@ -263,8 +269,6 @@ func unmarshalNamedValuesIntoStructPtr(prefix string, namedValues map[string][]s
 					return
 				}
 				structPtrFieldValue.Set(reflect.Append(structPtrFieldValue, parsedValue))
-
-				// todo: sorting of list using value names as applicable
 			}
 		} else {
 			// field is not a slice, expecting only a single value
-- 
GitLab