Skip to content

Commit 76f33c8

Browse files
committed
Add validator for https urls to framework code
1 parent b8e10c9 commit 76f33c8

File tree

2 files changed

+153
-0
lines changed

2 files changed

+153
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package validation
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
8+
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
9+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
10+
)
11+
12+
// isURLWithHTTPSorEmptyStringValidator validates that a string Attribute is an https URL or is an empty string.
13+
type isURLWithHTTPSorEmptyStringValidator struct {
14+
}
15+
16+
// Description describes the validation in plain text formatting.
17+
func (v isURLWithHTTPSorEmptyStringValidator) Description(_ context.Context) string {
18+
return fmt.Sprintf("string must be a valid https URL or empty")
19+
}
20+
21+
// MarkdownDescription describes the validation in Markdown formatting.
22+
func (v isURLWithHTTPSorEmptyStringValidator) MarkdownDescription(ctx context.Context) string {
23+
return v.Description(ctx)
24+
}
25+
26+
// Validate performs the validation.
27+
func (v isURLWithHTTPSorEmptyStringValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) {
28+
if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() {
29+
return
30+
}
31+
32+
urlString := request.ConfigValue.ValueString()
33+
34+
if urlString == "" {
35+
return
36+
}
37+
38+
parsedURL, err := url.Parse(urlString)
39+
if err != nil {
40+
response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
41+
request.Path,
42+
fmt.Sprintf("invalid URL: %+v", err),
43+
urlString,
44+
))
45+
} else if parsedURL.Host == "" {
46+
response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
47+
request.Path,
48+
fmt.Sprintf("invalid URL, missing host"),
49+
urlString,
50+
))
51+
} else if parsedURL.Scheme != "https" {
52+
response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
53+
request.Path,
54+
fmt.Sprintf("invalid URL, expected protocol scheme %q", "https"),
55+
urlString,
56+
))
57+
}
58+
}
59+
60+
// IsURLWithHTTPSorEmptyString returns a validator which ensures
61+
// that the given rawURL is a https url or is an empty string.
62+
// Null (unconfigured) and unknown (known after apply)
63+
// values are skipped.
64+
func IsURLWithHTTPSorEmptyString() validator.String {
65+
return isURLWithHTTPSorEmptyStringValidator{}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package validation
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"regexp"
7+
"testing"
8+
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+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestFrameworkIsURLWithHTTPSorEmptyString(t *testing.T) {
16+
var testCases = []struct {
17+
inputURL interface{}
18+
expectedErrors []string
19+
}{
20+
{
21+
inputURL: "http://example.com",
22+
expectedErrors: []string{
23+
"Attribute theTestURLPath invalid URL, expected protocol scheme \"https\", got: http://example.com",
24+
},
25+
},
26+
{
27+
inputURL: "http://example.com/foo",
28+
expectedErrors: []string{
29+
"Attribute theTestURLPath invalid URL, expected protocol scheme \"https\", got: http://example.com/foo",
30+
},
31+
},
32+
{
33+
inputURL: "http://example.com#foo",
34+
expectedErrors: []string{
35+
"Attribute theTestURLPath invalid URL, expected protocol scheme \"https\", got: http://example.com#foo",
36+
},
37+
},
38+
{
39+
inputURL: "https://example.com/foo",
40+
expectedErrors: nil,
41+
},
42+
{
43+
inputURL: "https://example.com#foo",
44+
expectedErrors: nil,
45+
},
46+
{
47+
inputURL: "",
48+
expectedErrors: nil,
49+
},
50+
{
51+
inputURL: "broken/url",
52+
expectedErrors: []string{
53+
"Attribute theTestURLPath invalid URL, missing host, got: broken/url",
54+
},
55+
},
56+
{
57+
inputURL: nil,
58+
expectedErrors: nil,
59+
},
60+
{
61+
inputURL: "://example.com",
62+
expectedErrors: []string{
63+
"Attribute theTestURLPath invalid URL: parse \"://example.com\": missing protocol scheme, got: ://example.com",
64+
},
65+
},
66+
}
67+
urlValidator := IsURLWithHTTPSorEmptyString()
68+
69+
for i, testCase := range testCases {
70+
t.Run(fmt.Sprintf("test case #%d", i), func(t *testing.T) {
71+
response := validator.StringResponse{}
72+
request := validator.StringRequest{
73+
Path: path.Root("theTestURLPath"),
74+
}
75+
if testCase.inputURL == nil {
76+
request.ConfigValue = types.StringNull()
77+
} else {
78+
request.ConfigValue = types.StringValue(testCase.inputURL.(string))
79+
}
80+
urlValidator.ValidateString(context.Background(), request, &response)
81+
assert.Equal(t, len(testCase.expectedErrors), len(response.Diagnostics))
82+
for i, diagnostic := range response.Diagnostics {
83+
assert.Regexp(t, regexp.MustCompile(testCase.expectedErrors[i]), diagnostic.Detail())
84+
}
85+
})
86+
}
87+
}

0 commit comments

Comments
 (0)