diff --git a/go.mod b/go.mod index 97abf8a7e8ecfa30df469cb43f47287bf46b8f5b..b8461cd55d2c97ac440dcd625e4b58039f80d775 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 82ebf050f317ffa1e5efe28a40a210022df883b7..cb29536ba98516e6d496bdc8d9ad14ac2fb890c0 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/struct_utils/named_values_to_struct_test.go b/struct_utils/named_values_to_struct_test.go index 653cb0256d48ceea4db3da4690e09e0e18e8b767..114c45fada028253bebba18d13ad560541a1e8ad 100644 --- a/struct_utils/named_values_to_struct_test.go +++ b/struct_utils/named_values_to_struct_test.go @@ -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", diff --git a/struct_utils/required_fields_test.go b/struct_utils/required_fields_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f8d54fcc41f54c0af6995a171de9d972ecae9a83 --- /dev/null +++ b/struct_utils/required_fields_test.go @@ -0,0 +1,653 @@ +package struct_utils + +import ( + "encoding/json" + "fmt" + "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors" + "reflect" + "testing" +) + +// Basic field types +type OptionalFieldStruct struct { + OptionalField string `json:"field"` +} + +type RequiredFieldStruct struct { + RequiredField string `json:"field" required:"true"` +} + +// Basic pointer field types +type OptionalPointerFieldStruct struct { + OptionalField *string `json:"pointer_field"` +} + +type RequiredPointerFieldStruct struct { + RequiredField *string `json:"pointer_field" required:"true"` +} + +type NotNilPointerFieldStruct struct { + RequiredField *string `json:"pointer_field" required:"not-nil"` +} + +// Basic slice field types +type OptionalSliceStruct struct { + OptionalSlice []string `json:"slice_field"` +} + +type OptionalDeepSliceStruct struct { + OptionalSlice []string `json:"slice_field" required:"deep"` +} + +type RequiredSliceStruct struct { + RequiredSlice []string `json:"slice_field" required:"true"` +} + +type RequiredDeepSliceStruct struct { + RequiredSlice []string `json:"slice_field" required:"true,deep"` +} + +type NotNilSliceStruct struct { + RequiredSlice []string `json:"slice_field" required:"not-nil"` +} + +type NotNilDeepSliceStruct struct { + RequiredSlice []string `json:"slice_field" required:"not-nil,deep"` +} + +// Custom slice type +type RequiredFieldSliceType []RequiredFieldStruct + +// Basic map field types +type OptionalMapStruct struct { + OptionalMap map[string]any `json:"map_field"` +} + +type RequiredMapStruct struct { + RequiredMap map[string]any `json:"map_field" required:"true"` +} + +type NotNilMapStruct struct { + RequiredMap map[string]any `json:"map_field" required:"not-nil"` +} + +// Structs with optional nested fields +type OptionalNestedOptionalFieldStruct struct { + OptionalNested OptionalFieldStruct `json:"optional_field_nested"` +} + +type OptionalNestedPointerOptionalFieldStruct struct { + OptionalNested *OptionalFieldStruct `json:"optional_field_nested"` +} + +type RequiredNestedOptionalFieldStruct struct { + RequiredNested OptionalFieldStruct `json:"optional_field_nested" required:"true"` +} + +type RequiredNestedPointerOptionalFieldStruct struct { + RequiredNested *OptionalFieldStruct `json:"optional_field_nested" required:"true"` +} + +type NotNilNestedPointerOptionalFieldStruct struct { + RequiredNested *OptionalFieldStruct `json:"optional_field_nested" required:"not-nil"` +} + +// Structs with required nested fields +type OptionalNestedRequiredFieldStruct struct { + OptionalNested RequiredFieldStruct `json:"required_field_nested"` +} + +type OptionalNestedPointerRequiredFieldStruct struct { + OptionalNested *RequiredFieldStruct `json:"required_field_nested"` +} + +type RequiredNestedRequiredFieldStruct struct { + RequiredNested RequiredFieldStruct `json:"required_field_nested" required:"true"` +} + +type RequiredNestedPointerRequiredFieldStruct struct { + RequiredNested *RequiredFieldStruct `json:"required_field_nested" required:"true"` +} + +type NotNilNestedPointerRequiredFieldStruct struct { + RequiredNested *RequiredFieldStruct `json:"required_field_nested" required:"not-nil"` +} + +// Structs with optional nested pointer fields +type OptionalNestedOptionalPointerFieldStruct struct { + OptionalNested OptionalPointerFieldStruct `json:"optional_field_nested"` +} + +type OptionalNestedPointerOptionalPointerFieldStruct struct { + OptionalNested *OptionalPointerFieldStruct `json:"optional_field_nested"` +} + +type RequiredNestedOptionalPointerFieldStruct struct { + RequiredNested OptionalPointerFieldStruct `json:"optional_field_nested" required:"true"` +} + +type RequiredNestedPointerOptionalPointerFieldStruct struct { + RequiredNested *OptionalPointerFieldStruct `json:"optional_field_nested" required:"true"` +} + +type NotNilNestedPointerOptionalPointerFieldStruct struct { + RequiredNested *OptionalPointerFieldStruct `json:"optional_field_nested" required:"not-nil"` +} + +// Structs with required nested pointer fields +type OptionalNestedRequiredPointerFieldStruct struct { + OptionalNested RequiredPointerFieldStruct `json:"required_field_nested"` +} + +type OptionalNestedPointerRequiredPointerFieldStruct struct { + OptionalNested *RequiredPointerFieldStruct `json:"required_field_nested"` +} + +type RequiredNestedRequiredPointerFieldStruct struct { + RequiredNested RequiredPointerFieldStruct `json:"required_field_nested" required:"true"` +} + +type RequiredNestedPointerRequiredPointerFieldStruct struct { + RequiredNested *RequiredPointerFieldStruct `json:"required_field_nested" required:"true"` +} + +type NotNilNestedPointerRequiredPointerFieldStruct struct { + RequiredNested *RequiredPointerFieldStruct `json:"required_field_nested" required:"not-nil"` +} + +// Deep test +type DeepFieldStruct struct { + OptionalField string `json:"optional_field"` + RequiredField string `json:"required_field" required:"true"` +} + +type OptionalNestedDeepRequiredDeepFieldStruct struct { + OptionalNested DeepFieldStruct `json:"deep_field_nested" required:"deep"` +} + +type OptionalNestedPointerDeepRequiredDeepFieldPointerStruct struct { + OptionalNested *DeepFieldStruct `json:"deep_field_nested" required:"deep"` +} + +type RequiredNestedDeepRequiredDeepFieldStruct struct { + RequiredNested DeepFieldStruct `json:"deep_field_nested" required:"true,deep"` +} + +type RequiredNestedPointerDeepRequiredDeepFieldStruct struct { + RequiredNested *DeepFieldStruct `json:"deep_field_nested" required:"true,deep"` +} + +type NotNilNestedPointerRequiredDeepFieldStruct struct { + RequiredNested *DeepFieldStruct `json:"deep_field_nested" required:"not-nil"` +} + +type NotNilNestedPointerDeepRequiredDeepFieldStruct struct { + RequiredNested *DeepFieldStruct `json:"deep_field_nested" required:"not-nil,deep"` +} + +// Complex test +type ComplexChildStruct struct { + OptionalField string `json:"optional_field"` + RequiredField string `json:"required_field" required:"true"` + RequiredSliceField []string `json:"required_slice_field" required:"not-nil"` + OptionalDeepFieldStruct *DeepFieldStruct `json:"optional_deep_field_struct" required:"not-nil,deep"` +} + +type ComplexParentStruct struct { + OptionalField string `json:"optional_field"` + RequiredField string `json:"required_field" required:"true"` + RequiredComplexChildStructSlice []ComplexChildStruct `json:"required_complex_child_struct_slice" required:"true,deep"` + NonNilComplexChildStructSlice []ComplexChildStruct `json:"non_nil_complex_child_struct_slice" required:"not-nil,deep"` + DeepRequiredDeepFieldStructSlice []DeepFieldStruct `json:"deep_required_deep_field_struct_slice" required:"deep"` + OptionalDeepComplexChildStruct *ComplexChildStruct `json:"optional_complex_child_struct" required:"deep"` +} + +// Embed test +type OptionalEmbedFieldStruct struct { + RequiredFieldStruct +} + +type RequiredEmbedFieldStruct struct { + RequiredFieldStruct `required:"deep"` +} + +type MultiRequiredEmbedFieldStruct struct { + RequiredEmbedFieldStruct `required:"deep"` +} + +func TestRequiredFieldsValidation(t *testing.T) { + var emptyJsonString, zeroValueJsonString, nonZeroValueJsonString string + + // **************************************************************************************************** + // Basic field types - Checked + // **************************************************************************************************** + var optionalFieldStruct OptionalFieldStruct + var requiredFieldStruct RequiredFieldStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "Basic.A", emptyJsonString, reflect.TypeOf(optionalFieldStruct), "") + CheckValidation(t, "Basic.B", emptyJsonString, reflect.TypeOf(requiredFieldStruct), "field is required") + + // Json string with zero value + zeroValueJsonString = `{"field":""}` + CheckValidation(t, "Basic.C", zeroValueJsonString, reflect.TypeOf(optionalFieldStruct), "") + CheckValidation(t, "Basic.D", zeroValueJsonString, reflect.TypeOf(requiredFieldStruct), "field is required") + + // Json string with non-zero value + nonZeroValueJsonString = `{"field":"value"}` + CheckValidation(t, "Basic.E", nonZeroValueJsonString, reflect.TypeOf(optionalFieldStruct), "") + CheckValidation(t, "Basic.F", nonZeroValueJsonString, reflect.TypeOf(requiredFieldStruct), "") + + // **************************************************************************************************** + // Basic pointer field types - Checked + // **************************************************************************************************** + var optionalPointerFieldStruct OptionalPointerFieldStruct + var requiredPointerFieldStruct RequiredPointerFieldStruct + var notNilPointerFieldStruct NotNilPointerFieldStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "Pointer.A", emptyJsonString, reflect.TypeOf(optionalPointerFieldStruct), "") + CheckValidation(t, "Pointer.B", emptyJsonString, reflect.TypeOf(requiredPointerFieldStruct), "pointer_field is required") + CheckValidation(t, "Pointer.C", emptyJsonString, reflect.TypeOf(notNilPointerFieldStruct), "pointer_field is required") + + // Json string with zero value + zeroValueJsonString = `{"pointer_field": ""}` + CheckValidation(t, "Pointer.D", zeroValueJsonString, reflect.TypeOf(optionalPointerFieldStruct), "") + CheckValidation(t, "Pointer.E", zeroValueJsonString, reflect.TypeOf(requiredPointerFieldStruct), "pointer_field is required") + CheckValidation(t, "Pointer.F", zeroValueJsonString, reflect.TypeOf(notNilPointerFieldStruct), "") + + // Json string with non-zero value + nonZeroValueJsonString = `{"pointer_field": "value"}` + CheckValidation(t, "Pointer.G", nonZeroValueJsonString, reflect.TypeOf(optionalPointerFieldStruct), "") + CheckValidation(t, "Pointer.H", nonZeroValueJsonString, reflect.TypeOf(requiredPointerFieldStruct), "") + CheckValidation(t, "Pointer.I", nonZeroValueJsonString, reflect.TypeOf(notNilPointerFieldStruct), "") + + // **************************************************************************************************** + // Basic slice field types - Checked + // **************************************************************************************************** + var optionalSliceStruct OptionalSliceStruct + var optionalDeepSliceStruct OptionalDeepSliceStruct + var requiredSliceStruct RequiredSliceStruct + var requiredDeepSliceStruct RequiredDeepSliceStruct + var notNilSliceStruct NotNilSliceStruct + var notNilDeepSliceStruct NotNilDeepSliceStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "Slice.A", emptyJsonString, reflect.TypeOf(optionalSliceStruct), "") + CheckValidation(t, "Slice.B", emptyJsonString, reflect.TypeOf(optionalDeepSliceStruct), "") + CheckValidation(t, "Slice.C", emptyJsonString, reflect.TypeOf(requiredSliceStruct), "slice_field is required") + CheckValidation(t, "Slice.D", emptyJsonString, reflect.TypeOf(requiredDeepSliceStruct), "slice_field is required") + CheckValidation(t, "Slice.E", emptyJsonString, reflect.TypeOf(notNilSliceStruct), "slice_field is required") + CheckValidation(t, "Slice.F", emptyJsonString, reflect.TypeOf(notNilDeepSliceStruct), "slice_field is required") + + // Json string with zero value slice + zeroValueJsonString = `{"slice_field": []}` + CheckValidation(t, "Slice.G", zeroValueJsonString, reflect.TypeOf(optionalSliceStruct), "") + CheckValidation(t, "Slice.H", zeroValueJsonString, reflect.TypeOf(optionalDeepSliceStruct), "") + CheckValidation(t, "Slice.I", zeroValueJsonString, reflect.TypeOf(requiredSliceStruct), "slice_field may not be empty") + CheckValidation(t, "Slice.J", zeroValueJsonString, reflect.TypeOf(requiredDeepSliceStruct), "slice_field may not be empty") + CheckValidation(t, "Slice.K", zeroValueJsonString, reflect.TypeOf(notNilSliceStruct), "") + CheckValidation(t, "Slice.L", zeroValueJsonString, reflect.TypeOf(notNilDeepSliceStruct), "") + + // Json string with single zero-value item + zeroValueJsonString = `{"slice_field": [""]}` + CheckValidation(t, "Slice.M", zeroValueJsonString, reflect.TypeOf(optionalSliceStruct), "") + CheckValidation(t, "Slice.N", zeroValueJsonString, reflect.TypeOf(optionalDeepSliceStruct), "slice_field is required") + CheckValidation(t, "Slice.O", zeroValueJsonString, reflect.TypeOf(requiredSliceStruct), "") + CheckValidation(t, "Slice.P", zeroValueJsonString, reflect.TypeOf(requiredDeepSliceStruct), "slice_field is required") + CheckValidation(t, "Slice.Q", zeroValueJsonString, reflect.TypeOf(notNilSliceStruct), "") + CheckValidation(t, "Slice.R", zeroValueJsonString, reflect.TypeOf(notNilDeepSliceStruct), "slice_field is required") + + // Json string with non-zero value + nonZeroValueJsonString = `{"slice_field": ["value"]}` + CheckValidation(t, "Slice.S", nonZeroValueJsonString, reflect.TypeOf(optionalSliceStruct), "") + CheckValidation(t, "Slice.T", nonZeroValueJsonString, reflect.TypeOf(optionalDeepSliceStruct), "") + CheckValidation(t, "Slice.U", nonZeroValueJsonString, reflect.TypeOf(requiredSliceStruct), "") + CheckValidation(t, "Slice.V", nonZeroValueJsonString, reflect.TypeOf(requiredDeepSliceStruct), "") + CheckValidation(t, "Slice.W", nonZeroValueJsonString, reflect.TypeOf(notNilSliceStruct), "") + CheckValidation(t, "Slice.X", nonZeroValueJsonString, reflect.TypeOf(notNilDeepSliceStruct), "") + + // **************************************************************************************************** + // Custom slice type - Checked + // **************************************************************************************************** + var requiredFieldSliceType RequiredFieldSliceType + + // Empty Json array + emptyJsonString = `[]` + CheckValidation(t, "SliceType.A", emptyJsonString, reflect.TypeOf(requiredFieldSliceType), "") + + // Json array with zero value + zeroValueJsonString = `[{}]` + CheckValidation(t, "SliceType.B", zeroValueJsonString, reflect.TypeOf(requiredFieldSliceType), "") + + // **************************************************************************************************** + // Basic map field types - Checked + // **************************************************************************************************** + var optionalMapStruct OptionalMapStruct + var requiredMapStruct RequiredMapStruct + var notNilMapStruct NotNilMapStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "Map.A", emptyJsonString, reflect.TypeOf(optionalMapStruct), "") + CheckValidation(t, "Map.B", emptyJsonString, reflect.TypeOf(requiredMapStruct), "map_field is required") + CheckValidation(t, "Map.C", emptyJsonString, reflect.TypeOf(notNilMapStruct), "map_field is required") + + // Json string with zero value slice + zeroValueJsonString = `{"map_field": {}}` + CheckValidation(t, "Map.D", zeroValueJsonString, reflect.TypeOf(optionalMapStruct), "") + CheckValidation(t, "Map.E", zeroValueJsonString, reflect.TypeOf(requiredMapStruct), "map_field may not be empty") + CheckValidation(t, "Map.F", zeroValueJsonString, reflect.TypeOf(notNilMapStruct), "") + + // Json string with non-zero value + nonZeroValueJsonString = `{"map_field": {"key": "value"}}` + CheckValidation(t, "Map.G", nonZeroValueJsonString, reflect.TypeOf(optionalMapStruct), "") + CheckValidation(t, "Map.H", nonZeroValueJsonString, reflect.TypeOf(requiredMapStruct), "") + CheckValidation(t, "Map.I", nonZeroValueJsonString, reflect.TypeOf(notNilMapStruct), "") + + // **************************************************************************************************** + // Structs with optional nested fields - Checked + // **************************************************************************************************** + var optionalNestedOptionalFieldStruct OptionalNestedOptionalFieldStruct + var optionalNestedPointerOptionalFieldStruct OptionalNestedPointerOptionalFieldStruct + var requiredNestedOptionalFieldStruct RequiredNestedOptionalFieldStruct + var requiredNestedPointerOptionalFieldStruct RequiredNestedPointerOptionalFieldStruct + var notNilNestedPointerOptionalFieldStruct NotNilNestedPointerOptionalFieldStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "OptionalNestedFields.A", emptyJsonString, reflect.TypeOf(optionalNestedOptionalFieldStruct), "") + CheckValidation(t, "OptionalNestedFields.B", emptyJsonString, reflect.TypeOf(optionalNestedPointerOptionalFieldStruct), "") + CheckValidation(t, "OptionalNestedFields.C", emptyJsonString, reflect.TypeOf(requiredNestedOptionalFieldStruct), "optional_field_nested is required") + CheckValidation(t, "OptionalNestedFields.D", emptyJsonString, reflect.TypeOf(requiredNestedPointerOptionalFieldStruct), "optional_field_nested is required") + CheckValidation(t, "OptionalNestedFields.E", emptyJsonString, reflect.TypeOf(notNilNestedPointerOptionalFieldStruct), "optional_field_nested is required") + + // Json string with zero value + zeroValueJsonString = `{"optional_field_nested": {}}` + CheckValidation(t, "OptionalNestedFields.F", zeroValueJsonString, reflect.TypeOf(optionalNestedOptionalFieldStruct), "") + CheckValidation(t, "OptionalNestedFields.G", zeroValueJsonString, reflect.TypeOf(optionalNestedPointerOptionalFieldStruct), "") + CheckValidation(t, "OptionalNestedFields.H", zeroValueJsonString, reflect.TypeOf(requiredNestedOptionalFieldStruct), "optional_field_nested is required") + CheckValidation(t, "OptionalNestedFields.I", zeroValueJsonString, reflect.TypeOf(requiredNestedPointerOptionalFieldStruct), "optional_field_nested is required") + CheckValidation(t, "OptionalNestedFields.J", zeroValueJsonString, reflect.TypeOf(notNilNestedPointerOptionalFieldStruct), "") + + // Json string with non-zero value + nonZeroValueJsonString = `{"optional_field_nested": {"field": "value"}}` + CheckValidation(t, "OptionalNestedFields.K", nonZeroValueJsonString, reflect.TypeOf(optionalNestedOptionalFieldStruct), "") + CheckValidation(t, "OptionalNestedFields.L", nonZeroValueJsonString, reflect.TypeOf(optionalNestedPointerOptionalFieldStruct), "") + CheckValidation(t, "OptionalNestedFields.M", nonZeroValueJsonString, reflect.TypeOf(requiredNestedOptionalFieldStruct), "") + CheckValidation(t, "OptionalNestedFields.N", nonZeroValueJsonString, reflect.TypeOf(requiredNestedPointerOptionalFieldStruct), "") + CheckValidation(t, "OptionalNestedFields.O", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerOptionalFieldStruct), "") + + // **************************************************************************************************** + // Structs with required nested fields - Checked + // **************************************************************************************************** + var optionalNestedRequiredFieldStruct OptionalNestedRequiredFieldStruct + var optionalNestedPointerRequiredFieldStruct OptionalNestedPointerRequiredFieldStruct + var requiredNestedRequiredFieldStruct RequiredNestedRequiredFieldStruct + var requiredNestedPointerRequiredFieldStruct RequiredNestedPointerRequiredFieldStruct + var notNilNestedPointerRequiredFieldStruct NotNilNestedPointerRequiredFieldStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "RequiredNestedFields.A", emptyJsonString, reflect.TypeOf(optionalNestedRequiredFieldStruct), "") + CheckValidation(t, "RequiredNestedFields.B", emptyJsonString, reflect.TypeOf(optionalNestedPointerRequiredFieldStruct), "") + CheckValidation(t, "RequiredNestedFields.C", emptyJsonString, reflect.TypeOf(requiredNestedRequiredFieldStruct), "required_field_nested is required") + CheckValidation(t, "RequiredNestedFields.D", emptyJsonString, reflect.TypeOf(requiredNestedPointerRequiredFieldStruct), "required_field_nested is required") + CheckValidation(t, "RequiredNestedFields.E", emptyJsonString, reflect.TypeOf(notNilNestedPointerRequiredFieldStruct), "required_field_nested is required") + + // Json string with zero value + zeroValueJsonString = `{"required_field_nested": {}}` + CheckValidation(t, "RequiredNestedFields.F", zeroValueJsonString, reflect.TypeOf(optionalNestedRequiredFieldStruct), "") + CheckValidation(t, "RequiredNestedFields.G", zeroValueJsonString, reflect.TypeOf(optionalNestedPointerRequiredFieldStruct), "") + CheckValidation(t, "RequiredNestedFields.H", zeroValueJsonString, reflect.TypeOf(requiredNestedRequiredFieldStruct), "required_field_nested is required") + CheckValidation(t, "RequiredNestedFields.I", zeroValueJsonString, reflect.TypeOf(requiredNestedPointerRequiredFieldStruct), "required_field_nested is required") + CheckValidation(t, "RequiredNestedFields.J", zeroValueJsonString, reflect.TypeOf(notNilNestedPointerRequiredFieldStruct), "") + + // Json string with non-zero value + nonZeroValueJsonString = `{"required_field_nested": {"field": "value"}}` + CheckValidation(t, "RequiredNestedFields.K", nonZeroValueJsonString, reflect.TypeOf(optionalNestedRequiredFieldStruct), "") + CheckValidation(t, "RequiredNestedFields.L", nonZeroValueJsonString, reflect.TypeOf(optionalNestedPointerRequiredFieldStruct), "") + CheckValidation(t, "RequiredNestedFields.M", nonZeroValueJsonString, reflect.TypeOf(requiredNestedRequiredFieldStruct), "") + CheckValidation(t, "RequiredNestedFields.N", nonZeroValueJsonString, reflect.TypeOf(requiredNestedPointerRequiredFieldStruct), "") + CheckValidation(t, "RequiredNestedFields.O", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerRequiredFieldStruct), "") + + // **************************************************************************************************** + // Structs with optional nested pointer fields - Checked + // **************************************************************************************************** + var optionalNestedOptionalPointerFieldStruct OptionalNestedOptionalPointerFieldStruct + var optionalNestedPointerOptionalPointerFieldStruct OptionalNestedPointerOptionalPointerFieldStruct + var requiredNestedOptionalPointerFieldStruct RequiredNestedOptionalPointerFieldStruct + var requiredNestedPointerOptionalPointerFieldStruct RequiredNestedPointerOptionalPointerFieldStruct + var notNilNestedPointerOptionalPointerFieldStruct NotNilNestedPointerOptionalPointerFieldStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "OptionalNestedPointers.A", emptyJsonString, reflect.TypeOf(optionalNestedOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.B", emptyJsonString, reflect.TypeOf(optionalNestedPointerOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.C", emptyJsonString, reflect.TypeOf(requiredNestedOptionalPointerFieldStruct), "optional_field_nested is required") + CheckValidation(t, "OptionalNestedPointers.D", emptyJsonString, reflect.TypeOf(requiredNestedPointerOptionalPointerFieldStruct), "optional_field_nested is required") + CheckValidation(t, "OptionalNestedPointers.E", emptyJsonString, reflect.TypeOf(notNilNestedPointerOptionalPointerFieldStruct), "optional_field_nested is required") + + // Json string with zero value + zeroValueJsonString = `{"optional_field_nested": {}}` + CheckValidation(t, "OptionalNestedPointers.F", zeroValueJsonString, reflect.TypeOf(optionalNestedOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.G", zeroValueJsonString, reflect.TypeOf(optionalNestedPointerOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.H", zeroValueJsonString, reflect.TypeOf(requiredNestedOptionalPointerFieldStruct), "optional_field_nested is required") + CheckValidation(t, "OptionalNestedPointers.I", zeroValueJsonString, reflect.TypeOf(requiredNestedPointerOptionalPointerFieldStruct), "optional_field_nested is required") + CheckValidation(t, "OptionalNestedPointers.J", zeroValueJsonString, reflect.TypeOf(notNilNestedPointerOptionalPointerFieldStruct), "") + + // Json string with non-nil pointer value + nonZeroValueJsonString = `{"optional_field_nested": {"pointer_field": ""}}` + CheckValidation(t, "OptionalNestedPointers.K", nonZeroValueJsonString, reflect.TypeOf(optionalNestedOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.L", nonZeroValueJsonString, reflect.TypeOf(optionalNestedPointerOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.M", nonZeroValueJsonString, reflect.TypeOf(requiredNestedOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.N", nonZeroValueJsonString, reflect.TypeOf(requiredNestedPointerOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.O", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerOptionalPointerFieldStruct), "") + + // Json string with non-zero value + nonZeroValueJsonString = `{"optional_field_nested": {"pointer_field": "value"}}` + CheckValidation(t, "OptionalNestedPointers.P", nonZeroValueJsonString, reflect.TypeOf(optionalNestedOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.Q", nonZeroValueJsonString, reflect.TypeOf(optionalNestedPointerOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.R", nonZeroValueJsonString, reflect.TypeOf(requiredNestedOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.S", nonZeroValueJsonString, reflect.TypeOf(requiredNestedPointerOptionalPointerFieldStruct), "") + CheckValidation(t, "OptionalNestedPointers.T", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerOptionalPointerFieldStruct), "") + + // **************************************************************************************************** + // Structs with required nested pointer fields - Checked + // **************************************************************************************************** + var optionalNestedRequiredPointerFieldStruct OptionalNestedRequiredPointerFieldStruct + var optionalNestedPointerRequiredPointerFieldStruct OptionalNestedPointerRequiredPointerFieldStruct + var requiredNestedRequiredPointerFieldStruct RequiredNestedRequiredPointerFieldStruct + var requiredNestedPointerRequiredPointerFieldStruct RequiredNestedPointerRequiredPointerFieldStruct + var notNilNestedPointerRequiredPointerFieldStruct NotNilNestedPointerRequiredPointerFieldStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "RequiredNestedPointers.A", emptyJsonString, reflect.TypeOf(optionalNestedRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.B", emptyJsonString, reflect.TypeOf(optionalNestedPointerRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.C", emptyJsonString, reflect.TypeOf(requiredNestedRequiredPointerFieldStruct), "required_field_nested is required") + CheckValidation(t, "RequiredNestedPointers.D", emptyJsonString, reflect.TypeOf(requiredNestedPointerRequiredPointerFieldStruct), "required_field_nested is required") + CheckValidation(t, "RequiredNestedPointers.E", emptyJsonString, reflect.TypeOf(notNilNestedPointerRequiredPointerFieldStruct), "required_field_nested is required") + + // Json string with zero value + zeroValueJsonString = `{"required_field_nested": {}}` + CheckValidation(t, "RequiredNestedPointers.F", zeroValueJsonString, reflect.TypeOf(optionalNestedRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.G", zeroValueJsonString, reflect.TypeOf(optionalNestedPointerRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.H", zeroValueJsonString, reflect.TypeOf(requiredNestedRequiredPointerFieldStruct), "required_field_nested is required") + CheckValidation(t, "RequiredNestedPointers.I", zeroValueJsonString, reflect.TypeOf(requiredNestedPointerRequiredPointerFieldStruct), "required_field_nested is required") + CheckValidation(t, "RequiredNestedPointers.J", zeroValueJsonString, reflect.TypeOf(notNilNestedPointerRequiredPointerFieldStruct), "") + + // Json string with non-nil pointer value + nonZeroValueJsonString = `{"required_field_nested": {"pointer_field": ""}}` + CheckValidation(t, "RequiredNestedPointers.K", nonZeroValueJsonString, reflect.TypeOf(optionalNestedRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.L", nonZeroValueJsonString, reflect.TypeOf(optionalNestedPointerRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.M", nonZeroValueJsonString, reflect.TypeOf(requiredNestedRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.N", nonZeroValueJsonString, reflect.TypeOf(requiredNestedPointerRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.O", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerRequiredPointerFieldStruct), "") + + // Json string with non-zero value + nonZeroValueJsonString = `{"required_field_nested": {"pointer_field": "value"}}` + CheckValidation(t, "RequiredNestedPointers.P", nonZeroValueJsonString, reflect.TypeOf(optionalNestedRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.Q", nonZeroValueJsonString, reflect.TypeOf(optionalNestedPointerRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.R", nonZeroValueJsonString, reflect.TypeOf(requiredNestedRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.S", nonZeroValueJsonString, reflect.TypeOf(requiredNestedPointerRequiredPointerFieldStruct), "") + CheckValidation(t, "RequiredNestedPointers.T", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerRequiredPointerFieldStruct), "") + + // **************************************************************************************************** + // Deep test - Checked + // **************************************************************************************************** + var optionalNestedDeepRequiredDeepFieldStruct OptionalNestedDeepRequiredDeepFieldStruct + var optionalNestedPointerDeepRequiredDeepFieldPointerStruct OptionalNestedPointerDeepRequiredDeepFieldPointerStruct + var requiredNestedDeepRequiredDeepFieldStruct RequiredNestedDeepRequiredDeepFieldStruct + var requiredNestedPointerDeepRequiredDeepFieldStruct RequiredNestedPointerDeepRequiredDeepFieldStruct + var notNilNestedPointerRequiredDeepFieldStruct NotNilNestedPointerRequiredDeepFieldStruct + var notNilNestedPointerDeepRequiredDeepFieldStruct NotNilNestedPointerDeepRequiredDeepFieldStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "Deep.A", emptyJsonString, reflect.TypeOf(optionalNestedDeepRequiredDeepFieldStruct), "deep_field_nested.required_field is required") + CheckValidation(t, "Deep.B", emptyJsonString, reflect.TypeOf(optionalNestedPointerDeepRequiredDeepFieldPointerStruct), "") + CheckValidation(t, "Deep.C", emptyJsonString, reflect.TypeOf(requiredNestedDeepRequiredDeepFieldStruct), "deep_field_nested is required") + CheckValidation(t, "Deep.D", emptyJsonString, reflect.TypeOf(requiredNestedPointerDeepRequiredDeepFieldStruct), "deep_field_nested is required") + CheckValidation(t, "Deep.E", emptyJsonString, reflect.TypeOf(notNilNestedPointerRequiredDeepFieldStruct), "deep_field_nested is required") + CheckValidation(t, "Deep.F", emptyJsonString, reflect.TypeOf(notNilNestedPointerDeepRequiredDeepFieldStruct), "deep_field_nested is required") + + // Json string with zero value + zeroValueJsonString = `{"deep_field_nested": {}}` + CheckValidation(t, "Deep.G", zeroValueJsonString, reflect.TypeOf(optionalNestedDeepRequiredDeepFieldStruct), "deep_field_nested.required_field is required") + CheckValidation(t, "Deep.H", zeroValueJsonString, reflect.TypeOf(optionalNestedPointerDeepRequiredDeepFieldPointerStruct), "deep_field_nested.required_field is required") + CheckValidation(t, "Deep.I", zeroValueJsonString, reflect.TypeOf(requiredNestedDeepRequiredDeepFieldStruct), "deep_field_nested is required") + CheckValidation(t, "Deep.J", zeroValueJsonString, reflect.TypeOf(requiredNestedPointerDeepRequiredDeepFieldStruct), "deep_field_nested is required") + CheckValidation(t, "Deep.K", zeroValueJsonString, reflect.TypeOf(notNilNestedPointerRequiredDeepFieldStruct), "") + CheckValidation(t, "Deep.L", zeroValueJsonString, reflect.TypeOf(notNilNestedPointerDeepRequiredDeepFieldStruct), "deep_field_nested.required_field is required") + + // Json string with non-zero optional value + nonZeroValueJsonString = `{"deep_field_nested": {"optional_field": "value"}}` + CheckValidation(t, "Deep.M", nonZeroValueJsonString, reflect.TypeOf(optionalNestedDeepRequiredDeepFieldStruct), "deep_field_nested.required_field is required") + CheckValidation(t, "Deep.N", nonZeroValueJsonString, reflect.TypeOf(optionalNestedPointerDeepRequiredDeepFieldPointerStruct), "deep_field_nested.required_field is required") + CheckValidation(t, "Deep.O", nonZeroValueJsonString, reflect.TypeOf(requiredNestedDeepRequiredDeepFieldStruct), "deep_field_nested.required_field is required") + CheckValidation(t, "Deep.P", nonZeroValueJsonString, reflect.TypeOf(requiredNestedPointerDeepRequiredDeepFieldStruct), "deep_field_nested.required_field is required") + CheckValidation(t, "Deep.Q", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerRequiredDeepFieldStruct), "") + CheckValidation(t, "Deep.R", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerDeepRequiredDeepFieldStruct), "deep_field_nested.required_field is required") + + // Json string with non-zero required value + nonZeroValueJsonString = `{"deep_field_nested": {"required_field": "value"}}` + CheckValidation(t, "Deep.S", nonZeroValueJsonString, reflect.TypeOf(optionalNestedDeepRequiredDeepFieldStruct), "") + CheckValidation(t, "Deep.T", nonZeroValueJsonString, reflect.TypeOf(optionalNestedPointerDeepRequiredDeepFieldPointerStruct), "") + CheckValidation(t, "Deep.U", nonZeroValueJsonString, reflect.TypeOf(requiredNestedDeepRequiredDeepFieldStruct), "") + CheckValidation(t, "Deep.V", nonZeroValueJsonString, reflect.TypeOf(requiredNestedPointerDeepRequiredDeepFieldStruct), "") + CheckValidation(t, "Deep.W", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerRequiredDeepFieldStruct), "") + CheckValidation(t, "Deep.X", nonZeroValueJsonString, reflect.TypeOf(notNilNestedPointerDeepRequiredDeepFieldStruct), "") + + // **************************************************************************************************** + // Complex test + // **************************************************************************************************** + var complexParentStruct ComplexParentStruct + + // This test will gradually fix errors until all required fields are filled + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "Complex.A", emptyJsonString, reflect.TypeOf(complexParentStruct), "required_field is required") + + // required_field is filled + nonZeroValueJsonString = `{"required_field": "value"}` + CheckValidation(t, "Complex.B", nonZeroValueJsonString, reflect.TypeOf(complexParentStruct), "required_complex_child_struct_slice is required") + + // previous fields filled, and required_complex_child_struct_slice is present, but empty + zeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": []}` + CheckValidation(t, "Complex.C", zeroValueJsonString, reflect.TypeOf(complexParentStruct), "required_complex_child_struct_slice may not be empty") + + // previous fields filled, and required_complex_child_struct_slice with one empty object + zeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{}]}` + CheckValidation(t, "Complex.D", zeroValueJsonString, reflect.TypeOf(complexParentStruct), "required_complex_child_struct_slice.required_field is required") + + // previous fields filled, and required_complex_child_struct_slice with one object with required_field filled + nonZeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{"required_field": "value"}]}` + CheckValidation(t, "Complex.E", nonZeroValueJsonString, reflect.TypeOf(complexParentStruct), "required_complex_child_struct_slice.required_slice_field is required") + + // previous fields filled, and required_complex_child_struct_slice with one object with required_field and required_slice_field present + zeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{"required_field": "value", "required_slice_field": []}]}` + CheckValidation(t, "Complex.F", zeroValueJsonString, reflect.TypeOf(complexParentStruct), "required_complex_child_struct_slice.optional_deep_field_struct is required") + + // previous fields filled, and required_complex_child_struct_slice with one object with required_field, required_slice_field and optional_deep_field_struct present + nonZeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{"required_field": "value", "required_slice_field": [], "optional_deep_field_struct": {}}]}` + CheckValidation(t, "Complex.G", nonZeroValueJsonString, reflect.TypeOf(complexParentStruct), "required_complex_child_struct_slice.optional_deep_field_struct.required_field is required") + + // previous fields filled, and required_complex_child_struct_slice with one object with required_field, required_slice_field, optional_deep_field_struct and required_field present + nonZeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{"required_field": "value", "required_slice_field": [], "optional_deep_field_struct": {"required_field": "value"}}]}` + CheckValidation(t, "Complex.H", nonZeroValueJsonString, reflect.TypeOf(complexParentStruct), "non_nil_complex_child_struct_slice is required") + + // previous fields filled, and not_nil_complex_child_struct_slice is present, but empty + zeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{"required_field": "value", "required_slice_field": [], "optional_deep_field_struct": {"required_field": "value"}}], "non_nil_complex_child_struct_slice": []}` + CheckValidation(t, "Complex.I", zeroValueJsonString, reflect.TypeOf(complexParentStruct), "") + + // previous fields filled, and deep_complex_child_struct_slice is present, but empty + zeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{"required_field": "value", "required_slice_field": [], "optional_deep_field_struct": {"required_field": "value"}}], "non_nil_complex_child_struct_slice": [], "deep_complex_child_struct_slice": []}` + CheckValidation(t, "Complex.J", zeroValueJsonString, reflect.TypeOf(complexParentStruct), "") + + // previous fields filled, and deep_complex_child_struct_slice is present with one empty object + zeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{"required_field": "value", "required_slice_field": [], "optional_deep_field_struct": {"required_field": "value"}}], "non_nil_complex_child_struct_slice": [], "deep_required_deep_field_struct_slice": [{}]}` + CheckValidation(t, "Complex.K", zeroValueJsonString, reflect.TypeOf(complexParentStruct), "deep_required_deep_field_struct_slice.required_field is required") + + // previous fields filled, and deep_complex_child_struct_slice is present with one object with required_field filled, and one without + zeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{"required_field": "value", "required_slice_field": [], "optional_deep_field_struct": {"required_field": "value"}}], "non_nil_complex_child_struct_slice": [], "deep_required_deep_field_struct_slice": [{"required_field": "value"}, {"optional_field": "value"}]}` + CheckValidation(t, "Complex.L", zeroValueJsonString, reflect.TypeOf(complexParentStruct), "deep_required_deep_field_struct_slice.required_field is required") + + // previous fields filled, and optional_complex_child_struct is present, but empty + zeroValueJsonString = `{"required_field": "value", "required_complex_child_struct_slice": [{"required_field": "value", "required_slice_field": [], "optional_deep_field_struct": {"required_field": "value"}}], "non_nil_complex_child_struct_slice": [], "optional_complex_child_struct": {}}` + CheckValidation(t, "Complex.M", zeroValueJsonString, reflect.TypeOf(complexParentStruct), "optional_complex_child_struct.required_field is required") + + // **************************************************************************************************** + // Embed test + // **************************************************************************************************** + var optionalEmbedFieldStruct OptionalEmbedFieldStruct + var requiredEmbedFieldStruct RequiredEmbedFieldStruct + var multiRequiredEmbedFieldStruct MultiRequiredEmbedFieldStruct + + // Empty json string + emptyJsonString = `{}` + CheckValidation(t, "Embed.A", emptyJsonString, reflect.TypeOf(optionalEmbedFieldStruct), "") + CheckValidation(t, "Embed.B", emptyJsonString, reflect.TypeOf(requiredEmbedFieldStruct), "field is required") + CheckValidation(t, "Embed.C", emptyJsonString, reflect.TypeOf(multiRequiredEmbedFieldStruct), "field is required") + + // Json string with zero value + zeroValueJsonString = `{"field":""}` + CheckValidation(t, "Embed.D", zeroValueJsonString, reflect.TypeOf(optionalEmbedFieldStruct), "") + CheckValidation(t, "Embed.E", zeroValueJsonString, reflect.TypeOf(requiredEmbedFieldStruct), "field is required") + CheckValidation(t, "Embed.F", zeroValueJsonString, reflect.TypeOf(multiRequiredEmbedFieldStruct), "field is required") + + // Json string with non-zero value + nonZeroValueJsonString = `{"field":"value"}` + CheckValidation(t, "Embed.G", nonZeroValueJsonString, reflect.TypeOf(optionalEmbedFieldStruct), "") + CheckValidation(t, "Embed.H", nonZeroValueJsonString, reflect.TypeOf(requiredEmbedFieldStruct), "") + CheckValidation(t, "Embed.I", nonZeroValueJsonString, reflect.TypeOf(multiRequiredEmbedFieldStruct), "") +} + +func CheckValidation(t *testing.T, testNumber string, jsonString string, objectType reflect.Type, expectedResult string) { + objectValuePtr := reflect.New(objectType) + err := json.Unmarshal([]byte(jsonString), objectValuePtr.Interface()) + if err != nil { + panic(errors.Errorf("Error unmarshalling json string for test %s: %s", testNumber, err.Error())) + } + + err = ValidateRequiredFields(objectValuePtr.Interface()) + if expectedResult == "" { + if err != nil { + fmt.Printf("%#v\n", objectValuePtr.Interface()) + t.Errorf("%s: Expected no error, got %s", testNumber, err) + } + } else { + if err == nil { + fmt.Printf("%#v\n", objectValuePtr.Interface()) + t.Errorf("%s: Expected error, got nil", testNumber) + } + if err != nil && err.Error() != expectedResult { + fmt.Printf("%#v\n", objectValuePtr.Interface()) + t.Errorf("%s: Expected error %s, got %s", testNumber, expectedResult, err.Error()) + } + } +} diff --git a/struct_utils/struct_utils.go b/struct_utils/struct_utils.go index 1f25d4387f057ba2f466d12301d7f1f202382ef6..30a16412d1095447140ee62eab055aaac8751c21 100644 --- a/struct_utils/struct_utils.go +++ b/struct_utils/struct_utils.go @@ -1,6 +1,11 @@ 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, ".") +}