diff --git a/LICENSE b/LICENSE index 54548ad294ca856618a0ed3509c084a6d05545e7..550a15e24ee76bbb129c6f82bae67b3f1f1050b0 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 768113b595f16cc1844461546d297473f3c42e93..bb2810a05f33f3a09002c6116f5bf5e4d456d3a9 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 580513581cf6a07d8e1e2f08a9fdcff7fa8e6294..319083bc474ce03368a657dbab906a3ea7c8666d 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 1f2400bd3d0327c2b86bf1325225c4ad6dd249f9..7b22037b03fd0ae5855bcd81d70a7f0d15b128a9 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 ac79da6f825d272f7e2756c675a1ede4e2eeafae..3df6c2d9fcd36d2537329d3106a860bb45c70b78 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 8d6c99a8ca49ee71f31c72e9f8c6e384dca39a17..bca0d801887ba00d6be2afa369fd4af6a097769b 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 bd6c2afebeb32fa6e72e7c8ef74685757ca87d92..d8245cc13167008099b56368b518e0261f225b5e 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 56a9af09ca3eb7b6abc1ea666c5297a0bf9a8dc0..be466ce58f90bd2ac1836da72764bd80cc548f86 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 f1b4c59aeaa9b30030770817e48c4a6d30c22bc1..42fa46c64c14bd464e7a637e1267a12bbea923ee 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 c64b584b8223759cc537b8772310730bccf161e7..0000000000000000000000000000000000000000 --- 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 995e94a309eabbf18f1bee3cd9462242d930a2c4..9a36862709b8a8d78a3b09e20c5ea78c5443a68d 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 3c52e65fd4ae909cc7a7dda123d7d8d3c4adbca6..04da56723ed34dc3cc054a82b69d8ba4a8fb9fab 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 3482c22c3b460a587e00c4adc39274031c108ea6..97861207876e14ebce9cace94dadc1d10576825a 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