From fad24d43e821390cbc9935625dcdf3967957a4cf Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 19 Dec 2022 14:15:09 -0500 Subject: [PATCH 1/2] stringvalidator: Add UTF-8 character count validators, clarify original length validators Reference: https://github.com/hashicorp/terraform-plugin-framework-validators/issues/85 --- .changelog/pending.txt | 3 + stringvalidator/length_at_least.go | 11 +-- stringvalidator/length_at_least_test.go | 13 ++- stringvalidator/length_at_most.go | 11 +-- stringvalidator/length_at_most_test.go | 14 ++- stringvalidator/length_between.go | 11 +-- stringvalidator/length_between_test.go | 16 +++- stringvalidator/utf8_length_at_least.go | 65 +++++++++++++ .../utf8_length_at_least_example_test.go | 22 +++++ stringvalidator/utf8_length_at_least_test.go | 84 +++++++++++++++++ stringvalidator/utf8_length_at_most.go | 65 +++++++++++++ .../utf8_length_at_most_example_test.go | 22 +++++ stringvalidator/utf8_length_at_most_test.go | 84 +++++++++++++++++ stringvalidator/utf8_length_between.go | 67 +++++++++++++ .../utf8_length_between_example_test.go | 23 +++++ stringvalidator/utf8_length_between_test.go | 93 +++++++++++++++++++ 16 files changed, 573 insertions(+), 31 deletions(-) create mode 100644 .changelog/pending.txt create mode 100644 stringvalidator/utf8_length_at_least.go create mode 100644 stringvalidator/utf8_length_at_least_example_test.go create mode 100644 stringvalidator/utf8_length_at_least_test.go create mode 100644 stringvalidator/utf8_length_at_most.go create mode 100644 stringvalidator/utf8_length_at_most_example_test.go create mode 100644 stringvalidator/utf8_length_at_most_test.go create mode 100644 stringvalidator/utf8_length_between.go create mode 100644 stringvalidator/utf8_length_between_example_test.go create mode 100644 stringvalidator/utf8_length_between_test.go diff --git a/.changelog/pending.txt b/.changelog/pending.txt new file mode 100644 index 00000000..44f89414 --- /dev/null +++ b/.changelog/pending.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +stringvalidator: Added `UTF8LengthAtLeast`, `UTF8LengthAtMost`, and `UTF8LengthBetween` validators +``` diff --git a/stringvalidator/length_at_least.go b/stringvalidator/length_at_least.go index ce938f29..dfd07ce6 100644 --- a/stringvalidator/length_at_least.go +++ b/stringvalidator/length_at_least.go @@ -45,13 +45,12 @@ func (v lengthAtLeastValidator) ValidateString(ctx context.Context, request vali } } -// LengthAtLeast returns an AttributeValidator which ensures that any configured -// attribute value: +// LengthAtLeast returns an validator which ensures that any configured +// attribute value is of single-byte character length greater than or equal +// to the given minimum. Null (unconfigured) and unknown (known after apply) +// values are skipped. // -// - Is a string. -// - Is of length greater than or equal to the given minimum. -// -// Null (unconfigured) and unknown (known after apply) values are skipped. +// Use UTF8LengthAtLeast for checking multiple-byte characters. func LengthAtLeast(minLength int) validator.String { if minLength < 0 { return nil diff --git a/stringvalidator/length_at_least_test.go b/stringvalidator/length_at_least_test.go index 8a9d2971..2e32b462 100644 --- a/stringvalidator/length_at_least_test.go +++ b/stringvalidator/length_at_least_test.go @@ -20,23 +20,28 @@ func TestLengthAtLeastValidator(t *testing.T) { expectError bool } tests := map[string]testCase{ - "unknown String": { + "unknown": { val: types.StringUnknown(), minLength: 1, }, - "null String": { + "null": { val: types.StringNull(), minLength: 1, }, - "valid String": { + "valid": { val: types.StringValue("ok"), minLength: 1, }, - "too short String": { + "too short": { val: types.StringValue(""), minLength: 1, expectError: true, }, + "multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("⇄"), + minLength: 2, + }, } for name, test := range tests { diff --git a/stringvalidator/length_at_most.go b/stringvalidator/length_at_most.go index c57e82f0..b7c4415c 100644 --- a/stringvalidator/length_at_most.go +++ b/stringvalidator/length_at_most.go @@ -44,13 +44,12 @@ func (v lengthAtMostValidator) ValidateString(ctx context.Context, request valid } } -// LengthAtMost returns an AttributeValidator which ensures that any configured -// attribute value: +// LengthAtMost returns an validator which ensures that any configured +// attribute value is of single-byte character length less than or equal +// to the given maximum. Null (unconfigured) and unknown (known after apply) +// values are skipped. // -// - Is a string. -// - Is of length less than or equal to the given maximum. -// -// Null (unconfigured) and unknown (known after apply) values are skipped. +// Use UTF8LengthAtMost for checking multiple-byte characters. func LengthAtMost(maxLength int) validator.String { if maxLength < 0 { return nil diff --git a/stringvalidator/length_at_most_test.go b/stringvalidator/length_at_most_test.go index 07f75bdb..d5f00e09 100644 --- a/stringvalidator/length_at_most_test.go +++ b/stringvalidator/length_at_most_test.go @@ -20,23 +20,29 @@ func TestLengthAtMostValidator(t *testing.T) { expectError bool } tests := map[string]testCase{ - "unknown String": { + "unknown": { val: types.StringUnknown(), maxLength: 1, }, - "null String": { + "null": { val: types.StringNull(), maxLength: 1, }, - "valid String": { + "valid": { val: types.StringValue("ok"), maxLength: 2, }, - "too long String": { + "too long": { val: types.StringValue("not ok"), maxLength: 5, expectError: true, }, + "multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("⇄"), + maxLength: 2, + expectError: true, + }, } for name, test := range tests { diff --git a/stringvalidator/length_between.go b/stringvalidator/length_between.go index e2d3810d..bdb5701d 100644 --- a/stringvalidator/length_between.go +++ b/stringvalidator/length_between.go @@ -44,13 +44,12 @@ func (v lengthBetweenValidator) ValidateString(ctx context.Context, request vali } } -// LengthBetween returns an AttributeValidator which ensures that any configured -// attribute value: +// LengthBetween returns an validator which ensures that any configured +// attribute value is of single-byte character length greater than the given +// minimum and less than the given maximum. Null (unconfigured) and unknown +// (known after apply) values are skipped. // -// - 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. +// Use UTF8LengthBetween for checking multiple-byte characters. func LengthBetween(minLength, maxLength int) validator.String { if minLength < 0 || maxLength < 0 || minLength > maxLength { return nil diff --git a/stringvalidator/length_between_test.go b/stringvalidator/length_between_test.go index 19ba56d7..6c94722f 100644 --- a/stringvalidator/length_between_test.go +++ b/stringvalidator/length_between_test.go @@ -21,33 +21,39 @@ func TestLengthBetweenValidator(t *testing.T) { expectError bool } tests := map[string]testCase{ - "unknown String": { + "unknown": { val: types.StringUnknown(), minLength: 1, maxLength: 3, }, - "null String": { + "null": { val: types.StringNull(), minLength: 1, maxLength: 3, }, - "valid String": { + "valid": { val: types.StringValue("ok"), minLength: 1, maxLength: 3, }, - "too long String": { + "too long": { val: types.StringValue("not ok"), minLength: 1, maxLength: 3, expectError: true, }, - "too short String": { + "too short": { val: types.StringValue(""), minLength: 1, maxLength: 3, expectError: true, }, + "multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("⇄"), + minLength: 2, + maxLength: 4, + }, } for name, test := range tests { diff --git a/stringvalidator/utf8_length_at_least.go b/stringvalidator/utf8_length_at_least.go new file mode 100644 index 00000000..8cca7cd1 --- /dev/null +++ b/stringvalidator/utf8_length_at_least.go @@ -0,0 +1,65 @@ +package stringvalidator + +import ( + "context" + "fmt" + "unicode/utf8" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = utf8LengthAtLeastValidator{} + +// utf8LengthAtLeastValidator implements the validator. +type utf8LengthAtLeastValidator struct { + minLength int +} + +// Description describes the validation in plain text formatting. +func (validator utf8LengthAtLeastValidator) Description(_ context.Context) string { + return fmt.Sprintf("UTF-8 character count must be at least %d", validator.minLength) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator utf8LengthAtLeastValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (v utf8LengthAtLeastValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + count := utf8.RuneCountInString(value) + + if count < v.minLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", count), + )) + + return + } +} + +// UTF8LengthAtLeast returns an validator which ensures that any configured +// attribute value is of UTF-8 character count greater than or equal to the +// given minimum. Null (unconfigured) and unknown (known after apply) values +// are skipped. +// +// Use LengthAtLeast for checking single-byte character counts. +func UTF8LengthAtLeast(minLength int) validator.String { + if minLength < 0 { + return nil + } + + return utf8LengthAtLeastValidator{ + minLength: minLength, + } +} diff --git a/stringvalidator/utf8_length_at_least_example_test.go b/stringvalidator/utf8_length_at_least_example_test.go new file mode 100644 index 00000000..ab19230f --- /dev/null +++ b/stringvalidator/utf8_length_at_least_example_test.go @@ -0,0 +1,22 @@ +package stringvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func ExampleUTF8LengthAtLeast() { + // Used within a Schema method of a DataSource, Provider, or Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + // Validate UTF-8 character count must be at least 3 characters. + stringvalidator.UTF8LengthAtLeast(3), + }, + }, + }, + } +} diff --git a/stringvalidator/utf8_length_at_least_test.go b/stringvalidator/utf8_length_at_least_test.go new file mode 100644 index 00000000..671aa614 --- /dev/null +++ b/stringvalidator/utf8_length_at_least_test.go @@ -0,0 +1,84 @@ +package stringvalidator_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" +) + +func TestUTF8LengthAtLeastValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val types.String + minLength int + expectError bool + } + tests := map[string]testCase{ + "unknown": { + val: types.StringUnknown(), + minLength: 1, + }, + "null": { + val: types.StringNull(), + minLength: 1, + }, + "valid single byte characters": { + val: types.StringValue("ok"), + minLength: 1, + }, + "valid mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("test⇄test"), + minLength: 9, + }, + "valid multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("⇄"), + minLength: 1, + }, + "invalid single byte characters": { + val: types.StringValue("ok"), + minLength: 3, + expectError: true, + }, + "invalid mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("test⇄test"), + minLength: 10, + expectError: true, + }, + "invalid multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("⇄"), + minLength: 2, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + request := validator.StringRequest{ + Path: path.Root("test"), + PathExpression: path.MatchRoot("test"), + ConfigValue: test.val, + } + response := validator.StringResponse{} + stringvalidator.UTF8LengthAtLeast(test.minLength).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) + } + }) + } +} diff --git a/stringvalidator/utf8_length_at_most.go b/stringvalidator/utf8_length_at_most.go new file mode 100644 index 00000000..60364fc7 --- /dev/null +++ b/stringvalidator/utf8_length_at_most.go @@ -0,0 +1,65 @@ +package stringvalidator + +import ( + "context" + "fmt" + "unicode/utf8" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = utf8LengthAtMostValidator{} + +// utf8LengthAtMostValidator implements the validator. +type utf8LengthAtMostValidator struct { + maxLength int +} + +// Description describes the validation in plain text formatting. +func (validator utf8LengthAtMostValidator) Description(_ context.Context) string { + return fmt.Sprintf("UTF-8 character count must be at most %d", validator.maxLength) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (validator utf8LengthAtMostValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// Validate performs the validation. +func (v utf8LengthAtMostValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + count := utf8.RuneCountInString(value) + + if count > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", count), + )) + + return + } +} + +// UTF8LengthAtMost returns an validator which ensures that any configured +// attribute value is of UTF-8 character count less than or equal to the +// given maximum. Null (unconfigured) and unknown (known after apply) values +// are skipped. +// +// Use LengthAtMost for checking single-byte character counts. +func UTF8LengthAtMost(maxLength int) validator.String { + if maxLength < 0 { + return nil + } + + return utf8LengthAtMostValidator{ + maxLength: maxLength, + } +} diff --git a/stringvalidator/utf8_length_at_most_example_test.go b/stringvalidator/utf8_length_at_most_example_test.go new file mode 100644 index 00000000..25395c02 --- /dev/null +++ b/stringvalidator/utf8_length_at_most_example_test.go @@ -0,0 +1,22 @@ +package stringvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func ExampleUTF8LengthAtMost() { + // Used within a Schema method of a DataSource, Provider, or Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + // Validate UTF-8 character count must be at most 255 characters. + stringvalidator.UTF8LengthAtMost(255), + }, + }, + }, + } +} diff --git a/stringvalidator/utf8_length_at_most_test.go b/stringvalidator/utf8_length_at_most_test.go new file mode 100644 index 00000000..15f42eee --- /dev/null +++ b/stringvalidator/utf8_length_at_most_test.go @@ -0,0 +1,84 @@ +package stringvalidator_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" +) + +func TestUTF8LengthAtMostValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val types.String + maxLength int + expectError bool + } + tests := map[string]testCase{ + "unknown": { + val: types.StringUnknown(), + maxLength: 1, + }, + "null": { + val: types.StringNull(), + maxLength: 1, + }, + "valid single byte characters": { + val: types.StringValue("ok"), + maxLength: 3, + }, + "valid mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("test⇄test"), + maxLength: 9, + }, + "valid multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("⇄"), + maxLength: 1, + }, + "invalid single byte characters": { + val: types.StringValue("ok"), + maxLength: 1, + expectError: true, + }, + "invalid mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("test⇄test"), + maxLength: 8, + expectError: true, + }, + "invalid multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("⇄⇄"), + maxLength: 1, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + request := validator.StringRequest{ + Path: path.Root("test"), + PathExpression: path.MatchRoot("test"), + ConfigValue: test.val, + } + response := validator.StringResponse{} + stringvalidator.UTF8LengthAtMost(test.maxLength).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) + } + }) + } +} diff --git a/stringvalidator/utf8_length_between.go b/stringvalidator/utf8_length_between.go new file mode 100644 index 00000000..adfe1a1c --- /dev/null +++ b/stringvalidator/utf8_length_between.go @@ -0,0 +1,67 @@ +package stringvalidator + +import ( + "context" + "fmt" + "unicode/utf8" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" +) + +var _ validator.String = utf8LengthBetweenValidator{} + +// utf8LengthBetweenValidator implements the validator. +type utf8LengthBetweenValidator struct { + maxLength int + minLength int +} + +// Description describes the validation in plain text formatting. +func (v utf8LengthBetweenValidator) Description(_ context.Context) string { + return fmt.Sprintf("UTF-8 character count must be between %d and %d", v.minLength, v.maxLength) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v utf8LengthBetweenValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v utf8LengthBetweenValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + value := request.ConfigValue.ValueString() + + count := utf8.RuneCountInString(value) + + if count < v.minLength || count > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", count), + )) + + return + } +} + +// UTF8LengthBetween returns an validator which ensures that any configured +// attribute value is of UTF-8 character count greater than or equal to the +// given minimum and less than or equal to the given maximum. Null +// (unconfigured) and unknown (known after apply) values are skipped. +// +// Use LengthBetween for checking single-byte character counts. +func UTF8LengthBetween(minLength int, maxLength int) validator.String { + if minLength < 0 || maxLength < 0 || minLength > maxLength { + return nil + } + + return utf8LengthBetweenValidator{ + maxLength: maxLength, + minLength: minLength, + } +} diff --git a/stringvalidator/utf8_length_between_example_test.go b/stringvalidator/utf8_length_between_example_test.go new file mode 100644 index 00000000..b3aafbda --- /dev/null +++ b/stringvalidator/utf8_length_between_example_test.go @@ -0,0 +1,23 @@ +package stringvalidator_test + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func ExampleUTF8LengthBetween() { + // Used within a Schema method of a DataSource, Provider, or Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attr": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + // Validate UTF-8 character count must be at least 3 characters + // and at most 255 characters. + stringvalidator.UTF8LengthBetween(3, 255), + }, + }, + }, + } +} diff --git a/stringvalidator/utf8_length_between_test.go b/stringvalidator/utf8_length_between_test.go new file mode 100644 index 00000000..96ac1a64 --- /dev/null +++ b/stringvalidator/utf8_length_between_test.go @@ -0,0 +1,93 @@ +package stringvalidator_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" +) + +func TestUTF8LengthBetweenValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + val types.String + minLength int + maxLength int + expectError bool + } + tests := map[string]testCase{ + "unknown": { + val: types.StringUnknown(), + minLength: 1, + maxLength: 1, + }, + "null": { + val: types.StringNull(), + minLength: 1, + maxLength: 1, + }, + "valid single byte characters": { + val: types.StringValue("ok"), + minLength: 2, + maxLength: 3, + }, + "valid mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("test⇄test"), + minLength: 8, + maxLength: 9, + }, + "valid multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("⇄"), + minLength: 1, + maxLength: 1, + }, + "invalid single byte characters": { + val: types.StringValue("ok"), + minLength: 1, + maxLength: 1, + expectError: true, + }, + "invalid mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("test⇄test"), + minLength: 8, + maxLength: 8, + expectError: true, + }, + "invalid multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringValue("⇄⇄"), + minLength: 1, + maxLength: 1, + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + request := validator.StringRequest{ + Path: path.Root("test"), + PathExpression: path.MatchRoot("test"), + ConfigValue: test.val, + } + response := validator.StringResponse{} + stringvalidator.UTF8LengthBetween(test.minLength, test.maxLength).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) + } + }) + } +} From d23e1f6cd5df6668a9e287642f14f50896debe0b Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 19 Dec 2022 14:17:39 -0500 Subject: [PATCH 2/2] Update CHANGELOG for #87 --- .changelog/{pending.txt => 87.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changelog/{pending.txt => 87.txt} (100%) diff --git a/.changelog/pending.txt b/.changelog/87.txt similarity index 100% rename from .changelog/pending.txt rename to .changelog/87.txt