Skip to content

Commit 7d19faa

Browse files
authored
Add NoNullValues validators (#246)
* listvalidator: add `NoNullValues` validator This validator will iterate over elements in the list, returning an error diagnostic if any null elements are detected. * setvalidator: add `NoNullValues` validator This validator will iterate over elements in the set, returning an error diagnostic if any null elements are detected. * mapvalidator: add `NoNullValues` validator This validator will iterate over elements in the map, returning an error diagnostic if any null elements are detected. * Documentation adjustments * changelogs ---------
1 parent e16013a commit 7d19faa

12 files changed

+702
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: FEATURES
2+
body: 'listvalidator: Added `NoNullValues` validator'
3+
time: 2024-12-12T14:05:48.064529-05:00
4+
custom:
5+
Issue: "245"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: FEATURES
2+
body: 'mapvalidator: Added `NoNullValues` validator'
3+
time: 2024-12-12T14:06:13.229216-05:00
4+
custom:
5+
Issue: "245"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: FEATURES
2+
body: 'setvalidator: Added `NoNullValues` validator'
3+
time: 2024-12-12T14:06:24.967598-05:00
4+
custom:
5+
Issue: "245"

listvalidator/no_null_values.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package listvalidator
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/function"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
)
12+
13+
var _ validator.List = noNullValuesValidator{}
14+
var _ function.ListParameterValidator = noNullValuesValidator{}
15+
16+
type noNullValuesValidator struct{}
17+
18+
func (v noNullValuesValidator) Description(_ context.Context) string {
19+
return "All values in the list must be configured"
20+
}
21+
22+
func (v noNullValuesValidator) MarkdownDescription(ctx context.Context) string {
23+
return v.Description(ctx)
24+
}
25+
26+
func (v noNullValuesValidator) ValidateList(_ context.Context, req validator.ListRequest, resp *validator.ListResponse) {
27+
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
28+
return
29+
}
30+
31+
elements := req.ConfigValue.Elements()
32+
33+
for _, e := range elements {
34+
// Only evaluate known values for null
35+
if e.IsUnknown() {
36+
continue
37+
}
38+
39+
if e.IsNull() {
40+
resp.Diagnostics.AddAttributeError(
41+
req.Path,
42+
"Null List Value",
43+
"This attribute contains a null value.",
44+
)
45+
}
46+
}
47+
}
48+
49+
func (v noNullValuesValidator) ValidateParameterList(ctx context.Context, req function.ListParameterValidatorRequest, resp *function.ListParameterValidatorResponse) {
50+
if req.Value.IsNull() || req.Value.IsUnknown() {
51+
return
52+
}
53+
54+
elements := req.Value.Elements()
55+
56+
for _, e := range elements {
57+
// Only evaluate known values for null
58+
if e.IsUnknown() {
59+
continue
60+
}
61+
62+
if e.IsNull() {
63+
resp.Error = function.ConcatFuncErrors(
64+
resp.Error,
65+
function.NewArgumentFuncError(
66+
req.ArgumentPosition,
67+
"Null List Value: This attribute contains a null value.",
68+
),
69+
)
70+
}
71+
}
72+
}
73+
74+
// NoNullValues returns a validator which ensures that any configured list
75+
// only contains non-null values.
76+
func NoNullValues() noNullValuesValidator {
77+
return noNullValuesValidator{}
78+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package listvalidator_test
5+
6+
import (
7+
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
8+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
9+
"github.com/hashicorp/terraform-plugin-framework/function"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
)
13+
14+
func ExampleNoNullValues() {
15+
// Used within a Schema method of a DataSource, Provider, or Resource
16+
_ = schema.Schema{
17+
Attributes: map[string]schema.Attribute{
18+
"example_attr": schema.ListAttribute{
19+
ElementType: types.StringType,
20+
Required: true,
21+
Validators: []validator.List{
22+
// Validate this list must contain no null values.
23+
listvalidator.NoNullValues(),
24+
},
25+
},
26+
},
27+
}
28+
}
29+
30+
func ExampleNoNullValues_function() {
31+
_ = function.Definition{
32+
Parameters: []function.Parameter{
33+
function.ListParameter{
34+
Name: "example_param",
35+
Validators: []function.ListParameterValidator{
36+
// Validate this list must contain no null values.
37+
listvalidator.NoNullValues(),
38+
},
39+
},
40+
},
41+
}
42+
}

listvalidator/no_null_values_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package listvalidator_test
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"testing"
10+
11+
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
12+
"github.com/hashicorp/terraform-plugin-framework/attr"
13+
"github.com/hashicorp/terraform-plugin-framework/function"
14+
"github.com/hashicorp/terraform-plugin-framework/path"
15+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
16+
"github.com/hashicorp/terraform-plugin-framework/types"
17+
)
18+
19+
func TestNoNullValuesValidator(t *testing.T) {
20+
t.Parallel()
21+
22+
type testCase struct {
23+
val types.List
24+
expectError bool
25+
}
26+
tests := map[string]testCase{
27+
"List unknown": {
28+
val: types.ListUnknown(
29+
types.StringType,
30+
),
31+
expectError: false,
32+
},
33+
"List null": {
34+
val: types.ListNull(
35+
types.StringType,
36+
),
37+
expectError: false,
38+
},
39+
"No null values": {
40+
val: types.ListValueMust(
41+
types.StringType,
42+
[]attr.Value{
43+
types.StringValue("first"),
44+
types.StringValue("second"),
45+
},
46+
),
47+
expectError: false,
48+
},
49+
"Unknown value": {
50+
val: types.ListValueMust(
51+
types.StringType,
52+
[]attr.Value{
53+
types.StringValue("first"),
54+
types.StringUnknown(),
55+
},
56+
),
57+
expectError: false,
58+
},
59+
"Null value": {
60+
val: types.ListValueMust(
61+
types.StringType,
62+
[]attr.Value{
63+
types.StringValue("first"),
64+
types.StringNull(),
65+
},
66+
),
67+
expectError: true,
68+
},
69+
}
70+
71+
for name, test := range tests {
72+
t.Run(fmt.Sprintf("ValidateList - %s", name), func(t *testing.T) {
73+
t.Parallel()
74+
request := validator.ListRequest{
75+
Path: path.Root("test"),
76+
PathExpression: path.MatchRoot("test"),
77+
ConfigValue: test.val,
78+
}
79+
response := validator.ListResponse{}
80+
listvalidator.NoNullValues().ValidateList(context.TODO(), request, &response)
81+
82+
if !response.Diagnostics.HasError() && test.expectError {
83+
t.Fatal("expected error, got no error")
84+
}
85+
86+
if response.Diagnostics.HasError() && !test.expectError {
87+
t.Fatalf("got unexpected error: %s", response.Diagnostics)
88+
}
89+
})
90+
91+
t.Run(fmt.Sprintf("ValidateParameterList - %s", name), func(t *testing.T) {
92+
t.Parallel()
93+
request := function.ListParameterValidatorRequest{
94+
ArgumentPosition: 0,
95+
Value: test.val,
96+
}
97+
response := function.ListParameterValidatorResponse{}
98+
listvalidator.NoNullValues().ValidateParameterList(context.TODO(), request, &response)
99+
100+
if response.Error == nil && test.expectError {
101+
t.Fatal("expected error, got no error")
102+
}
103+
104+
if response.Error != nil && !test.expectError {
105+
t.Fatalf("got unexpected error: %s", response.Error)
106+
}
107+
})
108+
}
109+
}

mapvalidator/no_null_values.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package mapvalidator
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/function"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
)
12+
13+
var _ validator.Map = noNullValuesValidator{}
14+
var _ function.MapParameterValidator = noNullValuesValidator{}
15+
16+
type noNullValuesValidator struct{}
17+
18+
func (v noNullValuesValidator) Description(_ context.Context) string {
19+
return "All values in the map must be configured"
20+
}
21+
22+
func (v noNullValuesValidator) MarkdownDescription(ctx context.Context) string {
23+
return v.Description(ctx)
24+
}
25+
26+
func (v noNullValuesValidator) ValidateMap(_ context.Context, req validator.MapRequest, resp *validator.MapResponse) {
27+
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
28+
return
29+
}
30+
31+
elements := req.ConfigValue.Elements()
32+
33+
for _, e := range elements {
34+
// Only evaluate known values for null
35+
if e.IsUnknown() {
36+
continue
37+
}
38+
39+
if e.IsNull() {
40+
resp.Diagnostics.AddAttributeError(
41+
req.Path,
42+
"Null Map Value",
43+
"This attribute contains a null value.",
44+
)
45+
}
46+
}
47+
}
48+
49+
func (v noNullValuesValidator) ValidateParameterMap(ctx context.Context, req function.MapParameterValidatorRequest, resp *function.MapParameterValidatorResponse) {
50+
if req.Value.IsNull() || req.Value.IsUnknown() {
51+
return
52+
}
53+
54+
elements := req.Value.Elements()
55+
56+
for _, e := range elements {
57+
// Only evaluate known values for null
58+
if e.IsUnknown() {
59+
continue
60+
}
61+
62+
if e.IsNull() {
63+
resp.Error = function.ConcatFuncErrors(
64+
resp.Error,
65+
function.NewArgumentFuncError(
66+
req.ArgumentPosition,
67+
"Null Map Value: This attribute contains a null value.",
68+
),
69+
)
70+
}
71+
}
72+
}
73+
74+
// NoNullValues returns a validator which ensures that any configured map
75+
// only contains non-null values.
76+
func NoNullValues() noNullValuesValidator {
77+
return noNullValuesValidator{}
78+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package mapvalidator_test
5+
6+
import (
7+
"github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator"
8+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
9+
"github.com/hashicorp/terraform-plugin-framework/function"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
)
13+
14+
func ExampleNoNullValues() {
15+
// Used within a Schema method of a DataSource, Provider, or Resource
16+
_ = schema.Schema{
17+
Attributes: map[string]schema.Attribute{
18+
"example_attr": schema.MapAttribute{
19+
ElementType: types.StringType,
20+
Required: true,
21+
Validators: []validator.Map{
22+
// Validate this map must contain no null values.
23+
mapvalidator.NoNullValues(),
24+
},
25+
},
26+
},
27+
}
28+
}
29+
30+
func ExampleNoNullValues_function() {
31+
_ = function.Definition{
32+
Parameters: []function.Parameter{
33+
function.MapParameter{
34+
Name: "example_param",
35+
Validators: []function.MapParameterValidator{
36+
// Validate this map must contain no null values.
37+
mapvalidator.NoNullValues(),
38+
},
39+
},
40+
},
41+
}
42+
}

0 commit comments

Comments
 (0)