feat: add locale setting resource support with unifi_setting_locale resource (#34)
* feat: add locale setting resource support with `unifi_setting_locale` resource * lint
This commit is contained in:
committed by
GitHub
parent
273d0daddd
commit
f815ffef79
93
internal/provider/validators/timezone.go
Normal file
93
internal/provider/validators/timezone.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
)
|
||||
|
||||
// Timezone returns a validator which ensures that the string value is a valid IANA timezone identifier
|
||||
// according to the time.LoadLocation function.
|
||||
func Timezone() validator.String {
|
||||
return timezoneValidator{}
|
||||
}
|
||||
|
||||
type timezoneValidator struct{}
|
||||
|
||||
func (v timezoneValidator) Description(_ context.Context) string {
|
||||
return "must be a valid IANA timezone identifier (e.g., 'America/New_York')"
|
||||
}
|
||||
|
||||
func (v timezoneValidator) MarkdownDescription(ctx context.Context) string {
|
||||
return v.Description(ctx)
|
||||
}
|
||||
|
||||
func (v timezoneValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
value := req.ConfigValue
|
||||
if !base.IsDefined(value) {
|
||||
return
|
||||
}
|
||||
|
||||
val := value.ValueString()
|
||||
|
||||
// Check for empty string
|
||||
if val == "" {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
"Timezone cannot be empty. Use a valid IANA timezone identifier like 'America/New_York'",
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for proper case (IANA timezone identifiers are case-sensitive)
|
||||
// Regions should start with uppercase
|
||||
if val[0] >= 'a' && val[0] <= 'z' {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
fmt.Sprintf("%q has incorrect case. IANA timezone regions should start with uppercase (e.g., 'America/New_York')", val),
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Try to load the timezone location
|
||||
_, err := time.LoadLocation(val)
|
||||
if err != nil {
|
||||
// For better error messages, check common mistakes
|
||||
if strings.Contains(val, "UTC") && val != "UTC" {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
fmt.Sprintf("%q is not a valid timezone. For UTC offset use the standard 'UTC' timezone instead.", val),
|
||||
),
|
||||
)
|
||||
} else if strings.Contains(val, " ") {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
fmt.Sprintf("%q is not a valid timezone. Timezones should not contain spaces.", val),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
fmt.Sprintf("%q is not a valid IANA timezone identifier. Use a value like 'America/New_York'", val),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
85
internal/provider/validators/timezone_test.go
Normal file
85
internal/provider/validators/timezone_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
func TestTimezoneValidator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type testCase struct {
|
||||
val types.String
|
||||
expectError bool
|
||||
}
|
||||
tests := map[string]testCase{
|
||||
"unknown": {
|
||||
val: types.StringUnknown(),
|
||||
},
|
||||
"null": {
|
||||
val: types.StringNull(),
|
||||
},
|
||||
"valid-america": {
|
||||
val: types.StringValue("America/Los_Angeles"),
|
||||
},
|
||||
"valid-europe": {
|
||||
val: types.StringValue("Europe/London"),
|
||||
},
|
||||
"valid-asia": {
|
||||
val: types.StringValue("Asia/Tokyo"),
|
||||
},
|
||||
"valid-australia": {
|
||||
val: types.StringValue("Australia/Sydney"),
|
||||
},
|
||||
"valid-utc": {
|
||||
val: types.StringValue("UTC"),
|
||||
},
|
||||
"invalid-with-space": {
|
||||
val: types.StringValue("America/New York"),
|
||||
expectError: true,
|
||||
},
|
||||
"invalid-nonexistent": {
|
||||
val: types.StringValue("NonExistent/Timezone"),
|
||||
expectError: true,
|
||||
},
|
||||
"invalid-empty-string": {
|
||||
val: types.StringValue(""),
|
||||
expectError: true,
|
||||
},
|
||||
"invalid-just-region": {
|
||||
val: types.StringValue("America"),
|
||||
expectError: true,
|
||||
},
|
||||
"invalid-lowercase": {
|
||||
val: types.StringValue("america/los_angeles"),
|
||||
expectError: true,
|
||||
},
|
||||
"invalid-utc-offset": {
|
||||
val: types.StringValue("UTC+01:00"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
name, test := name, test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
request := validator.StringRequest{
|
||||
ConfigValue: test.val,
|
||||
}
|
||||
response := validator.StringResponse{}
|
||||
Timezone().ValidateString(context.Background(), 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
92
internal/provider/validators/url.go
Normal file
92
internal/provider/validators/url.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
)
|
||||
|
||||
// URL returns a validator which ensures that the string value is a valid URL.
|
||||
func URL() validator.String {
|
||||
return urlValidator{requireHTTPS: false}
|
||||
}
|
||||
|
||||
// HTTPSUrl returns a validator which ensures that the string value is a valid HTTPS URL.
|
||||
func HTTPSUrl() validator.String {
|
||||
return urlValidator{requireHTTPS: true}
|
||||
}
|
||||
|
||||
type urlValidator struct {
|
||||
requireHTTPS bool
|
||||
}
|
||||
|
||||
func (v urlValidator) Description(_ context.Context) string {
|
||||
if v.requireHTTPS {
|
||||
return "must be a valid HTTPS URL"
|
||||
}
|
||||
return "must be a valid URL"
|
||||
}
|
||||
|
||||
func (v urlValidator) MarkdownDescription(ctx context.Context) string {
|
||||
return v.Description(ctx)
|
||||
}
|
||||
|
||||
func (v urlValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
value := req.ConfigValue
|
||||
if !base.IsDefined(value) {
|
||||
return
|
||||
}
|
||||
|
||||
val := value.ValueString()
|
||||
parsedURL, err := url.Parse(val)
|
||||
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
fmt.Sprintf("%q is not a valid URL: %s", val, err),
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if URL has a scheme
|
||||
if parsedURL.Scheme == "" {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
fmt.Sprintf("%q is missing a scheme (e.g., http:// or https://)", val),
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if HTTPS is required
|
||||
if v.requireHTTPS && parsedURL.Scheme != "https" {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
fmt.Sprintf("%q must use HTTPS scheme", val),
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if URL has a host
|
||||
if parsedURL.Host == "" {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
fmt.Sprintf("%q is missing a host", val),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
128
internal/provider/validators/url_test.go
Normal file
128
internal/provider/validators/url_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
func TestURLValidator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type testCase struct {
|
||||
val types.String
|
||||
expectError bool
|
||||
}
|
||||
tests := map[string]testCase{
|
||||
"unknown": {
|
||||
val: types.StringUnknown(),
|
||||
},
|
||||
"null": {
|
||||
val: types.StringNull(),
|
||||
},
|
||||
"valid-http": {
|
||||
val: types.StringValue("http://example.com"),
|
||||
},
|
||||
"valid-https": {
|
||||
val: types.StringValue("https://example.com"),
|
||||
},
|
||||
"valid-with-path": {
|
||||
val: types.StringValue("https://example.com/path"),
|
||||
},
|
||||
"valid-with-query": {
|
||||
val: types.StringValue("https://example.com/path?query=value"),
|
||||
},
|
||||
"valid-with-port": {
|
||||
val: types.StringValue("https://example.com:8443"),
|
||||
},
|
||||
"invalid-no-scheme": {
|
||||
val: types.StringValue("example.com"),
|
||||
expectError: true,
|
||||
},
|
||||
"invalid-no-host": {
|
||||
val: types.StringValue("https://"),
|
||||
expectError: true,
|
||||
},
|
||||
"invalid-malformed": {
|
||||
val: types.StringValue("htt ps://example.com"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
name, test := name, test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
request := validator.StringRequest{
|
||||
ConfigValue: test.val,
|
||||
}
|
||||
response := validator.StringResponse{}
|
||||
URL().ValidateString(context.Background(), 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPSURLValidator(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type testCase struct {
|
||||
val types.String
|
||||
expectError bool
|
||||
}
|
||||
tests := map[string]testCase{
|
||||
"unknown": {
|
||||
val: types.StringUnknown(),
|
||||
},
|
||||
"null": {
|
||||
val: types.StringNull(),
|
||||
},
|
||||
"valid-https": {
|
||||
val: types.StringValue("https://example.com"),
|
||||
},
|
||||
"valid-with-path": {
|
||||
val: types.StringValue("https://example.com/path"),
|
||||
},
|
||||
"invalid-http": {
|
||||
val: types.StringValue("http://example.com"),
|
||||
expectError: true,
|
||||
},
|
||||
"invalid-no-scheme": {
|
||||
val: types.StringValue("example.com"),
|
||||
expectError: true,
|
||||
},
|
||||
"invalid-no-host": {
|
||||
val: types.StringValue("https://"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
name, test := name, test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
request := validator.StringRequest{
|
||||
ConfigValue: test.val,
|
||||
}
|
||||
response := validator.StringResponse{}
|
||||
HTTPSUrl().ValidateString(context.Background(), 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user