Skip to content

Commit a5a5f3f

Browse files
author
Pier-Luc Caron-St-Pierre
committed
Migration from TypeWithValidate to ValidateableAttribute
1 parent 4214933 commit a5a5f3f

File tree

9 files changed

+610
-125
lines changed

9 files changed

+610
-125
lines changed

internal/provider/customtypes/common.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,8 @@ import (
88

99
"github.com/hashicorp/terraform-plugin-framework/attr"
1010
"github.com/hashicorp/terraform-plugin-framework/diag"
11-
"github.com/hashicorp/terraform-plugin-framework/path"
1211
)
1312

14-
func newInvalidTerraformValueError(valuePath path.Path, err error) diag.Diagnostic {
15-
return diag.NewAttributeErrorDiagnostic(
16-
valuePath,
17-
"Invalid Terraform Value",
18-
"An unexpected error occurred while attempting to convert a Terraform value to a string. "+
19-
"This generally is an issue with the provider schema implementation. "+
20-
"Please contact the provider developers.\n\n"+
21-
"Path: "+valuePath.String()+"\n"+
22-
"Error: "+err.Error(),
23-
)
24-
}
25-
2613
func newSemanticEqualityCheckTypeError[V attr.Value](expected V, got V) diag.Diagnostic {
2714
return diag.NewErrorDiagnostic(
2815
"Semantic Equality Check Type Error",

internal/provider/customtypes/packer_fingerprint.go

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ import (
1111
"github.com/hashicorp/terraform-plugin-framework/attr"
1212
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
1313
"github.com/hashicorp/terraform-plugin-framework/diag"
14-
"github.com/hashicorp/terraform-plugin-framework/path"
14+
"github.com/hashicorp/terraform-plugin-framework/function"
1515
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
1616
"github.com/hashicorp/terraform-plugin-go/tftypes"
1717
)
1818

19-
// PackerFingerprintType is a custom type for HCP Packer Fingerprints
19+
var (
20+
_ basetypes.StringTypable = &PackerFingerprintType{}
21+
)
22+
2023
type PackerFingerprintType struct {
2124
basetypes.StringType
2225
}
2326

24-
var _ basetypes.StringTypable = &PackerFingerprintType{}
25-
var _ xattr.TypeWithValidate = PackerFingerprintType{}
26-
2727
func (t PackerFingerprintType) String() string {
2828
return "PackerFingerprintType"
2929
}
@@ -66,13 +66,12 @@ func (t PackerFingerprintType) ValueFromTerraform(ctx context.Context, in tftype
6666
return fingerprintValue, nil
6767
}
6868

69-
func NewPackerFingerprintValue(value string) PackerFingerprintValue {
70-
return PackerFingerprintValue{
71-
StringValue: basetypes.NewStringValue(value),
72-
}
73-
}
69+
var (
70+
_ basetypes.StringValuable = &PackerFingerprintValue{}
71+
_ xattr.ValidateableAttribute = &PackerFingerprintValue{}
72+
_ function.ValidateableParameter = &PackerFingerprintValue{}
73+
)
7474

75-
// PackerFingerprintValue is a custom value used to validate that a string is an HCP Packer Fingerprint
7675
type PackerFingerprintValue struct {
7776
basetypes.StringValue
7877
}
@@ -92,38 +91,66 @@ func (v PackerFingerprintValue) Equal(o attr.Value) bool {
9291
return v.StringValue.Equal(other.StringValue)
9392
}
9493

95-
// Validate checks that the value is a valid PackerFingerprint, if it is known and not null.
96-
func (t PackerFingerprintType) Validate(ctx context.Context, value tftypes.Value, valuePath path.Path) diag.Diagnostics {
97-
if value.IsNull() || !value.IsKnown() {
98-
return nil
94+
func (v PackerFingerprintValue) ValidateAttribute(ctx context.Context, req xattr.ValidateAttributeRequest, resp *xattr.ValidateAttributeResponse) {
95+
if v.IsUnknown() || v.IsNull() {
96+
return
97+
}
98+
99+
valueString := v.ValueString()
100+
101+
if len(valueString) < 1 || len(valueString) > 40 {
102+
resp.Diagnostics.Append(
103+
diag.NewAttributeErrorDiagnostic(
104+
req.Path,
105+
"invalid format for an HCP Packer Fingerprint",
106+
"must be between 1 and 40 characters long, inclusive",
107+
),
108+
)
109+
return
99110
}
100111

101-
var diags diag.Diagnostics
102-
var valueString string
112+
if !regexp.MustCompile(`^[a-zA-Z0-9_\-\.\"]+$`).MatchString(valueString) {
113+
resp.Diagnostics.Append(
114+
diag.NewAttributeErrorDiagnostic(
115+
req.Path,
116+
"invalid format for an HCP Packer Fingerprint",
117+
// TODO: The regex also allows double quotes, and does not check the first or last characters.
118+
// This is because the v1 HCP Packer API allowed quotes. Once that API is deprecated,
119+
// and there are no more offending versions, we can add the strict validation.
120+
"must contain only alphanumeric characters, underscores, dashes, and periods",
121+
),
122+
)
123+
}
124+
}
103125

104-
if err := value.As(&valueString); err != nil {
105-
diags.Append(newInvalidTerraformValueError(valuePath, err))
106-
return diags
126+
func (v PackerFingerprintValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) {
127+
if v.IsUnknown() || v.IsNull() {
128+
return
107129
}
108130

131+
valueString := v.ValueString()
132+
109133
if len(valueString) < 1 || len(valueString) > 40 {
110-
diags.AddAttributeError(
111-
valuePath,
112-
"invalid format for an HCP Packer Fingerprint",
113-
"must be between 1 and 40 characters long, inclusive",
134+
resp.Error = function.NewArgumentFuncError(
135+
req.Position,
136+
"HCP Packer Fingerprint must be between 1 and 40 characters long, inclusive",
114137
)
138+
return
115139
}
116140

117141
if !regexp.MustCompile(`^[a-zA-Z0-9_\-\.\"]+$`).MatchString(valueString) {
118-
diags.AddAttributeError(
119-
valuePath,
120-
"invalid format for an HCP Packer Fingerprint",
142+
resp.Error = function.NewArgumentFuncError(
143+
req.Position,
121144
// TODO: The regex also allows double quotes, and does not check the first or last characters.
122145
// This is because the v1 HCP Packer API allowed quotes. Once that API is deprecated,
123146
// and there are no more offending versions, we can add the strict validation.
124-
"must contain only alphanumeric characters, underscores, dashes, and periods",
147+
"HCP Packer Fingerprint must contain only alphanumeric characters, underscores, dashes, and periods",
125148
)
126149
}
150+
}
127151

128-
return diags
152+
func NewPackerFingerprintValue(value string) PackerFingerprintValue {
153+
return PackerFingerprintValue{
154+
StringValue: basetypes.NewStringValue(value),
155+
}
129156
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package customtypes
2+
3+
import (
4+
"context"
5+
"strings"
6+
"testing"
7+
8+
"github.com/google/go-cmp/cmp"
9+
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
10+
"github.com/hashicorp/terraform-plugin-framework/diag"
11+
"github.com/hashicorp/terraform-plugin-framework/function"
12+
"github.com/hashicorp/terraform-plugin-framework/path"
13+
)
14+
15+
func TestPackerFingerprintValidateAttribute(t *testing.T) {
16+
testCases := map[string]struct {
17+
PackerFingerprint PackerFingerprintValue
18+
expectedDiags diag.Diagnostics
19+
}{
20+
"empty": {
21+
PackerFingerprint: PackerFingerprintValue{},
22+
},
23+
"valid": {
24+
PackerFingerprint: NewPackerFingerprintValue("01JDAW2CWCX1JHXE54XA7FFXHS"),
25+
},
26+
"too short": {
27+
PackerFingerprint: NewPackerFingerprintValue(""),
28+
expectedDiags: diag.Diagnostics{
29+
diag.NewAttributeErrorDiagnostic(
30+
path.Root("test"),
31+
"invalid format for an HCP Packer Fingerprint",
32+
"must be between 1 and 40 characters long, inclusive",
33+
),
34+
},
35+
},
36+
"too long": {
37+
PackerFingerprint: NewPackerFingerprintValue(strings.Repeat("A", 41)),
38+
expectedDiags: diag.Diagnostics{
39+
diag.NewAttributeErrorDiagnostic(
40+
path.Root("test"),
41+
"invalid format for an HCP Packer Fingerprint",
42+
"must be between 1 and 40 characters long, inclusive",
43+
),
44+
},
45+
},
46+
"invalid string": {
47+
PackerFingerprint: NewPackerFingerprintValue("$invalid$"),
48+
expectedDiags: diag.Diagnostics{
49+
diag.NewAttributeErrorDiagnostic(
50+
path.Root("test"),
51+
"invalid format for an HCP Packer Fingerprint",
52+
"must contain only alphanumeric characters, underscores, dashes, and periods",
53+
),
54+
},
55+
},
56+
}
57+
for name, testCase := range testCases {
58+
t.Run(name, func(t *testing.T) {
59+
resp := xattr.ValidateAttributeResponse{}
60+
61+
testCase.PackerFingerprint.ValidateAttribute(
62+
context.Background(),
63+
xattr.ValidateAttributeRequest{
64+
Path: path.Root("test"),
65+
},
66+
&resp,
67+
)
68+
69+
if diff := cmp.Diff(resp.Diagnostics, testCase.expectedDiags); diff != "" {
70+
t.Errorf("Unexpected diagnostics (-got, +expected): %s", diff)
71+
}
72+
})
73+
}
74+
}
75+
76+
func TestPackerFingerprintValidateParameter(t *testing.T) {
77+
testCases := map[string]struct {
78+
PackerFingerprint PackerFingerprintValue
79+
expectedFuncErr *function.FuncError
80+
}{
81+
"empty": {
82+
PackerFingerprint: PackerFingerprintValue{},
83+
},
84+
"valid": {
85+
PackerFingerprint: NewPackerFingerprintValue("01JDAW2CWCX1JHXE54XA7FFXHS"),
86+
},
87+
"too short": {
88+
PackerFingerprint: NewPackerFingerprintValue(""),
89+
expectedFuncErr: function.NewArgumentFuncError(
90+
0,
91+
"HCP Packer Fingerprint must be between 1 and 40 characters long, inclusive",
92+
),
93+
},
94+
"too long": {
95+
PackerFingerprint: NewPackerFingerprintValue(strings.Repeat("A", 41)),
96+
expectedFuncErr: function.NewArgumentFuncError(
97+
0,
98+
"HCP Packer Fingerprint must be between 1 and 40 characters long, inclusive",
99+
),
100+
},
101+
"invalid string": {
102+
PackerFingerprint: NewPackerFingerprintValue("$invalid$"),
103+
expectedFuncErr: function.NewArgumentFuncError(
104+
0,
105+
"HCP Packer Fingerprint must contain only alphanumeric characters, underscores, dashes, and periods",
106+
),
107+
},
108+
}
109+
for name, testCase := range testCases {
110+
t.Run(name, func(t *testing.T) {
111+
resp := function.ValidateParameterResponse{}
112+
113+
testCase.PackerFingerprint.ValidateParameter(
114+
context.Background(),
115+
function.ValidateParameterRequest{
116+
Position: int64(0),
117+
},
118+
&resp,
119+
)
120+
121+
if diff := cmp.Diff(resp.Error, testCase.expectedFuncErr); diff != "" {
122+
t.Errorf("Unexpected diagnostics (-got, +expected): %s", diff)
123+
}
124+
})
125+
}
126+
}

internal/provider/customtypes/slug.go

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ import (
1010
"github.com/hashicorp/terraform-plugin-framework/attr"
1111
"github.com/hashicorp/terraform-plugin-framework/attr/xattr"
1212
"github.com/hashicorp/terraform-plugin-framework/diag"
13-
"github.com/hashicorp/terraform-plugin-framework/path"
13+
"github.com/hashicorp/terraform-plugin-framework/function"
1414
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
1515
"github.com/hashicorp/terraform-plugin-go/tftypes"
1616
"github.com/hashicorp/terraform-provider-hcp/internal/input"
1717
)
1818

19-
// SlugType is a custom type for Slugs
19+
var (
20+
_ basetypes.StringTypable = &SlugType{}
21+
)
22+
2023
type SlugType struct {
2124
basetypes.StringType
2225
}
2326

24-
var _ basetypes.StringTypable = &SlugType{}
25-
var _ xattr.TypeWithValidate = SlugType{}
26-
2727
func (t SlugType) String() string {
2828
return "SlugType"
2929
}
@@ -66,19 +66,16 @@ func (t SlugType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (att
6666
return slugValue, nil
6767
}
6868

69-
func NewSlugValue(value string) SlugValue {
70-
return SlugValue{
71-
StringValue: basetypes.NewStringValue(value),
72-
}
73-
}
69+
var (
70+
_ basetypes.StringValuable = &SlugValue{}
71+
_ xattr.ValidateableAttribute = &SlugValue{}
72+
_ function.ValidateableParameter = &SlugValue{}
73+
)
7474

75-
// SlugValue is a custom value used to validate that a string is a Slug
7675
type SlugValue struct {
7776
basetypes.StringValue
7877
}
7978

80-
var _ basetypes.StringValuable = SlugValue{}
81-
8279
func (v SlugValue) Type(context.Context) attr.Type {
8380
return SlugType{}
8481
}
@@ -92,28 +89,37 @@ func (v SlugValue) Equal(o attr.Value) bool {
9289
return v.StringValue.Equal(other.StringValue)
9390
}
9491

95-
// Validate checks that the value is a valid Slug, if it is known and not null.
96-
func (t SlugType) Validate(ctx context.Context, value tftypes.Value, valuePath path.Path) diag.Diagnostics {
97-
if value.IsNull() || !value.IsKnown() {
98-
return nil
92+
func (v SlugValue) ValidateAttribute(ctx context.Context, req xattr.ValidateAttributeRequest, resp *xattr.ValidateAttributeResponse) {
93+
if v.IsNull() || v.IsUnknown() {
94+
return
9995
}
10096

101-
var diags diag.Diagnostics
102-
var valueString string
97+
if !input.IsSlug(v.ValueString()) {
98+
resp.Diagnostics.Append(
99+
diag.NewAttributeErrorDiagnostic(
100+
req.Path,
101+
"expected a valid Slug",
102+
"slugs must be of length 3-36 and contain only alphanumeric characters and hyphens",
103+
),
104+
)
105+
}
106+
}
103107

104-
if err := value.As(&valueString); err != nil {
105-
diags.Append(newInvalidTerraformValueError(valuePath, err))
106-
return diags
108+
func (v SlugValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) {
109+
if v.IsUnknown() || v.IsNull() {
110+
return
107111
}
108112

109-
if !input.IsSlug(valueString) {
110-
diags.AddAttributeError(
111-
valuePath,
112-
"expected a valid Slug",
113+
if !input.IsSlug(v.ValueString()) {
114+
resp.Error = function.NewArgumentFuncError(
115+
req.Position,
113116
"slugs must be of length 3-36 and contain only alphanumeric characters and hyphens",
114117
)
115-
return diags
116118
}
119+
}
117120

118-
return diags
121+
func NewSlugValue(value string) SlugValue {
122+
return SlugValue{
123+
StringValue: basetypes.NewStringValue(value),
124+
}
119125
}

0 commit comments

Comments
 (0)