Skip to content

Commit f27f636

Browse files
authored
Add IsRequired validators to list, set, and object validators for Blocks (#107)
* add `listvalidator` and diag helper * renamed to is_required * add set validator * added object validator * renamed to block * added examples and removed line in comment
1 parent f5056ff commit f27f636

File tree

10 files changed

+443
-0
lines changed

10 files changed

+443
-0
lines changed

helpers/validatordiag/diag.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ import (
99
"github.com/hashicorp/terraform-plugin-framework/path"
1010
)
1111

12+
// InvalidBlockDiagnostic returns an error Diagnostic to be used when a block is invalid
13+
func InvalidBlockDiagnostic(path path.Path, description string) diag.Diagnostic {
14+
return diag.NewAttributeErrorDiagnostic(
15+
path,
16+
"Invalid Block",
17+
fmt.Sprintf("Block %s %s", path, description),
18+
)
19+
}
20+
1221
// InvalidAttributeValueDiagnostic returns an error Diagnostic to be used when an attribute has an invalid value.
1322
func InvalidAttributeValueDiagnostic(path path.Path, description string, value string) diag.Diagnostic {
1423
return diag.NewAttributeErrorDiagnostic(

listvalidator/is_required.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package listvalidator
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
7+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
8+
)
9+
10+
var _ validator.List = isRequiredValidator{}
11+
12+
// isRequiredValidator validates that a list has a configuration value.
13+
type isRequiredValidator struct{}
14+
15+
// Description describes the validation in plain text formatting.
16+
func (v isRequiredValidator) Description(_ context.Context) string {
17+
return "must have a configuration value as the provider has marked it as required"
18+
}
19+
20+
// MarkdownDescription describes the validation in Markdown formatting.
21+
func (v isRequiredValidator) MarkdownDescription(ctx context.Context) string {
22+
return v.Description(ctx)
23+
}
24+
25+
// Validate performs the validation.
26+
func (v isRequiredValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) {
27+
if req.ConfigValue.IsNull() {
28+
resp.Diagnostics.Append(validatordiag.InvalidBlockDiagnostic(
29+
req.Path,
30+
v.Description(ctx),
31+
))
32+
}
33+
}
34+
35+
// IsRequired returns a validator which ensures that any configured list has a value (not null).
36+
//
37+
// This validator is equivalent to the `Required` field on attributes and is only
38+
// practical for use with `schema.ListNestedBlock`
39+
func IsRequired() validator.List {
40+
return isRequiredValidator{}
41+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package listvalidator_test
2+
3+
import (
4+
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
5+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
6+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
7+
)
8+
9+
func ExampleIsRequired() {
10+
// Used within a Schema method of a DataSource, Provider, or Resource
11+
_ = schema.Schema{
12+
Blocks: map[string]schema.Block{
13+
"example_block": schema.ListNestedBlock{
14+
Validators: []validator.List{
15+
// Validate this block has a value (not null).
16+
listvalidator.IsRequired(),
17+
},
18+
NestedObject: schema.NestedBlockObject{
19+
Attributes: map[string]schema.Attribute{
20+
"example_string_attribute": schema.StringAttribute{
21+
Required: true,
22+
},
23+
},
24+
},
25+
},
26+
},
27+
}
28+
}

listvalidator/is_required_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package listvalidator_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
8+
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
"github.com/hashicorp/terraform-plugin-framework/path"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
)
13+
14+
func TestIsRequiredValidator(t *testing.T) {
15+
t.Parallel()
16+
17+
type testCase struct {
18+
val types.List
19+
expectError bool
20+
}
21+
tests := map[string]testCase{
22+
"List null": {
23+
val: types.ListNull(
24+
types.StringType,
25+
),
26+
expectError: true,
27+
},
28+
"List unknown": {
29+
val: types.ListUnknown(
30+
types.StringType,
31+
),
32+
expectError: false,
33+
},
34+
"List empty": {
35+
val: types.ListValueMust(
36+
types.StringType,
37+
[]attr.Value{},
38+
),
39+
expectError: false,
40+
},
41+
"List with elements": {
42+
val: types.ListValueMust(
43+
types.StringType,
44+
[]attr.Value{
45+
types.StringValue("first"),
46+
},
47+
),
48+
expectError: false,
49+
},
50+
}
51+
52+
for name, test := range tests {
53+
name, test := name, test
54+
t.Run(name, func(t *testing.T) {
55+
t.Parallel()
56+
request := validator.ListRequest{
57+
Path: path.Root("test"),
58+
PathExpression: path.MatchRoot("test"),
59+
ConfigValue: test.val,
60+
}
61+
response := validator.ListResponse{}
62+
listvalidator.IsRequired().ValidateList(context.TODO(), request, &response)
63+
64+
if !response.Diagnostics.HasError() && test.expectError {
65+
t.Fatal("expected error, got no error")
66+
}
67+
68+
if response.Diagnostics.HasError() && !test.expectError {
69+
t.Fatalf("got unexpected error: %s", response.Diagnostics)
70+
}
71+
})
72+
}
73+
}

objectvalidator/is_required.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package objectvalidator
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
7+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
8+
)
9+
10+
var _ validator.Object = isRequiredValidator{}
11+
12+
// isRequiredValidator validates that an object has a configuration value.
13+
type isRequiredValidator struct{}
14+
15+
// Description describes the validation in plain text formatting.
16+
func (v isRequiredValidator) Description(_ context.Context) string {
17+
return "must have a configuration value as the provider has marked it as required"
18+
}
19+
20+
// MarkdownDescription describes the validation in Markdown formatting.
21+
func (v isRequiredValidator) MarkdownDescription(ctx context.Context) string {
22+
return v.Description(ctx)
23+
}
24+
25+
// Validate performs the validation.
26+
func (v isRequiredValidator) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
27+
if req.ConfigValue.IsNull() {
28+
resp.Diagnostics.Append(validatordiag.InvalidBlockDiagnostic(
29+
req.Path,
30+
v.Description(ctx),
31+
))
32+
}
33+
}
34+
35+
// IsRequired returns a validator which ensures that any configured object has a value (not null).
36+
//
37+
// This validator is equivalent to the `Required` field on attributes and is only
38+
// practical for use with `schema.SingleNestedBlock`
39+
func IsRequired() validator.Object {
40+
return isRequiredValidator{}
41+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package objectvalidator_test
2+
3+
import (
4+
"github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
5+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
6+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
7+
)
8+
9+
func ExampleIsRequired() {
10+
// Used within a Schema method of a DataSource, Provider, or Resource
11+
_ = schema.Schema{
12+
Blocks: map[string]schema.Block{
13+
"example_block": schema.SingleNestedBlock{
14+
Validators: []validator.Object{
15+
// Validate this block has a value (not null).
16+
objectvalidator.IsRequired(),
17+
},
18+
Attributes: map[string]schema.Attribute{
19+
"example_string_attribute": schema.StringAttribute{
20+
Required: true,
21+
},
22+
},
23+
},
24+
},
25+
}
26+
}

objectvalidator/is_required_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package objectvalidator_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
8+
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
"github.com/hashicorp/terraform-plugin-framework/path"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
)
13+
14+
func TestIsRequiredValidator(t *testing.T) {
15+
t.Parallel()
16+
17+
type testCase struct {
18+
val types.Object
19+
expectError bool
20+
}
21+
tests := map[string]testCase{
22+
"Object null": {
23+
val: types.ObjectNull(
24+
map[string]attr.Type{
25+
"field1": types.StringType,
26+
},
27+
),
28+
expectError: true,
29+
},
30+
"Object unknown": {
31+
val: types.ObjectUnknown(
32+
map[string]attr.Type{
33+
"field1": types.StringType,
34+
},
35+
),
36+
expectError: false,
37+
},
38+
"Object empty": {
39+
val: types.ObjectValueMust(
40+
map[string]attr.Type{
41+
"field1": types.StringType,
42+
},
43+
map[string]attr.Value{
44+
"field1": types.StringNull(),
45+
},
46+
),
47+
expectError: false,
48+
},
49+
"Object with elements": {
50+
val: types.ObjectValueMust(
51+
map[string]attr.Type{
52+
"field1": types.StringType,
53+
},
54+
map[string]attr.Value{
55+
"field1": types.StringValue("value1"),
56+
},
57+
),
58+
expectError: false,
59+
},
60+
}
61+
62+
for name, test := range tests {
63+
name, test := name, test
64+
t.Run(name, func(t *testing.T) {
65+
t.Parallel()
66+
request := validator.ObjectRequest{
67+
Path: path.Root("test"),
68+
PathExpression: path.MatchRoot("test"),
69+
ConfigValue: test.val,
70+
}
71+
response := validator.ObjectResponse{}
72+
objectvalidator.IsRequired().ValidateObject(context.TODO(), request, &response)
73+
74+
if !response.Diagnostics.HasError() && test.expectError {
75+
t.Fatal("expected error, got no error")
76+
}
77+
78+
if response.Diagnostics.HasError() && !test.expectError {
79+
t.Fatalf("got unexpected error: %s", response.Diagnostics)
80+
}
81+
})
82+
}
83+
}

setvalidator/is_required.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package setvalidator
2+
3+
import (
4+
"context"
5+
6+
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
7+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
8+
)
9+
10+
var _ validator.Set = isRequiredValidator{}
11+
12+
// isRequiredValidator validates that a set has a configuration value.
13+
type isRequiredValidator struct{}
14+
15+
// Description describes the validation in plain text formatting.
16+
func (v isRequiredValidator) Description(_ context.Context) string {
17+
return "must have a configuration value as the provider has marked it as required"
18+
}
19+
20+
// MarkdownDescription describes the validation in Markdown formatting.
21+
func (v isRequiredValidator) MarkdownDescription(ctx context.Context) string {
22+
return v.Description(ctx)
23+
}
24+
25+
// Validate performs the validation.
26+
func (v isRequiredValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) {
27+
if req.ConfigValue.IsNull() {
28+
resp.Diagnostics.Append(validatordiag.InvalidBlockDiagnostic(
29+
req.Path,
30+
v.Description(ctx),
31+
))
32+
}
33+
}
34+
35+
// IsRequired returns a validator which ensures that any configured set has a value (not null).
36+
//
37+
// This validator is equivalent to the `Required` field on attributes and is only
38+
// practical for use with `schema.SetNestedBlock`
39+
func IsRequired() validator.Set {
40+
return isRequiredValidator{}
41+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package setvalidator_test
2+
3+
import (
4+
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
5+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
6+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
7+
)
8+
9+
func ExampleIsRequired() {
10+
// Used within a Schema method of a DataSource, Provider, or Resource
11+
_ = schema.Schema{
12+
Blocks: map[string]schema.Block{
13+
"example_block": schema.SetNestedBlock{
14+
Validators: []validator.Set{
15+
// Validate this block has a value (not null).
16+
setvalidator.IsRequired(),
17+
},
18+
NestedObject: schema.NestedBlockObject{
19+
Attributes: map[string]schema.Attribute{
20+
"example_string_attribute": schema.StringAttribute{
21+
Required: true,
22+
},
23+
},
24+
},
25+
},
26+
},
27+
}
28+
}

0 commit comments

Comments
 (0)