Skip to content

Commit 63b234d

Browse files
committed
Deprecate ValidateFunc and SchemaValidateFunc
Ensure both ValidateFunc and ValidateDiagFunc can't be set truncate TypeSet attribute path and explain why
1 parent 90ac5a8 commit 63b234d

File tree

2 files changed

+52
-14
lines changed

2 files changed

+52
-14
lines changed

helper/schema/schema.go

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,17 @@ type Schema struct {
223223
//
224224
// ValidateFunc is honored only when the schema's Type is set to TypeInt,
225225
// TypeFloat, TypeString, TypeBool, or TypeMap. It is ignored for all other types.
226-
ValidateFunc SchemaValidateFunc
226+
//
227+
// Deprecated: please use ValidateDiagFunc
228+
ValidateFunc SchemaValidateFunc
229+
230+
// ValidateDiagFunc allows individual fields to define arbitrary validation
231+
// logic. It is yielded the provided config value as an interface{} that is
232+
// guaranteed to be of the proper Schema type, and it can yield diagnostics
233+
// based on inspection of that value.
234+
//
235+
// ValidateDiagFunc is honored only when the schema's Type is set to TypeInt,
236+
// TypeFloat, TypeString, TypeBool, or TypeMap. It is ignored for all other types.
227237
ValidateDiagFunc SchemaValidateDiagFunc
228238

229239
// Sensitive ensures that the attribute's value does not get displayed in
@@ -293,7 +303,12 @@ type SchemaStateFunc func(interface{}) string
293303

294304
// SchemaValidateFunc is a function used to validate a single field in the
295305
// schema.
306+
//
307+
// Deprecated: please use SchemaValidateDiagFunc
296308
type SchemaValidateFunc func(interface{}, string) ([]string, []error)
309+
310+
// SchemaValidateDiagFunc is a function used to validate a single field in the
311+
// schema and has Diagnostic support.
297312
type SchemaValidateDiagFunc func(interface{}, string) diag.Diagnostics
298313

299314
func (s *Schema) GoString() string {
@@ -842,6 +857,10 @@ func (m schemaMap) internalValidate(topSchemaMap schemaMap, attrsOnly bool) erro
842857
}
843858
}
844859

860+
if v.ValidateFunc != nil && v.ValidateDiagFunc != nil {
861+
return fmt.Errorf("%s: ValidateFunc and ValidateDiagFunc cannot both be set", k)
862+
}
863+
845864
if v.Deprecated == "" && v.Removed == "" {
846865
if !isValidFieldName(k) {
847866
return fmt.Errorf("%s: Field name may only contain lowercase alphanumeric characters & underscores.", k)
@@ -1606,8 +1625,7 @@ func (m schemaMap) validateList(
16061625
raw interface{},
16071626
schema *Schema,
16081627
c *terraform.ResourceConfig,
1609-
path cty.Path,
1610-
isTypeSet bool) diag.Diagnostics {
1628+
path cty.Path) diag.Diagnostics {
16111629

16121630
var diags diag.Diagnostics
16131631

@@ -1672,19 +1690,15 @@ func (m schemaMap) validateList(
16721690
for i, raw := range raws {
16731691
key := fmt.Sprintf("%s.%d", k, i)
16741692

1675-
// TODO: figure out cty.Path for TypeSet
1676-
// if isTypeSet {
1677-
// } else {
1678-
// }
1679-
p := append(path.Copy(), cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
1680-
16811693
// Reify the key value from the ResourceConfig.
16821694
// If the list was computed we have all raw values, but some of these
16831695
// may be known in the config, and aren't individually marked as Computed.
16841696
if r, ok := c.Get(key); ok {
16851697
raw = r
16861698
}
16871699

1700+
p := append(path.Copy(), cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
1701+
16881702
switch t := schema.Elem.(type) {
16891703
case *Resource:
16901704
// This is a sub-resource
@@ -1899,7 +1913,7 @@ func (m schemaMap) validateObject(
18991913
if k != "" {
19001914
key = fmt.Sprintf("%s.%s", k, subK)
19011915
}
1902-
diags = diags.Append(m.validate(key, s, c, append(path, cty.GetAttrStep{Name: subK})))
1916+
diags = diags.Append(m.validate(key, s, c, append(path.Copy(), cty.GetAttrStep{Name: subK})))
19031917
}
19041918

19051919
// Detect any extra/unknown keys and report those as errors.
@@ -1912,7 +1926,7 @@ func (m schemaMap) validateObject(
19121926
diags = diags.Append(&diag.Diagnostic{
19131927
Severity: diag.Error,
19141928
Summary: "Invalid or unknown key",
1915-
AttributePath: append(path, cty.GetAttrStep{Name: subk}),
1929+
AttributePath: append(path.Copy(), cty.GetAttrStep{Name: subk}),
19161930
})
19171931
}
19181932
}
@@ -2026,12 +2040,19 @@ func (m schemaMap) validateType(
20262040
path cty.Path) diag.Diagnostics {
20272041

20282042
var diags diag.Diagnostics
2029-
20302043
switch schema.Type {
20312044
case TypeList:
2032-
diags = m.validateList(k, raw, schema, c, path, false)
2045+
diags = m.validateList(k, raw, schema, c, path)
20332046
case TypeSet:
2034-
diags = m.validateList(k, raw, schema, c, path, true)
2047+
// indexing into sets is not representable in the current protocol
2048+
// best we can do is associate the path up to this attribute
2049+
// we may be able to try and enhance the rendered text with the rest of
2050+
// the path ie "error in TypeSet element { 'foo' : 'bar' }",
2051+
// but it will not be a perfect UX
2052+
diags = m.validateList(k, raw, schema, c, path)
2053+
for _, d := range diags {
2054+
d.AttributePath = path
2055+
}
20352056
case TypeMap:
20362057
diags = m.validateMap(k, raw, schema, c, path)
20372058
default:

helper/schema/schema_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313
"testing"
1414

15+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1516
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/hashcode"
1617
"github.com/hashicorp/terraform-plugin-sdk/v2/internal/configs/hcl2shim"
1718
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
@@ -3654,6 +3655,22 @@ func TestSchemaMap_InternalValidate(t *testing.T) {
36543655
},
36553656
true,
36563657
},
3658+
3659+
"ValidateFunc and ValidateDiagFunc cannot both be set": {
3660+
map[string]*Schema{
3661+
"foo": {
3662+
Type: TypeInt,
3663+
Required: true,
3664+
ValidateFunc: func(interface{}, string) ([]string, []error) {
3665+
return nil, nil
3666+
},
3667+
ValidateDiagFunc: func(interface{}, string) diag.Diagnostics {
3668+
return nil
3669+
},
3670+
},
3671+
},
3672+
true,
3673+
},
36573674
}
36583675

36593676
for tn, tc := range cases {

0 commit comments

Comments
 (0)