Skip to content
Merged
3 changes: 3 additions & 0 deletions .changelog/37.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Introduced `listvalidator` package with `ValuesAre()` validation functions
```
3 changes: 3 additions & 0 deletions .changelog/41.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Added `SizeAtLeast()`, `SizeAtMost()` and `SizeBetween` validation functions to `listvalidator` package
```
2 changes: 2 additions & 0 deletions listvalidator/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package listvalidator provides validators for types.List attributes.
package listvalidator
58 changes: 58 additions & 0 deletions listvalidator/size_at_least.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package listvalidator

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/tfsdk"

"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
)

var _ tfsdk.AttributeValidator = sizeAtLeastValidator{}

// sizeAtLeastValidator validates that list contains at least min elements.
type sizeAtLeastValidator struct {
min int
}

// Description describes the validation in plain text formatting.
func (v sizeAtLeastValidator) Description(ctx context.Context) string {
return fmt.Sprintf("list must contain at least %d elements", v.min)
}

// MarkdownDescription describes the validation in Markdown formatting.
func (v sizeAtLeastValidator) MarkdownDescription(ctx context.Context) string {
return v.Description(ctx)
}

// Validate performs the validation.
func (v sizeAtLeastValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) {
elems, ok := validateList(ctx, req, resp)
if !ok {
return
}

if len(elems) < v.min {
resp.Diagnostics.Append(validatordiag.AttributeValueDiagnostic(
req.AttributePath,
v.Description(ctx),
fmt.Sprintf("%d", len(elems)),
))

return
}
}

// SizeAtLeast returns an AttributeValidator which ensures that any configured
// attribute value:
//
// - Is a List.
// - Contains at least min elements.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func SizeAtLeast(min int) tfsdk.AttributeValidator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go documentation 😉

return sizeAtLeastValidator{
min: min,
}
}
90 changes: 90 additions & 0 deletions listvalidator/size_at_least_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package listvalidator

import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

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

type testCase struct {
val attr.Value
min int
expectError bool
}
tests := map[string]testCase{
"not a List": {
val: types.Bool{Value: true},
expectError: true,
},
"List unknown": {
val: types.List{
Unknown: true,
ElemType: types.StringType,
},
expectError: false,
},
"List null": {
val: types.List{
Null: true,
ElemType: types.StringType,
},
expectError: false,
},
"List size greater than min": {
val: types.List{
ElemType: types.StringType,
Elems: []attr.Value{
types.String{Value: "first"},
types.String{Value: "second"},
},
},
min: 1,
expectError: false,
},
"List size equal to min": {
val: types.List{
ElemType: types.StringType,
Elems: []attr.Value{
types.String{Value: "first"},
},
},
min: 1,
expectError: false,
},
"List size less than min": {
val: types.List{
ElemType: types.StringType,
Elems: []attr.Value{},
},
min: 1,
expectError: true,
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
SizeAtLeast(test.min).Validate(context.TODO(), request, &response)

if !response.Diagnostics.HasError() && test.expectError {
t.Fatal("expected error, got no error")
}

if response.Diagnostics.HasError() && !test.expectError {
t.Fatalf("got unexpected error: %s", response.Diagnostics)
}
})
}
}
58 changes: 58 additions & 0 deletions listvalidator/size_at_most.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package listvalidator

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/tfsdk"

"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
)

var _ tfsdk.AttributeValidator = sizeAtMostValidator{}

// sizeAtMostValidator validates that list contains at most max elements.
type sizeAtMostValidator struct {
max int
}

// Description describes the validation in plain text formatting.
func (v sizeAtMostValidator) Description(ctx context.Context) string {
return fmt.Sprintf("list must contain at most %d elements", v.max)
}

// MarkdownDescription describes the validation in Markdown formatting.
func (v sizeAtMostValidator) MarkdownDescription(ctx context.Context) string {
return v.Description(ctx)
}

// Validate performs the validation.
func (v sizeAtMostValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) {
elems, ok := validateList(ctx, req, resp)
if !ok {
return
}

if len(elems) > v.max {
resp.Diagnostics.Append(validatordiag.AttributeValueDiagnostic(
req.AttributePath,
v.Description(ctx),
fmt.Sprintf("%d", len(elems)),
))

return
}
}

// SizeAtMost returns an AttributeValidator which ensures that any configured
// attribute value:
//
// - Is a List.
// - Contains at most max elements.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func SizeAtMost(max int) tfsdk.AttributeValidator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go documentation 😉

return sizeAtMostValidator{
max: max,
}
}
93 changes: 93 additions & 0 deletions listvalidator/size_at_most_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package listvalidator

import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

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

type testCase struct {
val attr.Value
max int
expectError bool
}
tests := map[string]testCase{
"not a List": {
val: types.Bool{Value: true},
expectError: true,
},
"List unknown": {
val: types.List{
Unknown: true,
ElemType: types.StringType,
},
expectError: false,
},
"List null": {
val: types.List{
Null: true,
ElemType: types.StringType,
},
expectError: false,
},
"List size less than max": {
val: types.List{
ElemType: types.StringType,
Elems: []attr.Value{
types.String{Value: "first"},
},
},
max: 2,
expectError: false,
},
"List size equal to max": {
val: types.List{
ElemType: types.StringType,
Elems: []attr.Value{
types.String{Value: "first"},
types.String{Value: "second"},
},
},
max: 2,
expectError: false,
},
"List size greater than max": {
val: types.List{
ElemType: types.StringType,
Elems: []attr.Value{
types.String{Value: "first"},
types.String{Value: "second"},
types.String{Value: "third"},
}},
max: 2,
expectError: true,
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
SizeAtMost(test.max).Validate(context.TODO(), request, &response)

if !response.Diagnostics.HasError() && test.expectError {
t.Fatal("expected error, got no error")
}

if response.Diagnostics.HasError() && !test.expectError {
t.Fatalf("got unexpected error: %s", response.Diagnostics)
}
})
}
}
61 changes: 61 additions & 0 deletions listvalidator/size_between.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package listvalidator

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/tfsdk"

"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
)

var _ tfsdk.AttributeValidator = sizeBetweenValidator{}

// sizeBetweenValidator validates that list contains at least min elements
// and at most max elements.
type sizeBetweenValidator struct {
min int
max int
}

// Description describes the validation in plain text formatting.
func (v sizeBetweenValidator) Description(ctx context.Context) string {
return fmt.Sprintf("list must contain at least %d elements and at most %d elements", v.min, v.max)
}

// MarkdownDescription describes the validation in Markdown formatting.
func (v sizeBetweenValidator) MarkdownDescription(ctx context.Context) string {
return v.Description(ctx)
}

// Validate performs the validation.
func (v sizeBetweenValidator) Validate(ctx context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) {
elems, ok := validateList(ctx, req, resp)
if !ok {
return
}

if len(elems) < v.min || len(elems) > v.max {
resp.Diagnostics.Append(validatordiag.AttributeValueDiagnostic(
req.AttributePath,
v.Description(ctx),
fmt.Sprintf("%d", len(elems)),
))

return
}
}

// SizeBetween returns an AttributeValidator which ensures that any configured
// attribute value:
//
// - Is a List.
// - Contains at least min elements and at most max elements.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func SizeBetween(min, max int) tfsdk.AttributeValidator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go documentation 😉

return sizeBetweenValidator{
min: min,
max: max,
}
}
Loading