From 17ac9a57562370c1eb21a3aa07701c2907754c8e Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Wed, 17 Oct 2018 14:22:29 -0400 Subject: [PATCH] helper/validation: Add All() and IntInSlice() SchemaValidateFunc `All()` combines the outputs of multiple `SchemaValidateFunc`, to reduce the usage of custom validation functions that implement standard validation functions. Example provider usage: ```go ValidateFunc: validation.All( StringLenBetween(5, 42), StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"), ), ``` `IntInSlice()` is the `int` equivalent of `StringInSlice()` Example provider usage: ```go ValidateFunc: validation.IntInSlice([]int{30, 60, 120}) ``` Output from unit testing: ``` $ make test TEST=./helper/validation ==> Checking that code complies with gofmt requirements... go generate ./... 2018/10/17 14:16:03 Generated command/internal_plugin_list.go go list ./helper/validation | xargs -t -n4 go test -timeout=2m -parallel=4 go test -timeout=2m -parallel=4 github.com/hashicorp/terraform/helper/validation ok github.com/hashicorp/terraform/helper/validation 1.106s ``` --- helper/validation/validation.go | 36 +++++++++++++++++++++ helper/validation/validation_test.go | 47 ++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/helper/validation/validation.go b/helper/validation/validation.go index e9edcd333..01e131047 100644 --- a/helper/validation/validation.go +++ b/helper/validation/validation.go @@ -13,6 +13,21 @@ import ( "github.com/hashicorp/terraform/helper/structure" ) +// All returns a SchemaValidateFunc which tests if the provided value +// passes all provided SchemaValidateFunc +func All(validators ...schema.SchemaValidateFunc) schema.SchemaValidateFunc { + return func(i interface{}, k string) ([]string, []error) { + var allErrors []error + var allWarnings []string + for _, validator := range validators { + validatorWarnings, validatorErrors := validator(i, k) + allWarnings = append(allWarnings, validatorWarnings...) + allErrors = append(allErrors, validatorErrors...) + } + return allWarnings, allErrors + } +} + // IntBetween returns a SchemaValidateFunc which tests if the provided value // is of type int and is between min and max (inclusive) func IntBetween(min, max int) schema.SchemaValidateFunc { @@ -70,6 +85,27 @@ func IntAtMost(max int) schema.SchemaValidateFunc { } } +// IntInSlice returns a SchemaValidateFunc which tests if the provided value +// is of type int and matches the value of an element in the valid slice +func IntInSlice(valid []int) schema.SchemaValidateFunc { + return func(i interface{}, k string) (s []string, es []error) { + v, ok := i.(int) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be an integer", k)) + return + } + + for _, validInt := range valid { + if v == validInt { + return + } + } + + es = append(es, fmt.Errorf("expected %s to be one of %v, got %d", k, valid, v)) + return + } +} + // StringInSlice returns a SchemaValidateFunc which tests if the provided value // is of type string and matches the value of an element in the valid slice // will test with in lower case if ignoreCase is true diff --git a/helper/validation/validation_test.go b/helper/validation/validation_test.go index ea3b703bc..b3f779c0a 100644 --- a/helper/validation/validation_test.go +++ b/helper/validation/validation_test.go @@ -13,6 +13,34 @@ type testCase struct { expectedErr *regexp.Regexp } +func TestValidationAll(t *testing.T) { + runTestCases(t, []testCase{ + { + val: "valid", + f: All( + StringLenBetween(5, 42), + StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"), + ), + }, + { + val: "foo", + f: All( + StringLenBetween(5, 42), + StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"), + ), + expectedErr: regexp.MustCompile("expected length of [\\w]+ to be in the range \\(5 - 42\\), got foo"), + }, + { + val: "!!!!!", + f: All( + StringLenBetween(5, 42), + StringMatch(regexp.MustCompile(`[a-zA-Z0-9]+`), "value must be alphanumeric"), + ), + expectedErr: regexp.MustCompile("value must be alphanumeric"), + }, + }) +} + func TestValidationIntBetween(t *testing.T) { runTestCases(t, []testCase{ { @@ -82,6 +110,25 @@ func TestValidationIntAtMost(t *testing.T) { }) } +func TestValidationIntInSlice(t *testing.T) { + runTestCases(t, []testCase{ + { + val: 42, + f: IntInSlice([]int{1, 42}), + }, + { + val: 42, + f: IntInSlice([]int{10, 20}), + expectedErr: regexp.MustCompile("expected [\\w]+ to be one of \\[10 20\\], got 42"), + }, + { + val: "InvalidValue", + f: IntInSlice([]int{10, 20}), + expectedErr: regexp.MustCompile("expected type of [\\w]+ to be an integer"), + }, + }) +} + func TestValidationStringInSlice(t *testing.T) { runTestCases(t, []testCase{ {