Files
mysubaru/utils.go
Alex Savin aec4b8435b
All checks were successful
Golan Testing / testing (1.24.x, ubuntu-latest) (push) Successful in 25s
Enhance MySubaru API integration with improved error handling and new utility functions
- Refactor Response struct's parse method to return detailed error messages based on API error codes.
- Introduce UnixTime type for handling Unix timestamps in JSON marshaling and unmarshaling.
- Add email masking utility function to obfuscate email addresses for privacy.
- Implement containsValueInStruct function to check for substring presence in struct fields.
- Create comprehensive unit tests for UnixTime, email masking, and struct value checking.
- Update vehicle service request method documentation for clarity.
2025-07-08 11:26:45 -04:00

184 lines
4.3 KiB
Go

package mysubaru
import (
"fmt"
"math"
"net/mail"
"reflect"
"regexp"
"strconv"
"strings"
"time"
)
// timestamp is a function
func timestamp() string {
return strconv.FormatInt(time.Now().UnixNano()/1000000, 10)
}
// urlToGen .
func urlToGen(url string, gen string) string {
var re = regexp.MustCompile(`api_gen`)
// dirty trick for current G3
if gen == "g3" {
gen = "g2"
}
url = re.ReplaceAllString(url, gen)
return url
}
// VinCheck - Vehicle Identification Number check digit validation
// Parameter: string - 17 digit VIN
// Return:
//
// 1- boolean - Validity flag. Set to true if VIN check digit is correct, false otherwise.
// 2- string - Valid VIN. Same VIN passed as parameter but with the correct check digit on it.
func vinCheck(vin string) (bool, string) {
var valid = false
vin = strings.ToUpper(vin)
var retVin = vin
if len(vin) == 17 {
traSum := transcodeDigits(vin)
checkNum := math.Mod(float64(traSum), 11)
var checkDigit byte
if checkNum == 10 {
checkDigit = byte('X')
} else {
checkDigitTemp := strconv.Itoa(int(checkNum))
checkDigit = checkDigitTemp[len(checkDigitTemp)-1]
}
if retVin[8] == checkDigit {
valid = true
}
retVin = retVin[:8] + string(checkDigit) + retVin[9:]
} else {
valid = false
retVin = ""
}
return valid, retVin
}
// transcodeDigits transcodes VIN digits to a numeric value
func transcodeDigits(vin string) int {
var digitSum = 0
var code int
for i, chr := range vin {
code = 0
switch chr {
case 'A', 'J', '1':
code = 1
case 'B', 'K', 'S', '2':
code = 2
case 'C', 'L', 'T', '3':
code = 3
case 'D', 'M', 'U', '4':
code = 4
case 'E', 'N', 'V', '5':
code = 5
case 'F', 'W', '6':
code = 6
case 'G', 'P', 'X', '7':
code = 7
case 'H', 'Y', '8':
code = 8
case 'R', 'Z', '9':
code = 9
case 'I', 'O', 'Q':
code = 0
}
switch i + 1 {
case 1, 11:
digitSum += code * 8
case 2, 12:
digitSum += code * 7
case 3, 13:
digitSum += code * 6
case 4, 14:
digitSum += code * 5
case 5, 15:
digitSum += code * 4
case 6, 16:
digitSum += code * 3
case 7, 17:
digitSum += code * 2
case 8:
digitSum += code * 10
case 9:
digitSum += code * 0
case 10:
digitSum += code * 9
}
}
return digitSum
}
// emailMasking takes an email address as input and returns a version of the email
// with the username part partially hidden for privacy. Only the first and last
// characters of the username are visible, with the middle characters replaced by asterisks.
// The function validates the email format before processing.
// Returns the obfuscated email or an error if the input is not a valid email address.
func emailMasking(email string) (string, error) {
_, err := mail.ParseAddress(email)
if err != nil {
return "", fmt.Errorf("invalid email address: %s", email)
}
re1 := regexp.MustCompile(`^(.*?)@(.*)$`)
matches := re1.FindStringSubmatch(email)
var username, domain string
if len(matches) == 3 { // Expecting the full match, username, and domain
username = matches[1]
domain = matches[2]
} else {
return "", fmt.Errorf("invalid email format: %s", email)
}
re2 := regexp.MustCompile(`(.)(.*)(.)`)
replacedString := re2.ReplaceAllStringFunc(username, func(s string) string {
firstChar := string(s[0])
lastChar := string(s[len(s)-1])
middleCharsCount := len(s) - 2
if middleCharsCount < 0 { // Should not happen with the length check above, but for robustness
return s
}
return firstChar + strings.Repeat("*", middleCharsCount) + lastChar
})
return replacedString + "@" + domain, nil
}
// containsValueInStruct checks if any string field in the given struct 's' contains the specified 'search' substring (case-insensitive).
// It returns true if at least one string field contains the substring, and false otherwise.
// If 's' is not a struct, it returns false.
func containsValueInStruct(s any, search string) bool {
val := reflect.ValueOf(s)
if val.Kind() != reflect.Struct {
return false // Not a struct
}
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
if field.Kind() == reflect.String {
if strings.Contains(strings.ToLower(field.String()), strings.ToLower(search)) {
return true
}
}
}
return false
}
// timeTrack .
// func timeTrack(name string) {
// start := time.Now()
// fmt.Printf("%s took %v\n", name, time.Since(start))
// }