Skip to content
Snippets Groups Projects
Commit 3c118811 authored by Jano Hendriks's avatar Jano Hendriks
Browse files

Add struct validation functions

parent a457f019
No related branches found
No related tags found
No related merge requests found
......@@ -14,6 +14,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/batch v1.36.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.28.6
github.com/aws/aws-sdk-go-v2/service/ses v1.23.1
github.com/aws/aws-sdk-go-v2/service/sqs v1.31.4
github.com/aws/aws-secretsmanager-caching-go v1.1.0
github.com/aws/smithy-go v1.20.2
......@@ -30,6 +31,7 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/opensearch-project/opensearch-go/v2 v2.2.0
github.com/pkg/errors v0.9.1
github.com/prozz/aws-embedded-metrics-golang v1.2.0
github.com/r3labs/diff/v2 v2.14.2
github.com/samber/lo v1.38.1
github.com/sirupsen/logrus v1.8.1
......@@ -38,6 +40,7 @@ require (
golang.ngrok.com/ngrok v1.4.1
golang.org/x/crypto v0.22.0
golang.org/x/text v0.14.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
)
require (
......@@ -52,7 +55,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ses v1.23.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
......@@ -73,7 +75,6 @@ require (
github.com/mattn/go-runewidth v0.0.10 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pborman/uuid v1.2.1 // indirect
github.com/prozz/aws-embedded-metrics-golang v1.2.0 // indirect
github.com/rivo/uniseg v0.1.0 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
......@@ -92,6 +93,5 @@ require (
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
mellium.im/sasl v0.2.1 // indirect
)
......@@ -12,8 +12,6 @@ github.com/aws/aws-sdk-go v1.19.23/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi
github.com/aws/aws-sdk-go v1.44.180 h1:VLZuAHI9fa/3WME5JjpVjcPCNfpGHVMiHx8sLHWhMgI=
github.com/aws/aws-sdk-go v1.44.180/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.27.1 h1:xypCL2owhog46iFxBKKpBcw+bPTX/RJzwNj8uSilENw=
github.com/aws/aws-sdk-go-v2 v1.27.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2 v1.30.0 h1:6qAwtzlfcTtcL8NHtbDQAqgM5s6NDipQTkPxyH/6kAA=
github.com/aws/aws-sdk-go-v2 v1.30.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
......@@ -28,13 +26,9 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvU
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.8 h1:RnLB7p6aaFMRfyQkD6ckxR7myCC9SABIqSz4czYUUbU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.8/go.mod h1:XH7dQJd+56wEbP1I4e4Duo+QhSMxNArE8VP7NuUOTeM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12 h1:SJ04WXGTwnHlWIODtC5kJzKbeuHt+OUNOgKg7nfnUGw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.12/go.mod h1:FkpvXhA92gb3GE9LD6Og0pHHycTxW7xGpnEh5E7Opwo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.8 h1:jzApk2f58L9yW9q1GEab3BMMFWUkkiZhyrRUtbwUbKU=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.8/go.mod h1:WqO+FftfO3tGePUtQxPXM6iODVfqMwsVMgTbG/ZXIdQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12 h1:hb5KgeYfObi5MHkSSZMEudnIvX30iB+E21evI4r6BnQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.12/go.mod h1:CroKe/eWJdyfy9Vx4rljP5wTUjNJfb+fPz1uMYUhEGM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c=
......@@ -171,6 +165,7 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kinbiko/jsonassert v1.0.1 h1:8gdLmUaPWuxk2TzQSofKRqatFH6zwTF6AsUH4bugJYY=
github.com/kinbiko/jsonassert v1.0.1/go.mod h1:QRwBwiAsrcJpjw+L+Q4WS8psLxuUY+HylVZS/4j74TM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
......
......@@ -10,13 +10,12 @@ import (
"time"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/struct_utils"
)
func TestEnv(t *testing.T) {
logs.SetGlobalFormat(logs.NewConsole())
logs.SetGlobalLevel(logs.LevelDebug)
//logs.SetGlobalFormat(logs.NewConsole())
//logs.SetGlobalLevel(logs.LevelDebug)
// booleans
os.Setenv("TEST_VALUE_ENABLE_CACHE", "true")
os.Setenv("TEST_VALUE_DISABLE_LOG", "true")
......@@ -43,8 +42,8 @@ func TestEnv(t *testing.T) {
}
func TestURL1(t *testing.T) {
logs.SetGlobalFormat(logs.NewConsole())
logs.SetGlobalLevel(logs.LevelDebug)
//logs.SetGlobalFormat(logs.NewConsole())
//logs.SetGlobalLevel(logs.LevelDebug)
queryParams := map[string]string{
"enable_cache": "true",
......@@ -63,8 +62,8 @@ func TestURL1(t *testing.T) {
}
func TestURL2(t *testing.T) {
logs.SetGlobalFormat(logs.NewConsole())
logs.SetGlobalLevel(logs.LevelDebug)
//logs.SetGlobalFormat(logs.NewConsole())
//logs.SetGlobalLevel(logs.LevelDebug)
queryParams := map[string]string{
"disable_log": "true",
......
This diff is collapsed.
package struct_utils
import "strings"
import (
"github.com/samber/lo"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"reflect"
"strings"
)
// KeyValuePair defines a key/value pair derived from form data
type KeyValuePair struct {
......@@ -46,3 +51,179 @@ func GetValue(key string, kv []KeyValuePair) string {
return ""
}
// ValidateRequiredFields checks the required tag on struct fields and returns an error if any are set to zero.
func ValidateRequiredFields(object any, parentFieldKeys ...string) error {
objectValue := reflect.ValueOf(object)
if objectValue.Kind() == reflect.Interface {
objectValue = objectValue.Elem()
}
if objectValue.Kind() == reflect.Ptr {
objectValue = objectValue.Elem()
}
if objectValue.Kind() != reflect.Struct {
if objectValue.IsZero() {
errorKey := strings.Join(parentFieldKeys, ".")
return errors.Error(errorKey + " is required")
}
return nil
}
for i := 0; i < objectValue.NumField(); i++ {
field := objectValue.Field(i)
fieldType := objectValue.Type().Field(i)
requiredTag := fieldType.Tag.Get("required")
requiredOptions := strings.Split(requiredTag, ",")
required := lo.Contains(requiredOptions, "true")
notNil := lo.Contains(requiredOptions, "not-nil")
deep := lo.Contains(requiredOptions, "deep")
thisFieldKey := getRequiredKey(fieldType)
err := validateFieldByKind(field, required, notNil, deep, thisFieldKey, parentFieldKeys...)
if err != nil {
return err
}
}
return nil
}
func validateFieldByKind(field reflect.Value, required bool, notNil bool, deep bool, thisFieldKey string, parentFieldKeys ...string) error {
switch field.Kind() {
case reflect.Struct:
if required &&
field.IsZero() {
return errors.Error(getErrorKey(thisFieldKey, parentFieldKeys) + " is required")
}
if deep {
parentFieldKeys = append(parentFieldKeys, thisFieldKey)
err := ValidateRequiredFields(field.Interface(), parentFieldKeys...)
if err != nil {
return err
}
}
case reflect.Slice:
if (required || notNil) &&
field.IsZero() {
return errors.Error(getErrorKey(thisFieldKey, parentFieldKeys) + " is required")
}
if required &&
field.Len() == 0 {
return errors.Error(getErrorKey(thisFieldKey, parentFieldKeys) + " may not be empty")
}
if deep {
for j := 0; j < field.Len(); j++ {
sliceField := field.Index(j)
if sliceField.Kind() == reflect.Ptr {
sliceField = sliceField.Elem()
}
if sliceField.Kind() == reflect.Struct {
err := ValidateRequiredFields(sliceField.Interface(), append(parentFieldKeys, thisFieldKey)...)
if err != nil {
return err
}
} else {
err := validateFieldByKind(sliceField, true, notNil, deep, thisFieldKey, parentFieldKeys...)
if err != nil {
return err
}
}
}
}
case reflect.Map:
if (required || notNil) &&
field.IsZero() {
return errors.Error(getErrorKey(thisFieldKey, parentFieldKeys) + " is required")
}
if required &&
field.Len() == 0 {
return errors.Error(getErrorKey(thisFieldKey, parentFieldKeys) + " may not be empty")
}
if deep {
for _, key := range field.MapKeys() {
mapField := field.MapIndex(key)
if mapField.Kind() == reflect.Ptr {
mapField = mapField.Elem()
}
if mapField.Kind() == reflect.Struct {
err := ValidateRequiredFields(mapField.Interface(), append(parentFieldKeys, thisFieldKey)...)
if err != nil {
return err
}
} else {
err := validateFieldByKind(mapField, required, notNil, deep, thisFieldKey, parentFieldKeys...)
if err != nil {
return err
}
}
}
}
case reflect.Ptr:
if (required || notNil) &&
field.IsZero() {
return errors.Error(getErrorKey(thisFieldKey, parentFieldKeys) + " is required")
}
if field.Elem().Kind() == reflect.Struct {
if required &&
field.Elem().IsZero() {
return errors.Error(getErrorKey(thisFieldKey, parentFieldKeys) + " is required")
}
if deep {
parentFieldKeys = append(parentFieldKeys, thisFieldKey)
err := ValidateRequiredFields(field.Interface(), parentFieldKeys...)
if err != nil {
return err
}
}
} else if required {
return validateFieldByKind(field.Elem(), required, notNil, deep, thisFieldKey, parentFieldKeys...)
}
default:
if required &&
field.IsZero() {
return errors.Error(getErrorKey(thisFieldKey, parentFieldKeys) + " is required")
}
}
return nil
}
func getRequiredKey(fieldType reflect.StructField) string {
if fieldType.Anonymous {
return ""
}
jsonTag := fieldType.Tag.Get("json")
if jsonTag != "" && jsonTag != "-" {
commaIdx := strings.Index(jsonTag, ",")
if commaIdx > 0 {
// Tag is in the format "key,omitempty"
return jsonTag[:commaIdx]
} else {
return jsonTag
}
}
return fieldType.Name
}
func getErrorKey(thisKey string, parentKeys []string) string {
parentKeys = append(parentKeys, thisKey)
parentKeys = lo.Filter(parentKeys, func(key string, _ int) bool {
return key != ""
})
return strings.Join(parentKeys, ".")
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment