diff --git a/cognito/cognito.go b/cognito/cognito.go index d12159192396585c0899b99663b5b8de1b75a574..ac79da6f825d272f7e2756c675a1ede4e2eeafae 100644 --- a/cognito/cognito.go +++ b/cognito/cognito.go @@ -107,8 +107,8 @@ func confirmForgotPassword(appClientID string, username string, password string, func confirmPasswordReset(appClientID string, username string, password string, initiateAuthOutput *cognitoidentityprovider.InitiateAuthOutput) (*cognitoidentityprovider.RespondToAuthChallengeOutput, error) { // Respond to the Auth challenge to change the user's password authChallengeParameters := map[string]*string{ - "USERNAME": utils.PointerValue(username), - "NEW_PASSWORD": utils.PointerValue(password), + "USERNAME": utils.ValueToPointer(username), + "NEW_PASSWORD": utils.ValueToPointer(password), } respondToAuthChallengeInput := cognitoidentityprovider.RespondToAuthChallengeInput{ ChallengeName: initiateAuthOutput.ChallengeName, @@ -126,11 +126,11 @@ func confirmPasswordReset(appClientID string, username string, password string, func ConfirmPasswordReset(appClientID string, username string, password string, confirmationCode string) (interface{}, error) { // Initiate an auth for the user to see if a password reset or authParameters := map[string]*string{ - "USERNAME": utils.PointerValue(username), - "PASSWORD": utils.PointerValue(confirmationCode), + "USERNAME": utils.ValueToPointer(username), + "PASSWORD": utils.ValueToPointer(confirmationCode), } initiateAuthInput := cognitoidentityprovider.InitiateAuthInput{ - AuthFlow: utils.PointerValue(cognitoidentityprovider.ExplicitAuthFlowsTypeUserPasswordAuth), + AuthFlow: utils.ValueToPointer(cognitoidentityprovider.ExplicitAuthFlowsTypeUserPasswordAuth), AuthParameters: authParameters, ClientId: &appClientID, } @@ -142,7 +142,7 @@ func ConfirmPasswordReset(appClientID string, username string, password string, } return nil, err } - if utils.Unwrap(res.ChallengeName) == cognitoidentityprovider.ChallengeNameTypeNewPasswordRequired { + if utils.PointerToValue(res.ChallengeName) == cognitoidentityprovider.ChallengeNameTypeNewPasswordRequired { return confirmPasswordReset(appClientID, username, password, res) } diff --git a/map_utils/map_utils.go b/map_utils/map_utils.go new file mode 100644 index 0000000000000000000000000000000000000000..5a41270d8bb5059d5572f5de954ad11190e28eef --- /dev/null +++ b/map_utils/map_utils.go @@ -0,0 +1,53 @@ +package map_utils + +import ( + "fmt" + "strconv" + "strings" +) + +// MapStringInterfaceToMapStringString converts a generic value typed map to a map with string values +func MapStringInterfaceToMapStringString(inputMap map[string]interface{}) map[string]string { + query := make(map[string]string) + for mapKey, mapVal := range inputMap { + // Check if mapVal is a slice or a single value + switch mapValTyped := mapVal.(type) { + case []interface{}: + // Slice - convert each element individually + var mapValString []string + + // Loop through each element in the slice and check the type + for _, sliceElem := range mapValTyped { + switch sliceElemTyped := sliceElem.(type) { + case string: + // Enclose strings in escaped quotations + mapValString = append(mapValString, fmt.Sprintf("\"%v\"", sliceElemTyped)) + case float64: + // Use FormatFloat for least amount of precision. + mapValString = append(mapValString, strconv.FormatFloat(sliceElemTyped, 'f', -1, 64)) + default: + // Convert to string + mapValString = append(mapValString, fmt.Sprintf("%v", sliceElemTyped)) + } + } + // Join as a comma seperated array + query[mapKey] = "[" + strings.Join(mapValString, ",") + "]" + default: + // Single value - convert to string + query[mapKey] = fmt.Sprintf("%v", mapVal) + } + } + + return query +} + +// MergeMaps If there are similar properties in the maps, the last one will be used as the value +func MergeMaps(maps ...map[string]string) map[string]string { + ret := map[string]string{} + for _, mapV := range maps { + for k, v := range mapV { + ret[k] = v + } + } + return ret +} diff --git a/number_utils/number_utils.go b/number_utils/number_utils.go index d183fe9fbcaef6b730943dcd276cf82039a486b2..9c4221b6bcec705627f3f27889a4f50b6c5ad29f 100644 --- a/number_utils/number_utils.go +++ b/number_utils/number_utils.go @@ -1,6 +1,9 @@ package number_utils -import "math" +import ( + "math" + "strconv" +) func RoundFloat(value float64) float64 { return math.Round(value*100) / 100 // 2 decimal places @@ -11,16 +14,19 @@ func RoundFloatTo(value float64, to int) float64 { return math.Round(value*toValue) / toValue } -func UnwrapFloat64(f *float64) float64 { - if f == nil { - return 0.0 - } - return *f +func StringToInt(stringValue string) (int, error) { + return strconv.Atoi(stringValue) +} + +func StringToInt64(stringValue string) (int64, error) { + number, err := strconv.ParseInt(stringValue, 10, 64) + return number, err } -func UnwrapInt64(i *int64) int64 { - if i == nil { - return 0 +func StringToFloat64(stringValue string) (float64, error) { + number, err := strconv.ParseFloat(stringValue, 64) + if err != nil { + return 0, err } - return *i + return number, nil } diff --git a/slice_utils/slice_utils.go b/slice_utils/slice_utils.go index 0c400cb7863bf42ead48dfee4ee6b57464347de5..f203d0dc8d0dd698f63a5e53a815a990fb005aff 100644 --- a/slice_utils/slice_utils.go +++ b/slice_utils/slice_utils.go @@ -53,3 +53,14 @@ func FilterNonEmptyString(arr []string) []string { }).([]string) return nonEmptyStrings } + +func ArraySlice(s []any, offset, length int) []any { + if offset > len(s) { + return s + } + end := offset + length + if end < len(s) { + return s[offset:end] + } + return s[offset:] +} diff --git a/string_utils/string_utils.go b/string_utils/string_utils.go index 9b4cafe14ca1760701000f80aa1af10f5eaf6e8a..995e94a309eabbf18f1bee3cd9462242d930a2c4 100644 --- a/string_utils/string_utils.go +++ b/string_utils/string_utils.go @@ -4,9 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors" - "net/mail" - "net/url" + "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/number_utils" "regexp" "strconv" "strings" @@ -68,37 +66,6 @@ func IsNumericString(s string) bool { return err == nil } -// StandardisePhoneNumber standardises phone numbers with +27 instead of 0 prefix -func StandardisePhoneNumber(number string) string { - number = strings.TrimSpace(number) - - if number == "" { - return number - } - - // is the first rune/char of the string a 0 - if []rune(number)[0] == []rune("0")[0] { - // Add south african country code (hardcoded for now) - number = "+27" + number[1:] - } - return number -} - -func FormatPhoneNumber(phoneNumber string) string { - if len(phoneNumber) > 7 { - return phoneNumber - } - - // Format as 076 453 2188 - phoneNumber = insertInto(phoneNumber, 3, " ") - phoneNumber = insertInto(phoneNumber, 7, " ") - return phoneNumber -} - -func insertInto(s string, index int, characters string) string { - return s[:index] + characters + s[index:] -} - func IsAlphaNumeric(str string) bool { regex := regexp.MustCompile("^[a-zA-Z0-9]*$") return regex.MatchString(str) @@ -109,22 +76,10 @@ func IsAlphaNumericOrDash(str string) bool { return regex.MatchString(str) } -func IsValidUsername(str string) bool { - regex := regexp.MustCompile("^[a-zA-Z0-9-.@+_]*$") - return regex.MatchString(str) -} - func Equal(a string, b string) bool { return strings.TrimSpace(strings.ToLower(a)) == strings.TrimSpace(strings.ToLower(b)) } -func UnwrapString(s *string) string { - if s == nil { - return "" - } - return *s -} - // TrimP trims specified strings, replacing empty string with nil func TrimP(sp *string) *string { if sp == nil { @@ -180,9 +135,6 @@ func Int64ToString(number int64) string { func IntToString(number int) string { return strconv.Itoa(number) } -func StringToInt(stringValue string) (int, error) { - return strconv.Atoi(stringValue) -} func Int64SliceToString(numbers []int64) string { numString := fmt.Sprint(numbers) @@ -198,19 +150,6 @@ func Int64SliceToStringSlice(numbers []int64) []string { return numStringSlice } -func StringToInt64(stringValue string) (int64, error) { - number, err := strconv.ParseInt(stringValue, 10, 64) - return number, err -} - -func StringToFloat64(stringValue string) (float64, error) { - number, err := strconv.ParseFloat(stringValue, 64) - if err != nil { - return 0, err - } - return number, nil -} - func Float64ToString(number float64, precision int) string { return strconv.FormatFloat(number, 'f', precision, 64) } @@ -220,18 +159,10 @@ func Float64ToStringWithPrec(number float64, prec int) string { } func ValidateStringAsInt64(stringValue string) error { - _, err := StringToInt64(stringValue) + _, err := number_utils.StringToInt64(stringValue) return err } -func PtoString(stringPointer *string) string { - if stringPointer == nil { - return "" - } - - return *stringPointer -} - func IsEmpty(sp *string) bool { if sp == nil { return true @@ -269,35 +200,6 @@ func SentenceCase(str string) string { return "" } -// RemoveUrlScheme Removes http:// or https:// from a URL -func RemoveUrlScheme(str string) string { - newStr := strings.Replace(str, "http://", "", 1) - newStr = strings.Replace(str, "https://", "", 1) - return newStr -} - -// EscapeOpenSearchSearchString See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#_reserved_characters -func EscapeOpenSearchSearchString(str string) string { - searchString := str - - // Reserved characters - // NOTE: first char must be "\" to prevent replacing it again after replacing other chars with "\\" - reservedCharacters := []string{"\\", "+", "-", "=", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~", "*", "?", ":", "/"} - - // Remove "<" and ">" - strings.ReplaceAll(searchString, "<", "") - strings.ReplaceAll(searchString, ">", "") - - // Escape the reserved characters with double backslashes ("\\") - for _, char := range reservedCharacters { - if strings.Contains(searchString, char) { - re := regexp.MustCompile(char) - searchString = re.ReplaceAllString(searchString, "\\"+char) - } - } - return searchString -} - // 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 { @@ -307,12 +209,6 @@ func SplitString(str string, sep []rune) []string { return splitStrings } -// IsUrlStrict Returns whether a URL is valid in a strict way (Must have scheme and host) -func IsUrlStrict(str string) bool { - u, err := url.Parse(str) - return err == nil && u.Scheme != "" && u.Host != "" -} - func LimitStringToMaxLength(str string, maxLen int) string { if len(str) > maxLen { str = str[0:maxLen] @@ -320,33 +216,6 @@ func LimitStringToMaxLength(str string, maxLen int) string { return str } -// StripQueryString - Strips the query parameters from a URL -func StripQueryString(inputUrl string) (string, error) { - u, err := url.Parse(inputUrl) - if err != nil { - return inputUrl, err - } - u.RawQuery = "" - return u.String(), nil -} - -func ValidateEmailAddress(email string) (string, error) { - if email == "" { - return "", errors.Error("email address is empty") - } - - cleanEmail := strings.ToLower(strings.TrimSpace(email)) - cleanEmail = RemoveAllWhiteSpaces(cleanEmail) - - // We validate it but still return it since in some cases we don't want to break everything if the email is bad - _, err := mail.ParseAddress(cleanEmail) - if err != nil { - return cleanEmail, errors.Wrap(err, "could not parse email address") - } - - return cleanEmail, nil -} - func PascalCaseToSentence(pascal string) string { var parts []string start := 0 diff --git a/utils/utils.go b/utils/utils.go index 165a1dc575c6810c954bd04de3ccbd159bb9c0bc..b537f8163ddf17b48b95a2cd6565e0aed13c210e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -3,10 +3,13 @@ package utils import ( "archive/zip" "bytes" - "fmt" + "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors" + "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils" "io/ioutil" + "net/mail" + "net/url" "os" - "strconv" + "regexp" "strings" "time" @@ -101,67 +104,112 @@ func DeepCopy(fromValue interface{}) (toValue interface{}) { return deepcopy.Copy(fromValue) } -func UnwrapBool(b *bool) bool { - if b == nil { - return false - } - return *b -} - -// MapStringInterfaceToMapStringString converts a generic value typed map to a map with string values -func MapStringInterfaceToMapStringString(inputMap map[string]interface{}) map[string]string { - query := make(map[string]string) - for mapKey, mapVal := range inputMap { - // Check if mapVal is a slice or a single value - switch mapValTyped := mapVal.(type) { - case []interface{}: - // Slice - convert each element individually - var mapValString []string - - // Loop through each element in the slice and check the type - for _, sliceElem := range mapValTyped { - switch sliceElemTyped := sliceElem.(type) { - case string: - // Enclose strings in escaped quotations - mapValString = append(mapValString, fmt.Sprintf("\"%v\"", sliceElemTyped)) - case float64: - // Use FormatFloat for least amount of precision. - mapValString = append(mapValString, strconv.FormatFloat(sliceElemTyped, 'f', -1, 64)) - default: - // Convert to string - mapValString = append(mapValString, fmt.Sprintf("%v", sliceElemTyped)) - } - } - // Join as a comma seperated array - query[mapKey] = "[" + strings.Join(mapValString, ",") + "]" - default: - // Single value - convert to string - query[mapKey] = fmt.Sprintf("%v", mapVal) - } +func ValueToPointer[V any](value V) *V { + return &value +} + +func PointerToValue[V any](value *V) V { + if value != nil { + return *value } - return query + return *new(V) // zero value of V } -// MergeMaps If there are similar properties in the maps, the last one will be used as the value -func MergeMaps(maps ...map[string]string) map[string]string { - ret := map[string]string{} - for _, mapV := range maps { - for k, v := range mapV { - ret[k] = v - } +func ValidateEmailAddress(email string) (string, error) { + if email == "" { + return "", errors.Error("email address is empty") + } + + cleanEmail := strings.ToLower(strings.TrimSpace(email)) + cleanEmail = string_utils.RemoveAllWhiteSpaces(cleanEmail) + + // We validate it but still return it since in some cases we don't want to break everything if the email is bad + _, err := mail.ParseAddress(cleanEmail) + if err != nil { + return cleanEmail, errors.Wrap(err, "could not parse email address") } - return ret + + return cleanEmail, nil } -func PointerValue[V any](value V) *V { - return &value +// IsUrlStrict Returns whether a URL is valid in a strict way (Must have scheme and host) +func IsUrlStrict(str string) bool { + u, err := url.Parse(str) + return err == nil && u.Scheme != "" && u.Host != "" } -func Unwrap[V any](value *V) V { - if value != nil { - return *value +func IsValidUsername(str string) bool { + regex := regexp.MustCompile("^[a-zA-Z0-9-.@+_]*$") + return regex.MatchString(str) +} + +// StandardisePhoneNumber standardises phone numbers with +27 instead of 0 prefix +func StandardisePhoneNumber(number string) string { + number = strings.TrimSpace(number) + + if number == "" { + return number } - return *new(V) // zero value of V + // is the first rune/char of the string a 0 + if []rune(number)[0] == []rune("0")[0] { + // Add south african country code (hardcoded for now) + number = "+27" + number[1:] + } + return number +} + +func FormatPhoneNumber(phoneNumber string) string { + if len(phoneNumber) > 7 { + return phoneNumber + } + + // Format as 076 453 2188 + phoneNumber = insertInto(phoneNumber, 3, " ") + phoneNumber = insertInto(phoneNumber, 7, " ") + return phoneNumber +} + +func insertInto(s string, index int, characters string) string { + return s[:index] + characters + s[index:] +} + +// RemoveUrlScheme Removes http:// or https:// from a URL +func RemoveUrlScheme(str string) string { + newStr := strings.Replace(str, "http://", "", 1) + newStr = strings.Replace(str, "https://", "", 1) + return newStr +} + +// StripQueryString - Strips the query parameters from a URL +func StripQueryString(inputUrl string) (string, error) { + u, err := url.Parse(inputUrl) + if err != nil { + return inputUrl, err + } + u.RawQuery = "" + return u.String(), nil +} + +// EscapeOpenSearchSearchString See https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#_reserved_characters +func EscapeOpenSearchSearchString(str string) string { + searchString := str + + // Reserved characters + // NOTE: first char must be "\" to prevent replacing it again after replacing other chars with "\\" + reservedCharacters := []string{"\\", "+", "-", "=", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~", "*", "?", ":", "/"} + + // Remove "<" and ">" + strings.ReplaceAll(searchString, "<", "") + strings.ReplaceAll(searchString, ">", "") + + // Escape the reserved characters with double backslashes ("\\") + for _, char := range reservedCharacters { + if strings.Contains(searchString, char) { + re := regexp.MustCompile(char) + searchString = re.ReplaceAllString(searchString, "\\"+char) + } + } + return searchString }