Skip to content

String LengthAtLeast, LengthAtMost and LengthBetween validators #22

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

Merged
merged 2 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/22.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Introduced `stringvalidator` package with `LengthAtLeast()`, `LengthAtMost()`, and `LengthBetween()` validation functions
```
62 changes: 62 additions & 0 deletions stringvalidator/length_at_least.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package stringvalidator

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)

var _ tfsdk.AttributeValidator = lengthAtLeastValidator{}

// stringLenAtLeastValidator validates that a string Attribute's length is at least a certain value.
type lengthAtLeastValidator struct {
minLength int
}

// Description describes the validation in plain text formatting.
func (validator lengthAtLeastValidator) Description(_ context.Context) string {
return fmt.Sprintf("string length must be at least %d", validator.minLength)
}

// MarkdownDescription describes the validation in Markdown formatting.
func (validator lengthAtLeastValidator) MarkdownDescription(ctx context.Context) string {
return validator.Description(ctx)
}

// Validate performs the validation.
func (validator lengthAtLeastValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
s, ok := validateString(ctx, request, response)

if !ok {
return
}

if l := len(s); l < validator.minLength {
response.Diagnostics.Append(validatordiag.AttributeValueLengthDiagnostic(
request.AttributePath,
validator.Description(ctx),
fmt.Sprintf("%d", l),
))

return
}
}

// LengthAtLeast returns an AttributeValidator which ensures that any configured
// attribute value:
//
// - Is a string.
// - Is of length exclusively greater than the given minimum.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func LengthAtLeast(minLength int) tfsdk.AttributeValidator {
if minLength < 0 {
return nil
}

return lengthAtLeastValidator{
minLength: minLength,
}
}
64 changes: 64 additions & 0 deletions stringvalidator/length_at_least_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package stringvalidator

import (
"context"
"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 TestLengthAtLeastValidator(t *testing.T) {
t.Parallel()

type testCase struct {
val attr.Value
minLength int
expectError bool
}
tests := map[string]testCase{
"not a String": {
val: types.Bool{Value: true},
expectError: true,
},
"unknown String": {
val: types.String{Unknown: true},
minLength: 1,
},
"null String": {
val: types.String{Null: true},
minLength: 1,
},
"valid String": {
val: types.String{Value: "ok"},
minLength: 1,
},
"too short String": {
val: types.String{Value: ""},
minLength: 1,
expectError: true,
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
LengthAtLeast(test.minLength).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)
}
})
}
}
82 changes: 82 additions & 0 deletions stringvalidator/length_at_most.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package stringvalidator

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var _ tfsdk.AttributeValidator = lengthAtMostValidator{}

// lengthAtMostValidator validates that a string Attribute's length is at most a certain value.
type lengthAtMostValidator struct {
maxLength int
}

// Description describes the validation in plain text formatting.
func (validator lengthAtMostValidator) Description(_ context.Context) string {
return fmt.Sprintf("string length must be at most %d", validator.maxLength)
}

// MarkdownDescription describes the validation in Markdown formatting.
func (validator lengthAtMostValidator) MarkdownDescription(ctx context.Context) string {
return validator.Description(ctx)
}

// Validate performs the validation.
func (validator lengthAtMostValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
s, ok := validateString(ctx, request, response)

if !ok {
return
}

if l := len(s); l > validator.maxLength {
response.Diagnostics.Append(validatordiag.AttributeValueLengthDiagnostic(
request.AttributePath,
validator.Description(ctx),
fmt.Sprintf("%d", l),
))

return
}
}

// LengthAtMost returns an AttributeValidator which ensures that any configured
// attribute value:
//
// - Is a string.
// - Is of length exclusively less than the given maximum.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func LengthAtMost(maxLength int) tfsdk.AttributeValidator {
if maxLength < 0 {
return nil
}

return lengthAtMostValidator{
maxLength: maxLength,
}
}

// validateString ensures that the request contains a String value.
func validateString(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) (string, bool) {
var s types.String

diags := tfsdk.ValueAs(ctx, request.AttributeConfig, &s)

if diags.HasError() {
response.Diagnostics = append(response.Diagnostics, diags...)

return "", false
}

if s.Unknown || s.Null {
return "", false
}

return s.Value, true
}
64 changes: 64 additions & 0 deletions stringvalidator/length_at_most_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package stringvalidator

import (
"context"
"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 TestLengthAtMostValidator(t *testing.T) {
t.Parallel()

type testCase struct {
val attr.Value
maxLength int
expectError bool
}
tests := map[string]testCase{
"not a String": {
val: types.Bool{Value: true},
expectError: true,
},
"unknown String": {
val: types.String{Unknown: true},
maxLength: 1,
},
"null String": {
val: types.String{Null: true},
maxLength: 1,
},
"valid String": {
val: types.String{Value: "ok"},
maxLength: 2,
},
"too long String": {
val: types.String{Value: "not ok"},
maxLength: 5,
expectError: true,
},
}

for name, test := range tests {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
LengthAtMost(test.maxLength).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)
}
})
}
}
63 changes: 63 additions & 0 deletions stringvalidator/length_between.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package stringvalidator

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework-validators/validatordiag"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)

var _ tfsdk.AttributeValidator = lengthBetweenValidator{}

// stringLenBetweenValidator validates that a string Attribute's length is in a range.
type lengthBetweenValidator struct {
minLength, maxLength int
}

// Description describes the validation in plain text formatting.
func (validator lengthBetweenValidator) Description(_ context.Context) string {
return fmt.Sprintf("string length must be between %d and %d", validator.minLength, validator.maxLength)
}

// MarkdownDescription describes the validation in Markdown formatting.
func (validator lengthBetweenValidator) MarkdownDescription(ctx context.Context) string {
return validator.Description(ctx)
}

// Validate performs the validation.
func (validator lengthBetweenValidator) Validate(ctx context.Context, request tfsdk.ValidateAttributeRequest, response *tfsdk.ValidateAttributeResponse) {
s, ok := validateString(ctx, request, response)

if !ok {
return
}

if l := len(s); l < validator.minLength || l > validator.maxLength {
response.Diagnostics.Append(validatordiag.AttributeValueLengthDiagnostic(
request.AttributePath,
validator.Description(ctx),
fmt.Sprintf("%d", l),
))

return
}
}

// LengthBetween returns an AttributeValidator which ensures that any configured
// attribute value:
//
// - Is a string.
// - Is of length greater than the given minimum and less than the given maximum.
//
// Null (unconfigured) and unknown (known after apply) values are skipped.
func LengthBetween(minLength, maxLength int) tfsdk.AttributeValidator {
if minLength < 0 || maxLength < 0 || minLength > maxLength {
return nil
}

return lengthBetweenValidator{
minLength: minLength,
maxLength: maxLength,
}
}
Loading