Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

listvalidator: Added UniqueValues validator #88

Merged
merged 2 commits into from
Dec 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/88.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
listvalidator: Added `UniqueValues` validator
```
65 changes: 65 additions & 0 deletions listvalidator/unique_values.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package listvalidator

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

var _ validator.List = uniqueValuesValidator{}

// uniqueValuesValidator implements the validator.
type uniqueValuesValidator struct{}

// Description returns the plaintext description of the validator.
func (v uniqueValuesValidator) Description(_ context.Context) string {
return "all values must be unique"
}

// MarkdownDescription returns the Markdown description of the validator.
func (v uniqueValuesValidator) MarkdownDescription(ctx context.Context) string {
return v.Description(ctx)
}

// ValidateList implements the validation logic.
func (v uniqueValuesValidator) ValidateList(_ context.Context, req validator.ListRequest, resp *validator.ListResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}

elements := req.ConfigValue.Elements()

for indexOuter, elementOuter := range elements {
// Only evaluate known values for duplicates.
if elementOuter.IsUnknown() {
continue
}

for indexInner := indexOuter + 1; indexInner < len(elements); indexInner++ {
elementInner := elements[indexInner]

if elementInner.IsUnknown() {
continue
}

if !elementInner.Equal(elementOuter) {
continue
}

resp.Diagnostics.AddAttributeError(
req.Path,
"Duplicate List Value",
fmt.Sprintf("This attribute contains duplicate values of: %s", elementInner),
)
}
}
}

// UniqueValues returns a validator which ensures that any configured list
// only contains unique values. This is similar to using a set attribute type
// which inherently validates unique values, but with list ordering semantics.
// Null (unconfigured) and unknown (known after apply) values are skipped.
func UniqueValues() validator.List {
return uniqueValuesValidator{}
}
24 changes: 24 additions & 0 deletions listvalidator/unique_values_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package listvalidator_test

import (
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func ExampleUniqueValues() {
// Used within a Schema method of a DataSource, Provider, or Resource
_ = schema.Schema{
Attributes: map[string]schema.Attribute{
"example_attr": schema.ListAttribute{
ElementType: types.StringType,
Required: true,
Validators: []validator.List{
// Validate this list must contain only unique values.
listvalidator.UniqueValues(),
},
},
},
}
}
127 changes: 127 additions & 0 deletions listvalidator/unique_values_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package listvalidator_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

func TestUniqueValues(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
list types.List
expectedDiagnostics diag.Diagnostics
}{
"null-list": {
list: types.ListNull(types.StringType),
expectedDiagnostics: nil,
},
"unknown-list": {
list: types.ListUnknown(types.StringType),
expectedDiagnostics: nil,
},
"null-value": {
list: types.ListValueMust(
types.StringType,
[]attr.Value{types.StringNull()},
),
expectedDiagnostics: nil,
},
"null-values-duplicate": {
list: types.ListValueMust(
types.StringType,
[]attr.Value{types.StringNull(), types.StringNull()},
),
expectedDiagnostics: diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("test"),
"Duplicate List Value",
"This attribute contains duplicate values of: <null>",
),
},
},
"null-values-valid": {
list: types.ListValueMust(
types.StringType,
[]attr.Value{types.StringNull(), types.StringValue("test")},
),
expectedDiagnostics: nil,
},
"unknown-value": {
list: types.ListValueMust(
types.StringType,
[]attr.Value{types.StringUnknown()},
),
expectedDiagnostics: nil,
},
"unknown-values-duplicate": {
list: types.ListValueMust(
types.StringType,
[]attr.Value{types.StringUnknown(), types.StringUnknown()},
),
expectedDiagnostics: nil,
},
"unknown-values-valid": {
list: types.ListValueMust(
types.StringType,
[]attr.Value{types.StringUnknown(), types.StringValue("test")},
),
expectedDiagnostics: nil,
},
"known-value": {
list: types.ListValueMust(
types.StringType,
[]attr.Value{types.StringValue("test")},
),
expectedDiagnostics: nil,
},
"known-values-duplicate": {
list: types.ListValueMust(
types.StringType,
[]attr.Value{types.StringValue("test"), types.StringValue("test")},
),
expectedDiagnostics: diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("test"),
"Duplicate List Value",
"This attribute contains duplicate values of: \"test\"",
),
},
},
"known-values-valid": {
list: types.ListValueMust(
types.StringType,
[]attr.Value{types.StringValue("test1"), types.StringValue("test2")},
),
expectedDiagnostics: nil,
},
}

for name, testCase := range testCases {
name, testCase := name, testCase

t.Run(name, func(t *testing.T) {
t.Parallel()

request := validator.ListRequest{
Path: path.Root("test"),
PathExpression: path.MatchRoot("test"),
ConfigValue: testCase.list,
}
response := validator.ListResponse{}
listvalidator.UniqueValues().ValidateList(context.Background(), request, &response)

if diff := cmp.Diff(response.Diagnostics, testCase.expectedDiagnostics); diff != "" {
t.Errorf("unexpected diagnostics difference: %s", diff)
}
})
}
}