From 2cc6fbfd15a85c863e6e06cb9a593d047f819ea0 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 21 Nov 2024 12:25:51 -0500 Subject: [PATCH 01/12] updated to use new interface --- go.mod | 4 +++ go.sum | 4 --- int64validator/at_most.go | 44 +++++++++++++++++++++++++++++-- int64validator/at_most_test.go | 29 ++++++++++++++++++++ stringvalidator/length_at_most.go | 26 ++++++++++++++++-- 5 files changed, 99 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 75219a0c..857ecfa0 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,10 @@ require ( github.com/hashicorp/terraform-plugin-go v0.25.0 ) +replace github.com/hashicorp/terraform-plugin-go => /Users/austin.valle/code/terraform-plugin-go + +replace github.com/hashicorp/terraform-plugin-framework => /Users/austin.valle/code/terraform-plugin-framework + require ( github.com/fatih/color v1.13.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect diff --git a/go.sum b/go.sum index fc181b56..a022d0c3 100644 --- a/go.sum +++ b/go.sum @@ -7,10 +7,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= -github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= -github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks= -github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= diff --git a/int64validator/at_most.go b/int64validator/at_most.go index afc7dffa..3163777d 100644 --- a/int64validator/at_most.go +++ b/int64validator/at_most.go @@ -7,6 +7,7 @@ import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -30,7 +31,28 @@ func (validator atMostValidator) MarkdownDescription(ctx context.Context) string } func (v atMostValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + // Check if there is a lower bound refinement, and if that lower bound indicates the eventual value will be invalid + if lowerRefn, ok := request.ConfigValue.LowerBoundRefinement(); ok { + if lowerRefn.IsInclusive() && lowerRefn.LowerBound() > v.max { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.Path, + "Invalid Attribute Value", + // TODO: improve error messaging? + fmt.Sprintf("Attribute %s %s, got an unknown value that will be greater than: %d", request.Path, v.Description(ctx), lowerRefn.LowerBound()), + )) + } else if !lowerRefn.IsInclusive() && lowerRefn.LowerBound() >= v.max { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.Path, + "Invalid Attribute Value", + fmt.Sprintf("Attribute %s %s, got an unknown value that will be at least: %d", request.Path, v.Description(ctx), lowerRefn.LowerBound()), + )) + } + } return } @@ -44,7 +66,25 @@ func (v atMostValidator) ValidateInt64(ctx context.Context, request validator.In } func (v atMostValidator) ValidateParameterInt64(ctx context.Context, request function.Int64ParameterValidatorRequest, response *function.Int64ParameterValidatorResponse) { - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + // Check if there is a lower bound refinement, and if that lower bound indicates the eventual value will be invalid + if lowerRefn, ok := request.Value.LowerBoundRefinement(); ok { + if lowerRefn.IsInclusive() && lowerRefn.LowerBound() > v.max { + response.Error = function.NewArgumentFuncError( + request.ArgumentPosition, + fmt.Sprintf("Invalid Parameter Value: %s, got an unknown value that will be greater than: %d", v.Description(ctx), lowerRefn.LowerBound()), + ) + } else if !lowerRefn.IsInclusive() && lowerRefn.LowerBound() >= v.max { + response.Error = function.NewArgumentFuncError( + request.ArgumentPosition, + fmt.Sprintf("Invalid Parameter Value: %s, got an unknown value that will be at least: %d", v.Description(ctx), lowerRefn.LowerBound()), + ) + } + } return } diff --git a/int64validator/at_most_test.go b/int64validator/at_most_test.go index a94908dc..a7943a5f 100644 --- a/int64validator/at_most_test.go +++ b/int64validator/at_most_test.go @@ -46,6 +46,35 @@ func TestAtMostValidator(t *testing.T) { max: 2, expectError: true, }, + // Unknown value will be > 2 + "unknown lower bound exclusive - invalid less than bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, false), + max: 1, + expectError: true, + }, + "unknown lower bound exclusive - invalid matches bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, false), + max: 2, + expectError: true, + }, + "unknown lower bound exclusive - valid greater than bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, false), + max: 3, + }, + // Unknown value will be >= 2 + "unknown lower bound inclusive - invalid less than bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, true), + max: 1, + expectError: true, + }, + "unknown lower bound inclusive - valid matches bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, true), + max: 2, + }, + "unknown lower bound inclusive - valid greater than bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, true), + max: 3, + }, } for name, test := range tests { diff --git a/stringvalidator/length_at_most.go b/stringvalidator/length_at_most.go index 2c326968..b6c4ac2a 100644 --- a/stringvalidator/length_at_most.go +++ b/stringvalidator/length_at_most.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatorfuncerr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) @@ -46,7 +47,18 @@ func (v lengthAtMostValidator) ValidateString(ctx context.Context, request valid return } - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if prefixRefn, ok := request.ConfigValue.PrefixRefinement(); ok && len(prefixRefn.PrefixValue()) > v.maxLength { + response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + request.Path, + "Invalid Attribute Value Length", + fmt.Sprintf("Attribute %s %s, got an unknown value with a prefix of length: %d", request.Path, v.Description(ctx), len(prefixRefn.PrefixValue())), + )) + } return } @@ -75,7 +87,17 @@ func (v lengthAtMostValidator) ValidateParameterString(ctx context.Context, requ return } - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if prefixRefn, ok := request.Value.PrefixRefinement(); ok && len(prefixRefn.PrefixValue()) > v.maxLength { + response.Error = function.NewArgumentFuncError( + request.ArgumentPosition, + fmt.Sprintf("Invalid Parameter Value: %s, got an unknown value with a prefix of length: %d", v.Description(ctx), len(prefixRefn.PrefixValue())), + ) + } return } From c77b5dc1904ac543b1facf497fd1254fa12155a0 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 3 Dec 2024 10:55:35 -0500 Subject: [PATCH 02/12] switch to commit hash --- go.mod | 8 ++------ go.sum | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 857ecfa0..988b905e 100644 --- a/go.mod +++ b/go.mod @@ -6,14 +6,10 @@ toolchain go1.22.7 require ( github.com/google/go-cmp v0.6.0 - github.com/hashicorp/terraform-plugin-framework v1.13.0 - github.com/hashicorp/terraform-plugin-go v0.25.0 + github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20241203155412-a2d406602012 + github.com/hashicorp/terraform-plugin-go v0.25.1-0.20241126200214-bd716fcfe407 ) -replace github.com/hashicorp/terraform-plugin-go => /Users/austin.valle/code/terraform-plugin-go - -replace github.com/hashicorp/terraform-plugin-framework => /Users/austin.valle/code/terraform-plugin-framework - require ( github.com/fatih/color v1.13.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect diff --git a/go.sum b/go.sum index a022d0c3..d376bf87 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,10 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20241203155412-a2d406602012 h1:mmXb115JfQLUvlUo86P9Hu5eTNRhs4TsgUihJs67mLM= +github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20241203155412-a2d406602012/go.mod h1:Rdj606MBP6yGeLrPjOEdIFpMsRZWP3E9lzGx++Zth6k= +github.com/hashicorp/terraform-plugin-go v0.25.1-0.20241126200214-bd716fcfe407 h1:oLzKb+YiJIEq0EY3qGgQTxCLW2CaXN1rJp3yg1H11qI= +github.com/hashicorp/terraform-plugin-go v0.25.1-0.20241126200214-bd716fcfe407/go.mod h1:f8P2pHGkZrtdKLpCI2qIvrewUY+c4nTvtayqjJR9IcY= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= From 623f39bebb5acae0ba081ba552d94b4bd40141cb Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 3 Dec 2024 14:48:07 -0500 Subject: [PATCH 03/12] implement all string validator refinements --- stringvalidator/length_at_most.go | 16 +++--- stringvalidator/length_at_most_test.go | 9 ++++ stringvalidator/length_between.go | 26 +++++++++- stringvalidator/length_between_test.go | 17 ++++++ stringvalidator/utf8_length_at_most.go | 26 +++++++++- stringvalidator/utf8_length_at_most_test.go | 31 +++++++++++ stringvalidator/utf8_length_between.go | 26 +++++++++- stringvalidator/utf8_length_between_test.go | 57 +++++++++++++++++++++ 8 files changed, 194 insertions(+), 14 deletions(-) diff --git a/stringvalidator/length_at_most.go b/stringvalidator/length_at_most.go index b6c4ac2a..c69a6885 100644 --- a/stringvalidator/length_at_most.go +++ b/stringvalidator/length_at_most.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatorfuncerr" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) @@ -52,11 +51,11 @@ func (v lengthAtMostValidator) ValidateString(ctx context.Context, request valid } if request.ConfigValue.IsUnknown() { - if prefixRefn, ok := request.ConfigValue.PrefixRefinement(); ok && len(prefixRefn.PrefixValue()) > v.maxLength { - response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + if refn, ok := request.ConfigValue.PrefixRefinement(); ok && len(refn.PrefixValue()) > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( request.Path, - "Invalid Attribute Value Length", - fmt.Sprintf("Attribute %s %s, got an unknown value with a prefix of length: %d", request.Path, v.Description(ctx), len(prefixRefn.PrefixValue())), + v.Description(ctx), + fmt.Sprintf("unknown value with prefix of length %d", len(refn.PrefixValue())), )) } return @@ -92,10 +91,11 @@ func (v lengthAtMostValidator) ValidateParameterString(ctx context.Context, requ } if request.Value.IsUnknown() { - if prefixRefn, ok := request.Value.PrefixRefinement(); ok && len(prefixRefn.PrefixValue()) > v.maxLength { - response.Error = function.NewArgumentFuncError( + if refn, ok := request.Value.PrefixRefinement(); ok && len(refn.PrefixValue()) > v.maxLength { + response.Error = validatorfuncerr.InvalidParameterValueLengthFuncError( request.ArgumentPosition, - fmt.Sprintf("Invalid Parameter Value: %s, got an unknown value with a prefix of length: %d", v.Description(ctx), len(prefixRefn.PrefixValue())), + v.Description(ctx), + fmt.Sprintf("unknown value with prefix of length %d", len(refn.PrefixValue())), ) } return diff --git a/stringvalidator/length_at_most_test.go b/stringvalidator/length_at_most_test.go index 6eeca554..966b8596 100644 --- a/stringvalidator/length_at_most_test.go +++ b/stringvalidator/length_at_most_test.go @@ -29,6 +29,15 @@ func TestLengthAtMostValidator(t *testing.T) { val: types.StringUnknown(), maxLength: 1, }, + "unknown-prefix-valid": { + val: types.StringUnknown().RefineWithPrefix("ok"), + maxLength: 2, + }, + "unknown-prefix-too-long": { + val: types.StringUnknown().RefineWithPrefix("not ok"), + maxLength: 5, + expectError: true, + }, "null": { val: types.StringNull(), maxLength: 1, diff --git a/stringvalidator/length_between.go b/stringvalidator/length_between.go index f02c5fdd..1c89fa59 100644 --- a/stringvalidator/length_between.go +++ b/stringvalidator/length_between.go @@ -46,7 +46,18 @@ func (v lengthBetweenValidator) ValidateString(ctx context.Context, request vali return } - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.PrefixRefinement(); ok && len(refn.PrefixValue()) > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value with prefix of length %d", len(refn.PrefixValue())), + )) + } return } @@ -75,7 +86,18 @@ func (v lengthBetweenValidator) ValidateParameterString(ctx context.Context, req return } - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.PrefixRefinement(); ok && len(refn.PrefixValue()) > v.maxLength { + response.Error = validatorfuncerr.InvalidParameterValueLengthFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value with prefix of length %d", len(refn.PrefixValue())), + ) + } return } diff --git a/stringvalidator/length_between_test.go b/stringvalidator/length_between_test.go index cf3db9b2..70bd8926 100644 --- a/stringvalidator/length_between_test.go +++ b/stringvalidator/length_between_test.go @@ -31,6 +31,23 @@ func TestLengthBetweenValidator(t *testing.T) { minLength: 1, maxLength: 3, }, + // Even if the refinement is too short, it's possible the final value could be longer, so no validation error. + "unknown-prefix-valid-short": { + val: types.StringUnknown().RefineWithPrefix("o"), + minLength: 2, + maxLength: 5, + }, + "unknown-prefix-valid": { + val: types.StringUnknown().RefineWithPrefix("ok"), + minLength: 1, + maxLength: 2, + }, + "unknown-prefix-too-long": { + val: types.StringUnknown().RefineWithPrefix("not ok"), + minLength: 1, + maxLength: 5, + expectError: true, + }, "null": { val: types.StringNull(), minLength: 1, diff --git a/stringvalidator/utf8_length_at_most.go b/stringvalidator/utf8_length_at_most.go index e02db80b..3dbef4ee 100644 --- a/stringvalidator/utf8_length_at_most.go +++ b/stringvalidator/utf8_length_at_most.go @@ -48,7 +48,18 @@ func (v utf8LengthAtMostValidator) ValidateString(ctx context.Context, request v return } - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.PrefixRefinement(); ok && utf8.RuneCountInString(refn.PrefixValue()) > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value with prefix UTF-8 character count of %d", utf8.RuneCountInString(refn.PrefixValue())), + )) + } return } @@ -79,7 +90,18 @@ func (v utf8LengthAtMostValidator) ValidateParameterString(ctx context.Context, return } - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.PrefixRefinement(); ok && utf8.RuneCountInString(refn.PrefixValue()) > v.maxLength { + response.Error = validatorfuncerr.InvalidParameterValueLengthFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value with prefix UTF-8 character count of %d", utf8.RuneCountInString(refn.PrefixValue())), + ) + } return } diff --git a/stringvalidator/utf8_length_at_most_test.go b/stringvalidator/utf8_length_at_most_test.go index ce70d88d..c96028ac 100644 --- a/stringvalidator/utf8_length_at_most_test.go +++ b/stringvalidator/utf8_length_at_most_test.go @@ -37,33 +37,64 @@ func TestUTF8LengthAtMostValidator(t *testing.T) { val: types.StringValue("ok"), maxLength: 3, }, + "valid unknown prefix single byte characters": { + val: types.StringUnknown().RefineWithPrefix("ok"), + maxLength: 3, + }, "valid mixed byte characters": { // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) val: types.StringValue("test⇄test"), maxLength: 9, }, + "valid unknown prefix mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("test⇄test"), + maxLength: 9, + }, "valid multiple byte characters": { // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) val: types.StringValue("⇄"), maxLength: 1, }, + "valid unknown prefix multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("⇄"), + maxLength: 1, + }, "invalid single byte characters": { val: types.StringValue("ok"), maxLength: 1, expectError: true, }, + "invalid unknown prefix single byte characters": { + val: types.StringUnknown().RefineWithPrefix("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 unknown prefix mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("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, }, + "invalid unknown prefix multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("⇄⇄"), + maxLength: 1, + expectError: true, + }, "invalid validator usage - maxLength < 0": { val: types.StringValue("ok"), maxLength: -1, diff --git a/stringvalidator/utf8_length_between.go b/stringvalidator/utf8_length_between.go index 05e22159..40f666fa 100644 --- a/stringvalidator/utf8_length_between.go +++ b/stringvalidator/utf8_length_between.go @@ -49,7 +49,18 @@ func (v utf8LengthBetweenValidator) ValidateString(ctx context.Context, request return } - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.PrefixRefinement(); ok && utf8.RuneCountInString(refn.PrefixValue()) > v.maxLength { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueLengthDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value with prefix UTF-8 character count of %d", utf8.RuneCountInString(refn.PrefixValue())), + )) + } return } @@ -80,7 +91,18 @@ func (v utf8LengthBetweenValidator) ValidateParameterString(ctx context.Context, return } - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.PrefixRefinement(); ok && utf8.RuneCountInString(refn.PrefixValue()) > v.maxLength { + response.Error = validatorfuncerr.InvalidParameterValueLengthFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value with prefix UTF-8 character count of %d", utf8.RuneCountInString(refn.PrefixValue())), + ) + } return } diff --git a/stringvalidator/utf8_length_between_test.go b/stringvalidator/utf8_length_between_test.go index 9a0b04e5..b1e3dc1b 100644 --- a/stringvalidator/utf8_length_between_test.go +++ b/stringvalidator/utf8_length_between_test.go @@ -41,24 +41,67 @@ func TestUTF8LengthBetweenValidator(t *testing.T) { minLength: 2, maxLength: 3, }, + "valid unknown prefix single byte characters": { + val: types.StringUnknown().RefineWithPrefix("ok"), + minLength: 2, + maxLength: 3, + }, + // Even if the refinement is too short, it's possible the final value could be longer, so no validation error. + "valid unknown prefix single byte characters - too short": { + val: types.StringUnknown().RefineWithPrefix("ok"), + minLength: 5, + maxLength: 6, + }, "valid mixed byte characters": { // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) val: types.StringValue("test⇄test"), minLength: 8, maxLength: 9, }, + "valid unknown prefix mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("test⇄test"), + minLength: 8, + maxLength: 9, + }, + // Even if the refinement is too short, it's possible the final value could be longer, so no validation error. + "valid unknown prefix mixed byte characters - too short": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("test⇄test"), + minLength: 12, + maxLength: 14, + }, "valid multiple byte characters": { // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) val: types.StringValue("⇄"), minLength: 1, maxLength: 1, }, + "valid unknown prefix multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("⇄"), + minLength: 1, + maxLength: 1, + }, + // Even if the refinement is too short, it's possible the final value could be longer, so no validation error. + "valid unknown prefix multiple byte characters - too short": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("⇄"), + minLength: 3, + maxLength: 4, + }, "invalid single byte characters": { val: types.StringValue("ok"), minLength: 1, maxLength: 1, expectError: true, }, + "invalid unknown prefix single byte characters": { + val: types.StringUnknown().RefineWithPrefix("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"), @@ -66,6 +109,13 @@ func TestUTF8LengthBetweenValidator(t *testing.T) { maxLength: 8, expectError: true, }, + "invalid unknown prefix mixed byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("test⇄test"), + minLength: 8, + maxLength: 8, + expectError: true, + }, "invalid multiple byte characters": { // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) val: types.StringValue("⇄⇄"), @@ -73,6 +123,13 @@ func TestUTF8LengthBetweenValidator(t *testing.T) { maxLength: 1, expectError: true, }, + "invalid unknown prefix multiple byte characters": { + // Rightwards Arrow Over Leftwards Arrow (U+21C4; 3 bytes) + val: types.StringUnknown().RefineWithPrefix("⇄⇄"), + minLength: 1, + maxLength: 1, + expectError: true, + }, "invalid validator usage - minLength < 0": { val: types.StringValue("ok"), minLength: -1, From 08938e02b4bcbc6b691d6d797999295b8c2c3606 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 3 Dec 2024 16:32:26 -0500 Subject: [PATCH 04/12] add int64 validator updates --- int64validator/at_least.go | 42 ++++++++++++++++++- int64validator/at_least_test.go | 29 +++++++++++++ int64validator/at_most.go | 38 ++++++++--------- int64validator/between.go | 74 ++++++++++++++++++++++++++++++++- int64validator/between_test.go | 70 +++++++++++++++++++++++++++++++ 5 files changed, 229 insertions(+), 24 deletions(-) diff --git a/int64validator/at_least.go b/int64validator/at_least.go index 54f89584..972772fd 100644 --- a/int64validator/at_least.go +++ b/int64validator/at_least.go @@ -30,7 +30,26 @@ func (validator atLeastValidator) MarkdownDescription(ctx context.Context) strin } func (v atLeastValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %d", refn.UpperBound()), + )) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %d", refn.UpperBound()), + )) + } + } return } @@ -44,7 +63,26 @@ func (v atLeastValidator) ValidateInt64(ctx context.Context, request validator.I } func (v atLeastValidator) ValidateParameterInt64(ctx context.Context, request function.Int64ParameterValidatorRequest, response *function.Int64ParameterValidatorResponse) { - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %d", refn.UpperBound()), + ) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %d", refn.UpperBound()), + ) + } + } return } diff --git a/int64validator/at_least_test.go b/int64validator/at_least_test.go index d224a5dc..4571a39f 100644 --- a/int64validator/at_least_test.go +++ b/int64validator/at_least_test.go @@ -46,6 +46,35 @@ func TestAtLeastValidator(t *testing.T) { min: 1, expectError: true, }, + // Unknown value will be < 2 + "unknown upper bound exclusive - valid less than bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, false), + min: 1, + }, + "unknown upper bound exclusive - invalid matches bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, false), + min: 2, + expectError: true, + }, + "unknown upper bound exclusive - invalid greater than bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, false), + min: 3, + expectError: true, + }, + // Unknown value will be <= 2 + "unknown upper bound inclusive - valid less than bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, true), + min: 1, + }, + "unknown upper bound inclusive - valid matches bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, true), + min: 2, + }, + "unknown upper bound inclusive - invalid greater than bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, true), + min: 3, + expectError: true, + }, } for name, test := range tests { diff --git a/int64validator/at_most.go b/int64validator/at_most.go index 3163777d..07d30d94 100644 --- a/int64validator/at_most.go +++ b/int64validator/at_most.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -36,20 +35,18 @@ func (v atMostValidator) ValidateInt64(ctx context.Context, request validator.In } if request.ConfigValue.IsUnknown() { - // Check if there is a lower bound refinement, and if that lower bound indicates the eventual value will be invalid - if lowerRefn, ok := request.ConfigValue.LowerBoundRefinement(); ok { - if lowerRefn.IsInclusive() && lowerRefn.LowerBound() > v.max { - response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + if refn, ok := request.ConfigValue.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( request.Path, - "Invalid Attribute Value", - // TODO: improve error messaging? - fmt.Sprintf("Attribute %s %s, got an unknown value that will be greater than: %d", request.Path, v.Description(ctx), lowerRefn.LowerBound()), + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %d", refn.LowerBound()), )) - } else if !lowerRefn.IsInclusive() && lowerRefn.LowerBound() >= v.max { - response.Diagnostics.Append(diag.NewAttributeErrorDiagnostic( + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( request.Path, - "Invalid Attribute Value", - fmt.Sprintf("Attribute %s %s, got an unknown value that will be at least: %d", request.Path, v.Description(ctx), lowerRefn.LowerBound()), + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %d", refn.LowerBound()), )) } } @@ -71,17 +68,18 @@ func (v atMostValidator) ValidateParameterInt64(ctx context.Context, request fun } if request.Value.IsUnknown() { - // Check if there is a lower bound refinement, and if that lower bound indicates the eventual value will be invalid - if lowerRefn, ok := request.Value.LowerBoundRefinement(); ok { - if lowerRefn.IsInclusive() && lowerRefn.LowerBound() > v.max { - response.Error = function.NewArgumentFuncError( + if refn, ok := request.Value.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( request.ArgumentPosition, - fmt.Sprintf("Invalid Parameter Value: %s, got an unknown value that will be greater than: %d", v.Description(ctx), lowerRefn.LowerBound()), + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %d", refn.LowerBound()), ) - } else if !lowerRefn.IsInclusive() && lowerRefn.LowerBound() >= v.max { - response.Error = function.NewArgumentFuncError( + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( request.ArgumentPosition, - fmt.Sprintf("Invalid Parameter Value: %s, got an unknown value that will be at least: %d", v.Description(ctx), lowerRefn.LowerBound()), + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %d", refn.LowerBound()), ) } } diff --git a/int64validator/between.go b/int64validator/between.go index e414c3c8..3e0a5c96 100644 --- a/int64validator/between.go +++ b/int64validator/between.go @@ -47,7 +47,42 @@ func (v betweenValidator) ValidateInt64(ctx context.Context, request validator.I return } - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %d", refn.LowerBound()), + )) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %d", refn.LowerBound()), + )) + } + } + + if refn, ok := request.ConfigValue.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %d", refn.UpperBound()), + )) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %d", refn.UpperBound()), + )) + } + } return } @@ -72,7 +107,42 @@ func (v betweenValidator) ValidateParameterInt64(ctx context.Context, request fu return } - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %d", refn.LowerBound()), + ) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %d", refn.LowerBound()), + ) + } + } + + if refn, ok := request.Value.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %d", refn.UpperBound()), + ) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %d", refn.UpperBound()), + ) + } + } return } diff --git a/int64validator/between_test.go b/int64validator/between_test.go index a7f428c8..e4d9a54c 100644 --- a/int64validator/between_test.go +++ b/int64validator/between_test.go @@ -69,6 +69,76 @@ func TestBetweenValidator(t *testing.T) { max: 1, expectError: true, }, + // Unknown value will be > 2 + "unknown lower bound exclusive - invalid less than bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, false), + min: 1, + max: 1, + expectError: true, + }, + "unknown lower bound exclusive - invalid matches bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, false), + min: 1, + max: 2, + expectError: true, + }, + "unknown lower bound exclusive - valid greater than bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, false), + min: 1, + max: 3, + }, + // Unknown value will be >= 2 + "unknown lower bound inclusive - invalid less than bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, true), + min: 1, + max: 1, + expectError: true, + }, + "unknown lower bound inclusive - valid matches bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, true), + min: 1, + max: 2, + }, + "unknown lower bound inclusive - valid greater than bound": { + val: types.Int64Unknown().RefineWithLowerBound(2, true), + min: 1, + max: 3, + }, + // Unknown value will be < 2 + "unknown upper bound exclusive - valid less than bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, false), + min: 1, + max: 5, + }, + "unknown upper bound exclusive - invalid matches bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, false), + min: 2, + max: 5, + expectError: true, + }, + "unknown upper bound exclusive - invalid greater than bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, false), + min: 3, + max: 5, + expectError: true, + }, + // Unknown value will be <= 2 + "unknown upper bound inclusive - valid less than bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, true), + min: 1, + max: 5, + }, + "unknown upper bound inclusive - valid matches bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, true), + min: 2, + max: 5, + }, + "unknown upper bound inclusive - invalid greater than bound": { + val: types.Int64Unknown().RefineWithUpperBound(2, true), + min: 3, + max: 5, + expectError: true, + }, } for name, test := range tests { From a6939af8f99900dbe0375f5c1567104c373f252a Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 3 Dec 2024 16:52:19 -0500 Subject: [PATCH 05/12] int32 validator changes --- int32validator/at_least.go | 42 ++++++++++++++++++- int32validator/at_least_test.go | 29 +++++++++++++ int32validator/at_most.go | 42 ++++++++++++++++++- int32validator/at_most_test.go | 29 +++++++++++++ int32validator/between.go | 74 ++++++++++++++++++++++++++++++++- int32validator/between_test.go | 70 +++++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+), 6 deletions(-) diff --git a/int32validator/at_least.go b/int32validator/at_least.go index c33d635b..92816877 100644 --- a/int32validator/at_least.go +++ b/int32validator/at_least.go @@ -30,7 +30,26 @@ func (validator atLeastValidator) MarkdownDescription(ctx context.Context) strin } func (v atLeastValidator) ValidateInt32(ctx context.Context, request validator.Int32Request, response *validator.Int32Response) { - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %d", refn.UpperBound()), + )) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %d", refn.UpperBound()), + )) + } + } return } @@ -44,7 +63,26 @@ func (v atLeastValidator) ValidateInt32(ctx context.Context, request validator.I } func (v atLeastValidator) ValidateParameterInt32(ctx context.Context, request function.Int32ParameterValidatorRequest, response *function.Int32ParameterValidatorResponse) { - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %d", refn.UpperBound()), + ) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %d", refn.UpperBound()), + ) + } + } return } diff --git a/int32validator/at_least_test.go b/int32validator/at_least_test.go index 9eeb036c..da03fb8f 100644 --- a/int32validator/at_least_test.go +++ b/int32validator/at_least_test.go @@ -46,6 +46,35 @@ func TestAtLeastValidator(t *testing.T) { min: 1, expectError: true, }, + // Unknown value will be < 2 + "unknown upper bound exclusive - valid less than bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, false), + min: 1, + }, + "unknown upper bound exclusive - invalid matches bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, false), + min: 2, + expectError: true, + }, + "unknown upper bound exclusive - invalid greater than bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, false), + min: 3, + expectError: true, + }, + // Unknown value will be <= 2 + "unknown upper bound inclusive - valid less than bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, true), + min: 1, + }, + "unknown upper bound inclusive - valid matches bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, true), + min: 2, + }, + "unknown upper bound inclusive - invalid greater than bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, true), + min: 3, + expectError: true, + }, } for name, test := range tests { diff --git a/int32validator/at_most.go b/int32validator/at_most.go index 38f15d6f..13262cfc 100644 --- a/int32validator/at_most.go +++ b/int32validator/at_most.go @@ -30,7 +30,26 @@ func (validator atMostValidator) MarkdownDescription(ctx context.Context) string } func (v atMostValidator) ValidateInt32(ctx context.Context, request validator.Int32Request, response *validator.Int32Response) { - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %d", refn.LowerBound()), + )) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %d", refn.LowerBound()), + )) + } + } return } @@ -44,7 +63,26 @@ func (v atMostValidator) ValidateInt32(ctx context.Context, request validator.In } func (v atMostValidator) ValidateParameterInt32(ctx context.Context, request function.Int32ParameterValidatorRequest, response *function.Int32ParameterValidatorResponse) { - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %d", refn.LowerBound()), + ) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %d", refn.LowerBound()), + ) + } + } return } diff --git a/int32validator/at_most_test.go b/int32validator/at_most_test.go index c28a1186..507fac28 100644 --- a/int32validator/at_most_test.go +++ b/int32validator/at_most_test.go @@ -46,6 +46,35 @@ func TestAtMostValidator(t *testing.T) { max: 2, expectError: true, }, + // Unknown value will be > 2 + "unknown lower bound exclusive - invalid less than bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, false), + max: 1, + expectError: true, + }, + "unknown lower bound exclusive - invalid matches bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, false), + max: 2, + expectError: true, + }, + "unknown lower bound exclusive - valid greater than bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, false), + max: 3, + }, + // Unknown value will be >= 2 + "unknown lower bound inclusive - invalid less than bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, true), + max: 1, + expectError: true, + }, + "unknown lower bound inclusive - valid matches bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, true), + max: 2, + }, + "unknown lower bound inclusive - valid greater than bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, true), + max: 3, + }, } for name, test := range tests { diff --git a/int32validator/between.go b/int32validator/between.go index ea7291d1..0ef24411 100644 --- a/int32validator/between.go +++ b/int32validator/between.go @@ -47,7 +47,42 @@ func (v betweenValidator) ValidateInt32(ctx context.Context, request validator.I return } - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %d", refn.LowerBound()), + )) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %d", refn.LowerBound()), + )) + } + } + + if refn, ok := request.ConfigValue.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %d", refn.UpperBound()), + )) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %d", refn.UpperBound()), + )) + } + } return } @@ -72,7 +107,42 @@ func (v betweenValidator) ValidateParameterInt32(ctx context.Context, request fu return } - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %d", refn.LowerBound()), + ) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %d", refn.LowerBound()), + ) + } + } + + if refn, ok := request.Value.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %d", refn.UpperBound()), + ) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %d", refn.UpperBound()), + ) + } + } return } diff --git a/int32validator/between_test.go b/int32validator/between_test.go index b67f45bf..49866e77 100644 --- a/int32validator/between_test.go +++ b/int32validator/between_test.go @@ -69,6 +69,76 @@ func TestBetweenValidator(t *testing.T) { max: 1, expectError: true, }, + // Unknown value will be > 2 + "unknown lower bound exclusive - invalid less than bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, false), + min: 1, + max: 1, + expectError: true, + }, + "unknown lower bound exclusive - invalid matches bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, false), + min: 1, + max: 2, + expectError: true, + }, + "unknown lower bound exclusive - valid greater than bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, false), + min: 1, + max: 3, + }, + // Unknown value will be >= 2 + "unknown lower bound inclusive - invalid less than bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, true), + min: 1, + max: 1, + expectError: true, + }, + "unknown lower bound inclusive - valid matches bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, true), + min: 1, + max: 2, + }, + "unknown lower bound inclusive - valid greater than bound": { + val: types.Int32Unknown().RefineWithLowerBound(2, true), + min: 1, + max: 3, + }, + // Unknown value will be < 2 + "unknown upper bound exclusive - valid less than bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, false), + min: 1, + max: 5, + }, + "unknown upper bound exclusive - invalid matches bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, false), + min: 2, + max: 5, + expectError: true, + }, + "unknown upper bound exclusive - invalid greater than bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, false), + min: 3, + max: 5, + expectError: true, + }, + // Unknown value will be <= 2 + "unknown upper bound inclusive - valid less than bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, true), + min: 1, + max: 5, + }, + "unknown upper bound inclusive - valid matches bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, true), + min: 2, + max: 5, + }, + "unknown upper bound inclusive - invalid greater than bound": { + val: types.Int32Unknown().RefineWithUpperBound(2, true), + min: 3, + max: 5, + expectError: true, + }, } for name, test := range tests { From 6beacf265fc8976a0a4e490cf06c240d69c663b3 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 3 Dec 2024 17:13:29 -0500 Subject: [PATCH 06/12] float64 validators --- float64validator/at_least.go | 42 +++++++++++++++++- float64validator/at_least_test.go | 29 ++++++++++++ float64validator/at_most.go | 42 +++++++++++++++++- float64validator/at_most_test.go | 29 ++++++++++++ float64validator/between.go | 74 ++++++++++++++++++++++++++++++- float64validator/between_test.go | 70 +++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+), 6 deletions(-) diff --git a/float64validator/at_least.go b/float64validator/at_least.go index 7d334b5e..34e63d4f 100644 --- a/float64validator/at_least.go +++ b/float64validator/at_least.go @@ -30,7 +30,26 @@ func (validator atLeastValidator) MarkdownDescription(ctx context.Context) strin } func (validator atLeastValidator) ValidateFloat64(ctx context.Context, request validator.Float64Request, response *validator.Float64Response) { - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < validator.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + validator.Description(ctx), + fmt.Sprintf("unknown value that will be at most %f", refn.UpperBound()), + )) + } else if !refn.IsInclusive() && refn.UpperBound() <= validator.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + validator.Description(ctx), + fmt.Sprintf("unknown value that will be less than %f", refn.UpperBound()), + )) + } + } return } @@ -46,7 +65,26 @@ func (validator atLeastValidator) ValidateFloat64(ctx context.Context, request v } func (validator atLeastValidator) ValidateParameterFloat64(ctx context.Context, request function.Float64ParameterValidatorRequest, response *function.Float64ParameterValidatorResponse) { - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < validator.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + validator.Description(ctx), + fmt.Sprintf("unknown value that will be at most %f", refn.UpperBound()), + ) + } else if !refn.IsInclusive() && refn.UpperBound() <= validator.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + validator.Description(ctx), + fmt.Sprintf("unknown value that will be less than %f", refn.UpperBound()), + ) + } + } return } diff --git a/float64validator/at_least_test.go b/float64validator/at_least_test.go index d24df495..fd056c62 100644 --- a/float64validator/at_least_test.go +++ b/float64validator/at_least_test.go @@ -50,6 +50,35 @@ func TestAtLeastValidator(t *testing.T) { min: 0.90, expectError: true, }, + // Unknown value will be < 2.1 + "unknown upper bound exclusive - valid less than bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, false), + min: 2, + }, + "unknown upper bound exclusive - invalid matches bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, false), + min: 2.1, + expectError: true, + }, + "unknown upper bound exclusive - invalid greater than bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, false), + min: 3, + expectError: true, + }, + // Unknown value will be <= 2.1 + "unknown upper bound inclusive - valid less than bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, true), + min: 2, + }, + "unknown upper bound inclusive - valid matches bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, true), + min: 2.1, + }, + "unknown upper bound inclusive - invalid greater than bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, true), + min: 3, + expectError: true, + }, } for name, test := range tests { diff --git a/float64validator/at_most.go b/float64validator/at_most.go index 786859b2..aae35a80 100644 --- a/float64validator/at_most.go +++ b/float64validator/at_most.go @@ -30,7 +30,26 @@ func (validator atMostValidator) MarkdownDescription(ctx context.Context) string } func (v atMostValidator) ValidateFloat64(ctx context.Context, request validator.Float64Request, response *validator.Float64Response) { - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %f", refn.LowerBound()), + )) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %f", refn.LowerBound()), + )) + } + } return } @@ -46,7 +65,26 @@ func (v atMostValidator) ValidateFloat64(ctx context.Context, request validator. } func (v atMostValidator) ValidateParameterFloat64(ctx context.Context, request function.Float64ParameterValidatorRequest, response *function.Float64ParameterValidatorResponse) { - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %f", refn.LowerBound()), + ) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %f", refn.LowerBound()), + ) + } + } return } diff --git a/float64validator/at_most_test.go b/float64validator/at_most_test.go index 464ef945..86be0408 100644 --- a/float64validator/at_most_test.go +++ b/float64validator/at_most_test.go @@ -50,6 +50,35 @@ func TestAtMostValidator(t *testing.T) { max: 2.00, expectError: true, }, + // Unknown value will be > 2.1 + "unknown lower bound exclusive - invalid less than bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, false), + max: 2, + expectError: true, + }, + "unknown lower bound exclusive - invalid matches bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, false), + max: 2.1, + expectError: true, + }, + "unknown lower bound exclusive - valid greater than bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, false), + max: 3, + }, + // Unknown value will be >= 2.1 + "unknown lower bound inclusive - invalid less than bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, true), + max: 2, + expectError: true, + }, + "unknown lower bound inclusive - valid matches bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, true), + max: 2.1, + }, + "unknown lower bound inclusive - valid greater than bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, true), + max: 3, + }, } for name, test := range tests { diff --git a/float64validator/between.go b/float64validator/between.go index 5afdab37..7c319a60 100644 --- a/float64validator/between.go +++ b/float64validator/between.go @@ -47,7 +47,42 @@ func (v betweenValidator) ValidateFloat64(ctx context.Context, request validator return } - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %f", refn.LowerBound()), + )) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %f", refn.LowerBound()), + )) + } + } + + if refn, ok := request.ConfigValue.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %f", refn.UpperBound()), + )) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %f", refn.UpperBound()), + )) + } + } return } @@ -74,7 +109,42 @@ func (v betweenValidator) ValidateParameterFloat64(ctx context.Context, request return } - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %f", refn.LowerBound()), + ) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %f", refn.LowerBound()), + ) + } + } + + if refn, ok := request.Value.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %f", refn.UpperBound()), + ) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %f", refn.UpperBound()), + ) + } + } return } diff --git a/float64validator/between_test.go b/float64validator/between_test.go index d9dbd4bc..67b1a2a9 100644 --- a/float64validator/between_test.go +++ b/float64validator/between_test.go @@ -73,6 +73,76 @@ func TestBetweenValidator(t *testing.T) { max: 3.10, expectError: true, }, + // Unknown value will be > 2.1 + "unknown lower bound exclusive - invalid less than bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, false), + min: 1, + max: 1, + expectError: true, + }, + "unknown lower bound exclusive - invalid matches bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, false), + min: 1, + max: 2.1, + expectError: true, + }, + "unknown lower bound exclusive - valid greater than bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, false), + min: 1, + max: 3, + }, + // Unknown value will be >= 2.1 + "unknown lower bound inclusive - invalid less than bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, true), + min: 1, + max: 1, + expectError: true, + }, + "unknown lower bound inclusive - valid matches bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, true), + min: 1, + max: 2.1, + }, + "unknown lower bound inclusive - valid greater than bound": { + val: types.Float64Unknown().RefineWithLowerBound(2.1, true), + min: 1, + max: 3, + }, + // Unknown value will be < 2.1 + "unknown upper bound exclusive - valid less than bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, false), + min: 1, + max: 5, + }, + "unknown upper bound exclusive - invalid matches bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, false), + min: 2.1, + max: 5, + expectError: true, + }, + "unknown upper bound exclusive - invalid greater than bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, false), + min: 3, + max: 5, + expectError: true, + }, + // Unknown value will be <= 2.1 + "unknown upper bound inclusive - valid less than bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, true), + min: 1, + max: 5, + }, + "unknown upper bound inclusive - valid matches bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, true), + min: 2.1, + max: 5, + }, + "unknown upper bound inclusive - invalid greater than bound": { + val: types.Float64Unknown().RefineWithUpperBound(2.1, true), + min: 3, + max: 5, + expectError: true, + }, } for name, test := range tests { From d13d0cabeabaacace7630518daa896bd9b37e1f0 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 3 Dec 2024 17:17:37 -0500 Subject: [PATCH 07/12] float32 validators --- float32validator/at_least.go | 42 +++++++++++++++++- float32validator/at_least_test.go | 29 ++++++++++++ float32validator/at_most.go | 42 +++++++++++++++++- float32validator/at_most_test.go | 29 ++++++++++++ float32validator/between.go | 74 ++++++++++++++++++++++++++++++- float32validator/between_test.go | 70 +++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+), 6 deletions(-) diff --git a/float32validator/at_least.go b/float32validator/at_least.go index 9a8eb82e..efc67191 100644 --- a/float32validator/at_least.go +++ b/float32validator/at_least.go @@ -30,7 +30,26 @@ func (validator atLeastValidator) MarkdownDescription(ctx context.Context) strin } func (validator atLeastValidator) ValidateFloat32(ctx context.Context, request validator.Float32Request, response *validator.Float32Response) { - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < validator.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + validator.Description(ctx), + fmt.Sprintf("unknown value that will be at most %f", refn.UpperBound()), + )) + } else if !refn.IsInclusive() && refn.UpperBound() <= validator.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + validator.Description(ctx), + fmt.Sprintf("unknown value that will be less than %f", refn.UpperBound()), + )) + } + } return } @@ -46,7 +65,26 @@ func (validator atLeastValidator) ValidateFloat32(ctx context.Context, request v } func (validator atLeastValidator) ValidateParameterFloat32(ctx context.Context, request function.Float32ParameterValidatorRequest, response *function.Float32ParameterValidatorResponse) { - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < validator.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + validator.Description(ctx), + fmt.Sprintf("unknown value that will be at most %f", refn.UpperBound()), + ) + } else if !refn.IsInclusive() && refn.UpperBound() <= validator.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + validator.Description(ctx), + fmt.Sprintf("unknown value that will be less than %f", refn.UpperBound()), + ) + } + } return } diff --git a/float32validator/at_least_test.go b/float32validator/at_least_test.go index ea768381..23172744 100644 --- a/float32validator/at_least_test.go +++ b/float32validator/at_least_test.go @@ -50,6 +50,35 @@ func TestAtLeastValidator(t *testing.T) { min: 0.90, expectError: true, }, + // Unknown value will be < 2.1 + "unknown upper bound exclusive - valid less than bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, false), + min: 2, + }, + "unknown upper bound exclusive - invalid matches bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, false), + min: 2.1, + expectError: true, + }, + "unknown upper bound exclusive - invalid greater than bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, false), + min: 3, + expectError: true, + }, + // Unknown value will be <= 2.1 + "unknown upper bound inclusive - valid less than bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, true), + min: 2, + }, + "unknown upper bound inclusive - valid matches bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, true), + min: 2.1, + }, + "unknown upper bound inclusive - invalid greater than bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, true), + min: 3, + expectError: true, + }, } for name, test := range tests { diff --git a/float32validator/at_most.go b/float32validator/at_most.go index 0fdf981b..2a799bfe 100644 --- a/float32validator/at_most.go +++ b/float32validator/at_most.go @@ -30,7 +30,26 @@ func (validator atMostValidator) MarkdownDescription(ctx context.Context) string } func (v atMostValidator) ValidateFloat32(ctx context.Context, request validator.Float32Request, response *validator.Float32Response) { - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %f", refn.LowerBound()), + )) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %f", refn.LowerBound()), + )) + } + } return } @@ -46,7 +65,26 @@ func (v atMostValidator) ValidateFloat32(ctx context.Context, request validator. } func (v atMostValidator) ValidateParameterFloat32(ctx context.Context, request function.Float32ParameterValidatorRequest, response *function.Float32ParameterValidatorResponse) { - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %f", refn.LowerBound()), + ) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %f", refn.LowerBound()), + ) + } + } return } diff --git a/float32validator/at_most_test.go b/float32validator/at_most_test.go index b3915037..172c27d1 100644 --- a/float32validator/at_most_test.go +++ b/float32validator/at_most_test.go @@ -50,6 +50,35 @@ func TestAtMostValidator(t *testing.T) { max: 2.00, expectError: true, }, + // Unknown value will be > 2.1 + "unknown lower bound exclusive - invalid less than bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, false), + max: 2, + expectError: true, + }, + "unknown lower bound exclusive - invalid matches bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, false), + max: 2.1, + expectError: true, + }, + "unknown lower bound exclusive - valid greater than bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, false), + max: 3, + }, + // Unknown value will be >= 2.1 + "unknown lower bound inclusive - invalid less than bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, true), + max: 2, + expectError: true, + }, + "unknown lower bound inclusive - valid matches bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, true), + max: 2.1, + }, + "unknown lower bound inclusive - valid greater than bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, true), + max: 3, + }, } for name, test := range tests { diff --git a/float32validator/between.go b/float32validator/between.go index a70cd8ca..1b21f689 100644 --- a/float32validator/between.go +++ b/float32validator/between.go @@ -47,7 +47,42 @@ func (v betweenValidator) ValidateFloat32(ctx context.Context, request validator return } - if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + if request.ConfigValue.IsNull() { + return + } + + if request.ConfigValue.IsUnknown() { + if refn, ok := request.ConfigValue.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %f", refn.LowerBound()), + )) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %f", refn.LowerBound()), + )) + } + } + + if refn, ok := request.ConfigValue.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %f", refn.UpperBound()), + )) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %f", refn.UpperBound()), + )) + } + } return } @@ -74,7 +109,42 @@ func (v betweenValidator) ValidateParameterFloat32(ctx context.Context, request return } - if request.Value.IsNull() || request.Value.IsUnknown() { + if request.Value.IsNull() { + return + } + + if request.Value.IsUnknown() { + if refn, ok := request.Value.LowerBoundRefinement(); ok { + if refn.IsInclusive() && refn.LowerBound() > v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at least %f", refn.LowerBound()), + ) + } else if !refn.IsInclusive() && refn.LowerBound() >= v.max { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be greater than %f", refn.LowerBound()), + ) + } + } + + if refn, ok := request.Value.UpperBoundRefinement(); ok { + if refn.IsInclusive() && refn.UpperBound() < v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be at most %f", refn.UpperBound()), + ) + } else if !refn.IsInclusive() && refn.UpperBound() <= v.min { + response.Error = validatorfuncerr.InvalidParameterValueFuncError( + request.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will be less than %f", refn.UpperBound()), + ) + } + } return } diff --git a/float32validator/between_test.go b/float32validator/between_test.go index be0c11d4..c60f2445 100644 --- a/float32validator/between_test.go +++ b/float32validator/between_test.go @@ -74,6 +74,76 @@ func TestBetweenValidator(t *testing.T) { max: 3.10, expectError: true, }, + // Unknown value will be > 2.1 + "unknown lower bound exclusive - invalid less than bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, false), + min: 1, + max: 1, + expectError: true, + }, + "unknown lower bound exclusive - invalid matches bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, false), + min: 1, + max: 2.1, + expectError: true, + }, + "unknown lower bound exclusive - valid greater than bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, false), + min: 1, + max: 3, + }, + // Unknown value will be >= 2.1 + "unknown lower bound inclusive - invalid less than bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, true), + min: 1, + max: 1, + expectError: true, + }, + "unknown lower bound inclusive - valid matches bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, true), + min: 1, + max: 2.1, + }, + "unknown lower bound inclusive - valid greater than bound": { + val: types.Float32Unknown().RefineWithLowerBound(2.1, true), + min: 1, + max: 3, + }, + // Unknown value will be < 2.1 + "unknown upper bound exclusive - valid less than bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, false), + min: 1, + max: 5, + }, + "unknown upper bound exclusive - invalid matches bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, false), + min: 2.1, + max: 5, + expectError: true, + }, + "unknown upper bound exclusive - invalid greater than bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, false), + min: 3, + max: 5, + expectError: true, + }, + // Unknown value will be <= 2.1 + "unknown upper bound inclusive - valid less than bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, true), + min: 1, + max: 5, + }, + "unknown upper bound inclusive - valid matches bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, true), + min: 2.1, + max: 5, + }, + "unknown upper bound inclusive - invalid greater than bound": { + val: types.Float32Unknown().RefineWithUpperBound(2.1, true), + min: 3, + max: 5, + expectError: true, + }, } for name, test := range tests { From 78d2949ae8a38332f8c8c849c9c77bdba8b20f7e Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 3 Dec 2024 17:39:57 -0500 Subject: [PATCH 08/12] list validators --- listvalidator/size_at_least.go | 26 ++++++++++++++++-- listvalidator/size_at_least_test.go | 14 ++++++++++ listvalidator/size_at_most.go | 26 ++++++++++++++++-- listvalidator/size_at_most_test.go | 14 ++++++++++ listvalidator/size_between.go | 42 +++++++++++++++++++++++++++-- listvalidator/size_between_test.go | 34 +++++++++++++++++++++++ 6 files changed, 150 insertions(+), 6 deletions(-) diff --git a/listvalidator/size_at_least.go b/listvalidator/size_at_least.go index 54f86bea..7d49a60f 100644 --- a/listvalidator/size_at_least.go +++ b/listvalidator/size_at_least.go @@ -30,7 +30,18 @@ func (v sizeAtLeastValidator) MarkdownDescription(ctx context.Context) string { } func (v sizeAtLeastValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + if req.ConfigValue.IsNull() { + return + } + + if req.ConfigValue.IsUnknown() { + if refn, ok := req.ConfigValue.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + )) + } return } @@ -46,7 +57,18 @@ func (v sizeAtLeastValidator) ValidateList(ctx context.Context, req validator.Li } func (v sizeAtLeastValidator) ValidateParameterList(ctx context.Context, req function.ListParameterValidatorRequest, resp *function.ListParameterValidatorResponse) { - if req.Value.IsNull() || req.Value.IsUnknown() { + if req.Value.IsNull() { + return + } + + if req.Value.IsUnknown() { + if refn, ok := req.Value.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + ) + } return } diff --git a/listvalidator/size_at_least_test.go b/listvalidator/size_at_least_test.go index ffb17606..601e942e 100644 --- a/listvalidator/size_at_least_test.go +++ b/listvalidator/size_at_least_test.go @@ -65,6 +65,20 @@ func TestSizeAtLeastValidator(t *testing.T) { min: 1, expectError: true, }, + // Unknown value will have <= 2 elements + "unknown length upper bound - valid less than bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 1, + }, + "unknown length upper bound - valid matches bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 2, + }, + "unknown length upper bound - invalid greater than bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 3, + expectError: true, + }, } for name, test := range tests { diff --git a/listvalidator/size_at_most.go b/listvalidator/size_at_most.go index 0ff5ed24..2fa63a93 100644 --- a/listvalidator/size_at_most.go +++ b/listvalidator/size_at_most.go @@ -30,7 +30,18 @@ func (v sizeAtMostValidator) MarkdownDescription(ctx context.Context) string { } func (v sizeAtMostValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + if req.ConfigValue.IsNull() { + return + } + + if req.ConfigValue.IsUnknown() { + if refn, ok := req.ConfigValue.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + )) + } return } @@ -46,7 +57,18 @@ func (v sizeAtMostValidator) ValidateList(ctx context.Context, req validator.Lis } func (v sizeAtMostValidator) ValidateParameterList(ctx context.Context, req function.ListParameterValidatorRequest, resp *function.ListParameterValidatorResponse) { - if req.Value.IsNull() || req.Value.IsUnknown() { + if req.Value.IsNull() { + return + } + + if req.Value.IsUnknown() { + if refn, ok := req.Value.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + ) + } return } diff --git a/listvalidator/size_at_most_test.go b/listvalidator/size_at_most_test.go index a8cad9ac..c2f3b4b7 100644 --- a/listvalidator/size_at_most_test.go +++ b/listvalidator/size_at_most_test.go @@ -69,6 +69,20 @@ func TestSizeAtMostValidator(t *testing.T) { max: 2, expectError: true, }, + // Unknown value will have >= 2 elements + "unknown length lower bound - invalid less than bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthLowerBound(2), + max: 1, + expectError: true, + }, + "unknown length lower bound - valid matches bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthLowerBound(2), + max: 2, + }, + "unknown length lower bound - valid greater than bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthLowerBound(2), + max: 3, + }, } for name, test := range tests { diff --git a/listvalidator/size_between.go b/listvalidator/size_between.go index cab9c9dc..e47faef5 100644 --- a/listvalidator/size_between.go +++ b/listvalidator/size_between.go @@ -31,7 +31,26 @@ func (v sizeBetweenValidator) MarkdownDescription(ctx context.Context) string { } func (v sizeBetweenValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + if req.ConfigValue.IsNull() { + return + } + + if req.ConfigValue.IsUnknown() { + if refn, ok := req.ConfigValue.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + )) + } + + if refn, ok := req.ConfigValue.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + )) + } return } @@ -47,7 +66,26 @@ func (v sizeBetweenValidator) ValidateList(ctx context.Context, req validator.Li } func (v sizeBetweenValidator) ValidateParameterList(ctx context.Context, req function.ListParameterValidatorRequest, resp *function.ListParameterValidatorResponse) { - if req.Value.IsNull() || req.Value.IsUnknown() { + if req.Value.IsNull() { + return + } + + if req.Value.IsUnknown() { + if refn, ok := req.Value.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + ) + } + + if refn, ok := req.Value.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + ) + } return } diff --git a/listvalidator/size_between_test.go b/listvalidator/size_between_test.go index 1b35c9d8..3afe049f 100644 --- a/listvalidator/size_between_test.go +++ b/listvalidator/size_between_test.go @@ -108,6 +108,40 @@ func TestSizeBetweenValidator(t *testing.T) { max: 3, expectError: true, }, + // Unknown value will have >= 2 elements + "unknown length lower bound - invalid less than bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthLowerBound(2), + min: 1, + max: 1, + expectError: true, + }, + "unknown length lower bound - valid matches bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthLowerBound(2), + min: 1, + max: 2, + }, + "unknown length lower bound - valid greater than bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthLowerBound(2), + min: 1, + max: 3, + }, + // Unknown value will have <= 2 elements + "unknown length upper bound - valid less than bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 1, + max: 5, + }, + "unknown length upper bound - valid matches bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 2, + max: 5, + }, + "unknown length upper bound - invalid greater than bound": { + val: types.ListUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 3, + max: 5, + expectError: true, + }, } for name, test := range tests { From ca4f8dc39811bc972d619f13df9f25af5f19fc8c Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 3 Dec 2024 17:43:57 -0500 Subject: [PATCH 09/12] map validators --- mapvalidator/size_at_least.go | 26 ++++++++++++++++-- mapvalidator/size_at_least_test.go | 14 ++++++++++ mapvalidator/size_at_most.go | 26 ++++++++++++++++-- mapvalidator/size_at_most_test.go | 14 ++++++++++ mapvalidator/size_between.go | 42 ++++++++++++++++++++++++++++-- mapvalidator/size_between_test.go | 34 ++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 6 deletions(-) diff --git a/mapvalidator/size_at_least.go b/mapvalidator/size_at_least.go index 8c9ff5a8..c7f9d512 100644 --- a/mapvalidator/size_at_least.go +++ b/mapvalidator/size_at_least.go @@ -30,7 +30,18 @@ func (v sizeAtLeastValidator) MarkdownDescription(ctx context.Context) string { } func (v sizeAtLeastValidator) ValidateMap(ctx context.Context, req validator.MapRequest, resp *validator.MapResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + if req.ConfigValue.IsNull() { + return + } + + if req.ConfigValue.IsUnknown() { + if refn, ok := req.ConfigValue.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + )) + } return } @@ -46,7 +57,18 @@ func (v sizeAtLeastValidator) ValidateMap(ctx context.Context, req validator.Map } func (v sizeAtLeastValidator) ValidateParameterMap(ctx context.Context, req function.MapParameterValidatorRequest, resp *function.MapParameterValidatorResponse) { - if req.Value.IsNull() || req.Value.IsUnknown() { + if req.Value.IsNull() { + return + } + + if req.Value.IsUnknown() { + if refn, ok := req.Value.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + ) + } return } diff --git a/mapvalidator/size_at_least_test.go b/mapvalidator/size_at_least_test.go index 969b3c8a..ee88da33 100644 --- a/mapvalidator/size_at_least_test.go +++ b/mapvalidator/size_at_least_test.go @@ -65,6 +65,20 @@ func TestSizeAtLeastValidator(t *testing.T) { min: 1, expectError: true, }, + // Unknown value will have <= 2 elements + "unknown length upper bound - valid less than bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 1, + }, + "unknown length upper bound - valid matches bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 2, + }, + "unknown length upper bound - invalid greater than bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 3, + expectError: true, + }, } for name, test := range tests { diff --git a/mapvalidator/size_at_most.go b/mapvalidator/size_at_most.go index 82fe3ef1..f6c878db 100644 --- a/mapvalidator/size_at_most.go +++ b/mapvalidator/size_at_most.go @@ -30,7 +30,18 @@ func (v sizeAtMostValidator) MarkdownDescription(ctx context.Context) string { } func (v sizeAtMostValidator) ValidateMap(ctx context.Context, req validator.MapRequest, resp *validator.MapResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + if req.ConfigValue.IsNull() { + return + } + + if req.ConfigValue.IsUnknown() { + if refn, ok := req.ConfigValue.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + )) + } return } @@ -46,7 +57,18 @@ func (v sizeAtMostValidator) ValidateMap(ctx context.Context, req validator.MapR } func (v sizeAtMostValidator) ValidateParameterMap(ctx context.Context, req function.MapParameterValidatorRequest, resp *function.MapParameterValidatorResponse) { - if req.Value.IsNull() || req.Value.IsUnknown() { + if req.Value.IsNull() { + return + } + + if req.Value.IsUnknown() { + if refn, ok := req.Value.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + ) + } return } diff --git a/mapvalidator/size_at_most_test.go b/mapvalidator/size_at_most_test.go index 5886bae6..41b9b9f3 100644 --- a/mapvalidator/size_at_most_test.go +++ b/mapvalidator/size_at_most_test.go @@ -69,6 +69,20 @@ func TestSizeAtMostValidator(t *testing.T) { max: 2, expectError: true, }, + // Unknown value will have >= 2 elements + "unknown length lower bound - invalid less than bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthLowerBound(2), + max: 1, + expectError: true, + }, + "unknown length lower bound - valid matches bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthLowerBound(2), + max: 2, + }, + "unknown length lower bound - valid greater than bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthLowerBound(2), + max: 3, + }, } for name, test := range tests { diff --git a/mapvalidator/size_between.go b/mapvalidator/size_between.go index 78e12fb3..dafdfa50 100644 --- a/mapvalidator/size_between.go +++ b/mapvalidator/size_between.go @@ -31,7 +31,26 @@ func (v sizeBetweenValidator) MarkdownDescription(ctx context.Context) string { } func (v sizeBetweenValidator) ValidateMap(ctx context.Context, req validator.MapRequest, resp *validator.MapResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + if req.ConfigValue.IsNull() { + return + } + + if req.ConfigValue.IsUnknown() { + if refn, ok := req.ConfigValue.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + )) + } + + if refn, ok := req.ConfigValue.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + )) + } return } @@ -47,7 +66,26 @@ func (v sizeBetweenValidator) ValidateMap(ctx context.Context, req validator.Map } func (v sizeBetweenValidator) ValidateParameterMap(ctx context.Context, req function.MapParameterValidatorRequest, resp *function.MapParameterValidatorResponse) { - if req.Value.IsNull() || req.Value.IsUnknown() { + if req.Value.IsNull() { + return + } + + if req.Value.IsUnknown() { + if refn, ok := req.Value.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + ) + } + + if refn, ok := req.Value.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + ) + } return } diff --git a/mapvalidator/size_between_test.go b/mapvalidator/size_between_test.go index d95634c7..308b5dbc 100644 --- a/mapvalidator/size_between_test.go +++ b/mapvalidator/size_between_test.go @@ -108,6 +108,40 @@ func TestSizeBetweenValidator(t *testing.T) { max: 3, expectError: true, }, + // Unknown value will have >= 2 elements + "unknown length lower bound - invalid less than bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthLowerBound(2), + min: 1, + max: 1, + expectError: true, + }, + "unknown length lower bound - valid matches bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthLowerBound(2), + min: 1, + max: 2, + }, + "unknown length lower bound - valid greater than bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthLowerBound(2), + min: 1, + max: 3, + }, + // Unknown value will have <= 2 elements + "unknown length upper bound - valid less than bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 1, + max: 5, + }, + "unknown length upper bound - valid matches bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 2, + max: 5, + }, + "unknown length upper bound - invalid greater than bound": { + val: types.MapUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 3, + max: 5, + expectError: true, + }, } for name, test := range tests { From 0ddd14ee75016edd6207efcdfa38e75fcde6c7d2 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 3 Dec 2024 17:48:27 -0500 Subject: [PATCH 10/12] set validators --- setvalidator/size_at_least.go | 26 ++++++++++++++++-- setvalidator/size_at_least_test.go | 14 ++++++++++ setvalidator/size_at_most.go | 26 ++++++++++++++++-- setvalidator/size_at_most_test.go | 14 ++++++++++ setvalidator/size_between.go | 42 ++++++++++++++++++++++++++++-- setvalidator/size_between_test.go | 34 ++++++++++++++++++++++++ 6 files changed, 150 insertions(+), 6 deletions(-) diff --git a/setvalidator/size_at_least.go b/setvalidator/size_at_least.go index 7af58b00..21a0c28c 100644 --- a/setvalidator/size_at_least.go +++ b/setvalidator/size_at_least.go @@ -30,7 +30,18 @@ func (v sizeAtLeastValidator) MarkdownDescription(ctx context.Context) string { } func (v sizeAtLeastValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + if req.ConfigValue.IsNull() { + return + } + + if req.ConfigValue.IsUnknown() { + if refn, ok := req.ConfigValue.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + )) + } return } @@ -46,7 +57,18 @@ func (v sizeAtLeastValidator) ValidateSet(ctx context.Context, req validator.Set } func (v sizeAtLeastValidator) ValidateParameterSet(ctx context.Context, req function.SetParameterValidatorRequest, resp *function.SetParameterValidatorResponse) { - if req.Value.IsNull() || req.Value.IsUnknown() { + if req.Value.IsNull() { + return + } + + if req.Value.IsUnknown() { + if refn, ok := req.Value.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + ) + } return } diff --git a/setvalidator/size_at_least_test.go b/setvalidator/size_at_least_test.go index e76da98f..a3da1404 100644 --- a/setvalidator/size_at_least_test.go +++ b/setvalidator/size_at_least_test.go @@ -65,6 +65,20 @@ func TestSizeAtLeastValidator(t *testing.T) { min: 1, expectError: true, }, + // Unknown value will have <= 2 elements + "unknown length upper bound - valid less than bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 1, + }, + "unknown length upper bound - valid matches bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 2, + }, + "unknown length upper bound - invalid greater than bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 3, + expectError: true, + }, } for name, test := range tests { diff --git a/setvalidator/size_at_most.go b/setvalidator/size_at_most.go index e0762acb..b65fe2cf 100644 --- a/setvalidator/size_at_most.go +++ b/setvalidator/size_at_most.go @@ -30,7 +30,18 @@ func (v sizeAtMostValidator) MarkdownDescription(ctx context.Context) string { } func (v sizeAtMostValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + if req.ConfigValue.IsNull() { + return + } + + if req.ConfigValue.IsUnknown() { + if refn, ok := req.ConfigValue.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + )) + } return } @@ -46,7 +57,18 @@ func (v sizeAtMostValidator) ValidateSet(ctx context.Context, req validator.SetR } func (v sizeAtMostValidator) ValidateParameterSet(ctx context.Context, req function.SetParameterValidatorRequest, resp *function.SetParameterValidatorResponse) { - if req.Value.IsNull() || req.Value.IsUnknown() { + if req.Value.IsNull() { + return + } + + if req.Value.IsUnknown() { + if refn, ok := req.Value.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + ) + } return } diff --git a/setvalidator/size_at_most_test.go b/setvalidator/size_at_most_test.go index 0cebc4d7..4acddddb 100644 --- a/setvalidator/size_at_most_test.go +++ b/setvalidator/size_at_most_test.go @@ -69,6 +69,20 @@ func TestSizeAtMostValidator(t *testing.T) { max: 2, expectError: true, }, + // Unknown value will have >= 2 elements + "unknown length lower bound - invalid less than bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthLowerBound(2), + max: 1, + expectError: true, + }, + "unknown length lower bound - valid matches bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthLowerBound(2), + max: 2, + }, + "unknown length lower bound - valid greater than bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthLowerBound(2), + max: 3, + }, } for name, test := range tests { diff --git a/setvalidator/size_between.go b/setvalidator/size_between.go index 9dd87044..2c5b07ea 100644 --- a/setvalidator/size_between.go +++ b/setvalidator/size_between.go @@ -31,7 +31,26 @@ func (v sizeBetweenValidator) MarkdownDescription(ctx context.Context) string { } func (v sizeBetweenValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + if req.ConfigValue.IsNull() { + return + } + + if req.ConfigValue.IsUnknown() { + if refn, ok := req.ConfigValue.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + )) + } + + if refn, ok := req.ConfigValue.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + )) + } return } @@ -47,7 +66,26 @@ func (v sizeBetweenValidator) ValidateSet(ctx context.Context, req validator.Set } func (v sizeBetweenValidator) ValidateParameterSet(ctx context.Context, req function.SetParameterValidatorRequest, resp *function.SetParameterValidatorResponse) { - if req.Value.IsNull() || req.Value.IsUnknown() { + if req.Value.IsNull() { + return + } + + if req.Value.IsUnknown() { + if refn, ok := req.Value.LengthLowerBoundRefinement(); ok && refn.LowerBound() > int64(v.max) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at least %d elements", refn.LowerBound()), + ) + } + + if refn, ok := req.Value.LengthUpperBoundRefinement(); ok && refn.UpperBound() < int64(v.min) { + resp.Error = validatorfuncerr.InvalidParameterValueFuncError( + req.ArgumentPosition, + v.Description(ctx), + fmt.Sprintf("unknown value that will have at most %d elements", refn.UpperBound()), + ) + } return } diff --git a/setvalidator/size_between_test.go b/setvalidator/size_between_test.go index 0b24e7e4..1f9e1cce 100644 --- a/setvalidator/size_between_test.go +++ b/setvalidator/size_between_test.go @@ -108,6 +108,40 @@ func TestSizeBetweenValidator(t *testing.T) { max: 3, expectError: true, }, + // Unknown value will have >= 2 elements + "unknown length lower bound - invalid less than bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthLowerBound(2), + min: 1, + max: 1, + expectError: true, + }, + "unknown length lower bound - valid matches bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthLowerBound(2), + min: 1, + max: 2, + }, + "unknown length lower bound - valid greater than bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthLowerBound(2), + min: 1, + max: 3, + }, + // Unknown value will have <= 2 elements + "unknown length upper bound - valid less than bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 1, + max: 5, + }, + "unknown length upper bound - valid matches bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 2, + max: 5, + }, + "unknown length upper bound - invalid greater than bound": { + val: types.SetUnknown(types.StringType).RefineWithLengthUpperBound(2), + min: 3, + max: 5, + expectError: true, + }, } for name, test := range tests { From 94142f54baadeb130fbcb00958d9bb4e22e2baba Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 4 Dec 2024 12:25:44 -0500 Subject: [PATCH 11/12] add schema and config validators --- internal/configvalidator/conflicting.go | 17 +++- internal/configvalidator/conflicting_test.go | 95 +++++++++++++++++++ internal/configvalidator/exactly_one_of.go | 26 +++-- .../configvalidator/exactly_one_of_test.go | 95 +++++++++++++++++++ internal/schemavalidator/conflicts_with.go | 15 ++- .../schemavalidator/conflicts_with_test.go | 37 ++++++++ internal/schemavalidator/exactly_one_of.go | 25 ++++- .../schemavalidator/exactly_one_of_test.go | 94 ++++++++++++++++++ 8 files changed, 390 insertions(+), 14 deletions(-) diff --git a/internal/configvalidator/conflicting.go b/internal/configvalidator/conflicting.go index 38dfd5c4..2947b47f 100644 --- a/internal/configvalidator/conflicting.go +++ b/internal/configvalidator/conflicting.go @@ -76,8 +76,21 @@ func (v ConflictingValidator) Validate(ctx context.Context, config tfsdk.Config) continue } - // Value must not be null or unknown to trigger validation error - if value.IsNull() || value.IsUnknown() { + // Value must not be null or fully unknown to trigger validation error + if value.IsNull() { + continue + } + + if value.IsUnknown() { + // If the unknown value will eventually be not null, we add it to the + // configured paths to potentially trigger a validation error + val, ok := value.(attr.ValueWithNotNullRefinement) + if ok { + if _, notNull := val.NotNullRefinement(); notNull { + configuredPaths.Append(matchedPath) + } + } + continue } diff --git a/internal/configvalidator/conflicting_test.go b/internal/configvalidator/conflicting_test.go index 1f513f27..d1f5acd5 100644 --- a/internal/configvalidator/conflicting_test.go +++ b/internal/configvalidator/conflicting_test.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + tfrefinement "github.com/hashicorp/terraform-plugin-go/tftypes/refinement" "github.com/hashicorp/terraform-plugin-framework-validators/internal/configvalidator" ) @@ -329,6 +330,52 @@ func TestConflictingValidatorValidate(t *testing.T) { }, expected: nil, }, + "two-matching-path-expression-one-notnull-unknown-one-value": { + validator: configvalidator.ConflictingValidator{ + PathExpressions: path.Expressions{ + path.MatchRoot("test1"), + path.MatchRoot("test2"), + }, + }, + config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "test1": schema.StringAttribute{ + Optional: true, + }, + "test2": schema.StringAttribute{ + Optional: true, + }, + "other": schema.StringAttribute{ + Optional: true, + }, + }, + }, + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test1": tftypes.String, + "test2": tftypes.String, + "other": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "test1": tftypes.NewValue(tftypes.String, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + "test2": tftypes.NewValue(tftypes.String, "test-value"), + "other": tftypes.NewValue(tftypes.String, "test-value"), + }, + ), + }, + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Root("test1"), + "Invalid Attribute Combination", + "These attributes cannot be configured together: [test1,test2]", + ), + }, + }, "two-matching-path-expression-two-null": { validator: configvalidator.ConflictingValidator{ PathExpressions: path.Expressions{ @@ -405,6 +452,54 @@ func TestConflictingValidatorValidate(t *testing.T) { }, expected: nil, }, + "two-matching-path-expression-two-notnull-unknown": { + validator: configvalidator.ConflictingValidator{ + PathExpressions: path.Expressions{ + path.MatchRoot("test1"), + path.MatchRoot("test2"), + }, + }, + config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "test1": schema.StringAttribute{ + Optional: true, + }, + "test2": schema.StringAttribute{ + Optional: true, + }, + "other": schema.StringAttribute{ + Optional: true, + }, + }, + }, + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test1": tftypes.String, + "test2": tftypes.String, + "other": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "test1": tftypes.NewValue(tftypes.String, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + "test2": tftypes.NewValue(tftypes.String, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + "other": tftypes.NewValue(tftypes.String, "test-value"), + }, + ), + }, + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Root("test1"), + "Invalid Attribute Combination", + "These attributes cannot be configured together: [test1,test2]", + ), + }, + }, "two-matching-path-expression-two-value": { validator: configvalidator.ConflictingValidator{ PathExpressions: path.Expressions{ diff --git a/internal/configvalidator/exactly_one_of.go b/internal/configvalidator/exactly_one_of.go index 14904d5a..be3ccf88 100644 --- a/internal/configvalidator/exactly_one_of.go +++ b/internal/configvalidator/exactly_one_of.go @@ -76,17 +76,27 @@ func (v ExactlyOneOfValidator) Validate(ctx context.Context, config tfsdk.Config continue } - // If value is unknown, it may be null or a value, so we cannot - // know if the validator should succeed or not. Collect the path - // path so we use it to skip the validation later and continue to - // collect all path matching diagnostics. - if value.IsUnknown() { - unknownPaths.Append(matchedPath) + // If value is null, move onto the next one. + if value.IsNull() { continue } - // If value is null, move onto the next one. - if value.IsNull() { + if value.IsUnknown() { + // If the unknown value will eventually be not null, we add it to the + // configured paths to potentially trigger a validation error + val, ok := value.(attr.ValueWithNotNullRefinement) + if ok { + if _, notNull := val.NotNullRefinement(); notNull { + configuredPaths.Append(matchedPath) + continue + } + } + + // If value is fully unknown, it may be null or a value, so we cannot + // know if the validator should succeed or not. Collect the path + // path so we use it to skip the validation later and continue to + // collect all path matching diagnostics. + unknownPaths.Append(matchedPath) continue } diff --git a/internal/configvalidator/exactly_one_of_test.go b/internal/configvalidator/exactly_one_of_test.go index 805c2ed8..b89f9e9b 100644 --- a/internal/configvalidator/exactly_one_of_test.go +++ b/internal/configvalidator/exactly_one_of_test.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + tfrefinement "github.com/hashicorp/terraform-plugin-go/tftypes/refinement" "github.com/hashicorp/terraform-plugin-framework-validators/internal/configvalidator" ) @@ -344,6 +345,52 @@ func TestExactlyOneOfValidatorValidate(t *testing.T) { }, expected: nil, }, + "two-matching-path-expression-one-notnull-unknown-one-value": { + validator: configvalidator.ExactlyOneOfValidator{ + PathExpressions: path.Expressions{ + path.MatchRoot("test1"), + path.MatchRoot("test2"), + }, + }, + config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "test1": schema.StringAttribute{ + Optional: true, + }, + "test2": schema.StringAttribute{ + Optional: true, + }, + "other": schema.StringAttribute{ + Optional: true, + }, + }, + }, + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test1": tftypes.String, + "test2": tftypes.String, + "other": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "test1": tftypes.NewValue(tftypes.String, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + "test2": tftypes.NewValue(tftypes.String, "test-value"), + "other": tftypes.NewValue(tftypes.String, "test-value"), + }, + ), + }, + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Root("test1"), + "Invalid Attribute Combination", + "Exactly one of these attributes must be configured: [test1,test2]", + ), + }, + }, "two-matching-path-expression-two-null": { validator: configvalidator.ExactlyOneOfValidator{ PathExpressions: path.Expressions{ @@ -425,6 +472,54 @@ func TestExactlyOneOfValidatorValidate(t *testing.T) { }, expected: nil, }, + "two-matching-path-expression-two-notnull-unknown": { + validator: configvalidator.ExactlyOneOfValidator{ + PathExpressions: path.Expressions{ + path.MatchRoot("test1"), + path.MatchRoot("test2"), + }, + }, + config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "test1": schema.StringAttribute{ + Optional: true, + }, + "test2": schema.StringAttribute{ + Optional: true, + }, + "other": schema.StringAttribute{ + Optional: true, + }, + }, + }, + Raw: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test1": tftypes.String, + "test2": tftypes.String, + "other": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "test1": tftypes.NewValue(tftypes.String, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + "test2": tftypes.NewValue(tftypes.String, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + "other": tftypes.NewValue(tftypes.String, "test-value"), + }, + ), + }, + expected: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Root("test1"), + "Invalid Attribute Combination", + "Exactly one of these attributes must be configured: [test1,test2]", + ), + }, + }, "two-matching-path-expression-two-value": { validator: configvalidator.ExactlyOneOfValidator{ PathExpressions: path.Expressions{ diff --git a/internal/schemavalidator/conflicts_with.go b/internal/schemavalidator/conflicts_with.go index 185d58e9..3b2261e2 100644 --- a/internal/schemavalidator/conflicts_with.go +++ b/internal/schemavalidator/conflicts_with.go @@ -89,8 +89,21 @@ func (av ConflictsWithValidator) Validate(ctx context.Context, req ConflictsWith continue } - // Delay validation until all involved attribute have a known value + // If value is fully unknown, delay validation until all involved attributes have a known value if mpVal.IsUnknown() { + // If the unknown value will eventually be not null, we can add the diagnostic and continue looping + val, ok := mpVal.(attr.ValueWithNotNullRefinement) + if ok { + if _, notNull := val.NotNullRefinement(); notNull { + res.Diagnostics.Append(validatordiag.InvalidAttributeCombinationDiagnostic( + req.Path, + fmt.Sprintf("Attribute %q cannot be specified when %q is specified", mp, req.Path), + )) + + continue + } + } + return } diff --git a/internal/schemavalidator/conflicts_with_test.go b/internal/schemavalidator/conflicts_with_test.go index 92b9c37b..cfc43561 100644 --- a/internal/schemavalidator/conflicts_with_test.go +++ b/internal/schemavalidator/conflicts_with_test.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" + tfrefinement "github.com/hashicorp/terraform-plugin-go/tftypes/refinement" "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" ) @@ -204,6 +205,42 @@ func TestConflictsWithValidatorValidate(t *testing.T) { }, //expErrors: 2, }, + "error_notnull-unknowns": { + req: schemavalidator.ConflictsWithValidatorRequest{ + ConfigValue: types.StringValue("bar value"), + Path: path.Root("bar"), + PathExpression: path.MatchRoot("bar"), + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "foo": schema.Int64Attribute{}, + "bar": schema.StringAttribute{}, + "baz": schema.Int64Attribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.Number, + "bar": tftypes.String, + "baz": tftypes.Number, + }, + }, map[string]tftypes.Value{ + "foo": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + "bar": tftypes.NewValue(tftypes.String, "bar value"), + "baz": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + }), + }, + }, + in: path.Expressions{ + path.MatchRoot("foo"), + path.MatchRoot("baz"), + }, + expErrors: 2, + }, "matches-no-attribute-in-schema": { req: schemavalidator.ConflictsWithValidatorRequest{ ConfigValue: types.StringValue("bar value"), diff --git a/internal/schemavalidator/exactly_one_of.go b/internal/schemavalidator/exactly_one_of.go index 40af4383..6a434813 100644 --- a/internal/schemavalidator/exactly_one_of.go +++ b/internal/schemavalidator/exactly_one_of.go @@ -59,9 +59,19 @@ func (av ExactlyOneOfValidator) Validate(ctx context.Context, req ExactlyOneOfVa count := 0 expressions := req.PathExpression.MergeExpressions(av.PathExpressions...) - // If current attribute is unknown, delay validation if req.ConfigValue.IsUnknown() { - return + val, ok := req.ConfigValue.(attr.ValueWithNotNullRefinement) + if !ok { + // The value is fully unknown (doesn't support value refinements), delay validation. + return + } + if _, notNull := val.NotNullRefinement(); !notNull { + // The value doesn't have a "not null" refinement, delay validation. + return + } + + // The unknown value will eventually be not null, we can increment the count and continue validating + count++ } // Now that we know the current attribute is known, check whether it is @@ -98,8 +108,17 @@ func (av ExactlyOneOfValidator) Validate(ctx context.Context, req ExactlyOneOfVa continue } - // Delay validation until all involved attribute have a known value + // If value is fully unknown, delay validation until all involved attributes have a known value if mpVal.IsUnknown() { + // If the unknown value will eventually be not null, we can increment the count and continue looping + val, ok := mpVal.(attr.ValueWithNotNullRefinement) + if ok { + if _, notNull := val.NotNullRefinement(); notNull { + count++ + continue + } + } + return } diff --git a/internal/schemavalidator/exactly_one_of_test.go b/internal/schemavalidator/exactly_one_of_test.go index c0c82b6d..13720fd4 100644 --- a/internal/schemavalidator/exactly_one_of_test.go +++ b/internal/schemavalidator/exactly_one_of_test.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-go/tftypes" + tfrefinement "github.com/hashicorp/terraform-plugin-go/tftypes/refinement" "github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator" ) @@ -81,6 +82,63 @@ func TestExactlyOneOfValidator(t *testing.T) { path.MatchRoot("foo"), }, }, + "self-is-fully-unknown": { + req: schemavalidator.ExactlyOneOfValidatorRequest{ + ConfigValue: types.StringUnknown(), + Path: path.Root("bar"), + PathExpression: path.MatchRoot("bar"), + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "foo": schema.Int64Attribute{}, + "bar": schema.StringAttribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.Number, + "bar": tftypes.String, + }, + }, map[string]tftypes.Value{ + "foo": tftypes.NewValue(tftypes.Number, 42), + "bar": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + }, + }, + in: path.Expressions{ + path.MatchRoot("foo"), + }, + }, + "self-is-notnull-unknown": { + req: schemavalidator.ExactlyOneOfValidatorRequest{ + ConfigValue: types.StringUnknown().RefineAsNotNull(), + Path: path.Root("bar"), + PathExpression: path.MatchRoot("bar"), + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "foo": schema.Int64Attribute{}, + "bar": schema.StringAttribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.Number, + "bar": tftypes.String, + }, + }, map[string]tftypes.Value{ + "foo": tftypes.NewValue(tftypes.Number, 42), + "bar": tftypes.NewValue(tftypes.String, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + }), + }, + }, + in: path.Expressions{ + path.MatchRoot("foo"), + }, + expErrors: 1, + }, "error_too-many": { req: schemavalidator.ExactlyOneOfValidatorRequest{ ConfigValue: types.StringValue("bar value"), @@ -113,6 +171,42 @@ func TestExactlyOneOfValidator(t *testing.T) { }, expErrors: 1, }, + "error_too-many-notnull-unknowns": { + req: schemavalidator.ExactlyOneOfValidatorRequest{ + ConfigValue: types.StringValue("bar value"), + Path: path.Root("bar"), + PathExpression: path.MatchRoot("bar"), + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "foo": schema.Int64Attribute{}, + "bar": schema.StringAttribute{}, + "baz": schema.Float64Attribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.Number, + "bar": tftypes.String, + "baz": tftypes.Number, + }, + }, map[string]tftypes.Value{ + "foo": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + "bar": tftypes.NewValue(tftypes.String, "bar value"), + "baz": tftypes.NewValue(tftypes.Number, tftypes.UnknownValue).Refine(tfrefinement.Refinements{ + tfrefinement.KeyNullness: tfrefinement.NewNullness(false), + }), + }), + }, + }, + in: path.Expressions{ + path.MatchRoot("foo"), + path.MatchRoot("baz"), + }, + expErrors: 1, + }, "error_too-few": { req: schemavalidator.ExactlyOneOfValidatorRequest{ ConfigValue: types.StringNull(), From c121e243f9df11827cbc57952d06f60addb7d963 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 16 Jan 2025 14:17:52 -0500 Subject: [PATCH 12/12] update deps --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 988b905e..a09e3a86 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ toolchain go1.22.7 require ( github.com/google/go-cmp v0.6.0 - github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20241203155412-a2d406602012 - github.com/hashicorp/terraform-plugin-go v0.25.1-0.20241126200214-bd716fcfe407 + github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20250116190529-2e147507972f + github.com/hashicorp/terraform-plugin-go v0.25.1-0.20250116190359-f977ddce3f6c ) require ( @@ -19,5 +19,5 @@ require ( github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/sys v0.24.0 // indirect + golang.org/x/sys v0.29.0 // indirect ) diff --git a/go.sum b/go.sum index d376bf87..f40b423e 100644 --- a/go.sum +++ b/go.sum @@ -7,10 +7,10 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20241203155412-a2d406602012 h1:mmXb115JfQLUvlUo86P9Hu5eTNRhs4TsgUihJs67mLM= -github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20241203155412-a2d406602012/go.mod h1:Rdj606MBP6yGeLrPjOEdIFpMsRZWP3E9lzGx++Zth6k= -github.com/hashicorp/terraform-plugin-go v0.25.1-0.20241126200214-bd716fcfe407 h1:oLzKb+YiJIEq0EY3qGgQTxCLW2CaXN1rJp3yg1H11qI= -github.com/hashicorp/terraform-plugin-go v0.25.1-0.20241126200214-bd716fcfe407/go.mod h1:f8P2pHGkZrtdKLpCI2qIvrewUY+c4nTvtayqjJR9IcY= +github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20250116190529-2e147507972f h1:q2bmhRSM68j8z76TygKncKkcXf7N0DJCtyn/FllnXOI= +github.com/hashicorp/terraform-plugin-framework v1.13.1-0.20250116190529-2e147507972f/go.mod h1:zrIM+i1WrpIUn7ui8DDsJVg+nJCmteS/gisopH0M3AE= +github.com/hashicorp/terraform-plugin-go v0.25.1-0.20250116190359-f977ddce3f6c h1:jRBaf696GIfT5LT+ZflOQUWI6MNEdntXFp6YO11ACUU= +github.com/hashicorp/terraform-plugin-go v0.25.1-0.20250116190359-f977ddce3f6c/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -37,8 +37,8 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=