-
Notifications
You must be signed in to change notification settings - Fork 13
String RegexMatches
validator
#23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:feature | ||
Introduced `stringvalidator.RegexMatches()` validation function | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package stringvalidator | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"regexp" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag" | ||
"github.com/hashicorp/terraform-plugin-framework/tfsdk" | ||
) | ||
|
||
var _ tfsdk.AttributeValidator = regexMatchesValidator{} | ||
|
||
// regexMatchesValidator validates that a string Attribute's value matches the specified regular expression. | ||
type regexMatchesValidator struct { | ||
regexp *regexp.Regexp | ||
message string | ||
} | ||
|
||
// Description describes the validation in plain text formatting. | ||
func (validator regexMatchesValidator) Description(_ context.Context) string { | ||
if validator.message != "" { | ||
return validator.message | ||
} | ||
return fmt.Sprintf("value must match regular expression '%s'", validator.regexp) | ||
} | ||
|
||
// MarkdownDescription describes the validation in Markdown formatting. | ||
func (validator regexMatchesValidator) MarkdownDescription(ctx context.Context) string { | ||
return validator.Description(ctx) | ||
} | ||
|
||
// Validate performs the validation. | ||
func (validator regexMatchesValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) { | ||
s, ok := validateString(ctx, request, response) | ||
ewbankkit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if !ok { | ||
return | ||
} | ||
|
||
if ok := validator.regexp.MatchString(s); !ok { | ||
response.Diagnostics.Append(validatordiag.AttributeValueMatchesDiagnostic( | ||
request.AttributePath, | ||
validator.Description(ctx), | ||
s, | ||
)) | ||
} | ||
} | ||
|
||
// RegexMatches returns an AttributeValidator which ensures that any configured | ||
// attribute value: | ||
// | ||
// - Is a string. | ||
// - Matches the given regular expression https://github.com/google/re2/wiki/Syntax. | ||
// | ||
// Null (unconfigured) and unknown (known after apply) values are skipped. | ||
// Optionally an error message can be provided to return something friendlier | ||
// than "value must match regular expression 'regexp'". | ||
func RegexMatches(regexp *regexp.Regexp, message string) tfsdk.AttributeValidator { | ||
return regexMatchesValidator{ | ||
regexp: regexp, | ||
message: message, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package stringvalidator | ||
|
||
import ( | ||
"context" | ||
"regexp" | ||
"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 TestRegexMatchesValidator(t *testing.T) { | ||
t.Parallel() | ||
|
||
type testCase struct { | ||
val attr.Value | ||
regexp *regexp.Regexp | ||
expectError bool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not for this PR, but it'd be great to switch at least this unit test to checking contents to ensure a given message flows to the returned error diagnostic. |
||
} | ||
tests := map[string]testCase{ | ||
"not a String": { | ||
val: types.Bool{Value: true}, | ||
expectError: true, | ||
}, | ||
"unknown String": { | ||
val: types.String{Unknown: true}, | ||
regexp: regexp.MustCompile(`^o[j-l]?$`), | ||
}, | ||
"null String": { | ||
val: types.String{Null: true}, | ||
regexp: regexp.MustCompile(`^o[j-l]?$`), | ||
}, | ||
"valid String": { | ||
val: types.String{Value: "ok"}, | ||
regexp: regexp.MustCompile(`^o[j-l]?$`), | ||
}, | ||
"invalid String": { | ||
val: types.String{Value: "not ok"}, | ||
regexp: regexp.MustCompile(`^o[j-l]?$`), | ||
expectError: true, | ||
}, | ||
} | ||
|
||
for name, test := range tests { | ||
name, test := name, test | ||
t.Run(name, func(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not for this PR, but I just realized that we can add |
||
request := tfsdk.ValidateAttributeRequest{ | ||
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"), | ||
AttributeConfig: test.val, | ||
} | ||
response := tfsdk.ValidateAttributeResponse{} | ||
RegexMatches(test.regexp, "").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) | ||
} | ||
}) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.