diff --git a/helper/validation/validation.go b/helper/validation/validation.go index bb5dd2096..8fc30478a 100644 --- a/helper/validation/validation.go +++ b/helper/validation/validation.go @@ -148,6 +148,19 @@ func ValidateJsonString(v interface{}, k string) (ws []string, errors []error) { return } +// ValidateListUniqueStrings is a ValidateFunc that ensures a list has no +// duplicate items in it. It's useful for when a list is needed over a set +// because order matters, yet the items still need to be unique. +func ValidateListUniqueStrings(v interface{}, k string) (ws []string, errors []error) { + for n1, v1 := range v.([]interface{}) { + for n2, v2 := range v.([]interface{}) { + if v1.(string) == v2.(string) && n1 != n2 { + errors = append(errors, fmt.Errorf("%q: duplicate entry - %s", k, v1.(string))) + } + } + } +} + // ValidateRegexp returns a SchemaValidateFunc which tests to make sure the // supplied string is a valid regular expression. func ValidateRegexp(v interface{}, k string) (ws []string, errors []error) { diff --git a/helper/validation/validation_test.go b/helper/validation/validation_test.go index ee2494f64..5fbd02a32 100644 --- a/helper/validation/validation_test.go +++ b/helper/validation/validation_test.go @@ -180,6 +180,25 @@ func TestValidateJsonString(t *testing.T) { } } +func TestValidateListUniqueStrings(t *testing.T) { + runTestCases(t, []testCase{ + { + val: []interface{}{"foo", "bar"}, + f: ValidateListUniqueStrings, + }, + { + val: []interface{}{"foo", "bar", "foo"}, + f: ValidateListUniqueStrings, + expectedErr: regexp.MustCompile("duplicate entry - foo"), + }, + { + val: []interface{}{"foo", "bar", "foo", "baz", "bar"}, + f: ValidateListUniqueStrings, + expectedErr: regexp.MustCompile("duplicate entry - (?:foo|bar)"), + }, + }) +} + func runTestCases(t *testing.T, cases []testCase) { matchErr := func(errs []error, r *regexp.Regexp) bool { // err must match one provided @@ -199,6 +218,10 @@ func runTestCases(t *testing.T, cases []testCase) { continue } + if len(errs) != 0 && tc.expectedErr == nil { + t.Fatalf("expected test case %d to produce no errors, got %v", i, errs) + } + if !matchErr(errs, tc.expectedErr) { t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs) }