Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introducing schemavalidator package #32

Merged
merged 20 commits into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/32.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
Introduced `schemavalidator` package with 4 new validation functions: `RequiredWith()`, `ConflictsWith()`, `AtLeastOneOf()`, `ExactlyOneOf()`
```
4 changes: 4 additions & 0 deletions .changelog/42.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ int64validator: Added `OneOf()` and `NoneOf()` validation functions
```release-note:feature
Introduced `numbervalidator` package with `OneOf()` and `NoneOf()` validation functions
```

```release-note:breaking-change
Renaming package `validatordiag` to be at `helpers/validatordiag`
```
detro marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 3 additions & 2 deletions float64validator/at_least_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ func TestAtLeastValidator(t *testing.T) {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: path.Root("test"),
AttributeConfig: test.val,
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
float64validator.AtLeast(test.min).Validate(context.TODO(), request, &response)
Expand Down
5 changes: 3 additions & 2 deletions float64validator/at_most_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ func TestAtMostValidator(t *testing.T) {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: path.Root("test"),
AttributeConfig: test.val,
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
float64validator.AtMost(test.max).Validate(context.TODO(), request, &response)
Expand Down
5 changes: 3 additions & 2 deletions float64validator/between_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ func TestBetweenValidator(t *testing.T) {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: path.Root("test"),
AttributeConfig: test.val,
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
float64validator.Between(test.min, test.max).Validate(context.TODO(), request, &response)
Expand Down
7 changes: 3 additions & 4 deletions float64validator/none_of_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"testing"

"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
Expand Down Expand Up @@ -73,12 +72,12 @@ func TestNoneOfValidator(t *testing.T) {
t.Fatalf("expected %d error(s), got none", test.expErrors)
}

if test.expErrors > 0 && test.expErrors != validatordiag.ErrorsCount(res.Diagnostics) {
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
if test.expErrors > 0 && test.expErrors != res.Diagnostics.ErrorsCount() {
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, res.Diagnostics.ErrorsCount(), res.Diagnostics)
}

if test.expErrors == 0 && res.Diagnostics.HasError() {
t.Fatalf("expected no error(s), got %d: %v", validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
t.Fatalf("expected no error(s), got %d: %v", res.Diagnostics.ErrorsCount(), res.Diagnostics)
}
})
}
Expand Down
7 changes: 3 additions & 4 deletions float64validator/one_of_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"testing"

"github.com/hashicorp/terraform-plugin-framework-validators/float64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
Expand Down Expand Up @@ -73,12 +72,12 @@ func TestOneOfValidator(t *testing.T) {
t.Fatalf("expected %d error(s), got none", test.expErrors)
}

if test.expErrors > 0 && test.expErrors != validatordiag.ErrorsCount(res.Diagnostics) {
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
if test.expErrors > 0 && test.expErrors != res.Diagnostics.ErrorsCount() {
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, res.Diagnostics.ErrorsCount(), res.Diagnostics)
}

if test.expErrors == 0 && res.Diagnostics.HasError() {
t.Fatalf("expected no error(s), got %d: %v", validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
t.Fatalf("expected no error(s), got %d: %v", res.Diagnostics.ErrorsCount(), res.Diagnostics)
}
})
}
Expand Down
20 changes: 12 additions & 8 deletions float64validator/type_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,36 @@ func TestValidateFloat(t *testing.T) {
}{
"invalid-type": {
request: tfsdk.ValidateAttributeRequest{
AttributeConfig: types.Bool{Value: true},
AttributePath: path.Root("test"),
AttributeConfig: types.Bool{Value: true},
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
},
expectedFloat64: 0.0,
expectedOk: false,
},
"float64-null": {
request: tfsdk.ValidateAttributeRequest{
AttributeConfig: types.Float64{Null: true},
AttributePath: path.Root("test"),
AttributeConfig: types.Float64{Null: true},
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
},
expectedFloat64: 0.0,
expectedOk: false,
},
"float64-value": {
request: tfsdk.ValidateAttributeRequest{
AttributeConfig: types.Float64{Value: 1.2},
AttributePath: path.Root("test"),
AttributeConfig: types.Float64{Value: 1.2},
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
},
expectedFloat64: 1.2,
expectedOk: true,
},
"float64-unknown": {
request: tfsdk.ValidateAttributeRequest{
AttributeConfig: types.Float64{Unknown: true},
AttributePath: path.Root("test"),
AttributeConfig: types.Float64{Unknown: true},
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
},
expectedFloat64: 0.0,
expectedOk: false,
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ go 1.17

require (
github.com/google/go-cmp v0.5.8
github.com/hashicorp/terraform-plugin-framework v0.9.1-0.20220627174514-5a338a7dd906
github.com/hashicorp/terraform-plugin-framework v0.9.1-0.20220629230053-34bd9b67bff9
github.com/hashicorp/terraform-plugin-go v0.10.0
)

require (
github.com/fatih/color v1.13.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/go-hclog v1.2.1 // indirect
github.com/hashicorp/terraform-plugin-go v0.10.0 // indirect
github.com/hashicorp/terraform-plugin-log v0.4.1 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH
github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/terraform-plugin-framework v0.9.1-0.20220627174514-5a338a7dd906 h1:Y9JWkRpLhbJfdN85Dx+joLWRiwzLlZQkhf1qSbyPJI8=
github.com/hashicorp/terraform-plugin-framework v0.9.1-0.20220627174514-5a338a7dd906/go.mod h1:ActelD2V6yt2m0MwIX4jESGDYJ573rAvZswGjSGm1rY=
github.com/hashicorp/terraform-plugin-framework v0.9.1-0.20220629230053-34bd9b67bff9 h1:VqgfzFc6Vv9kcw5A6rwZuicOjzXl2bqJv7H+suDtysY=
github.com/hashicorp/terraform-plugin-framework v0.9.1-0.20220629230053-34bd9b67bff9/go.mod h1:ActelD2V6yt2m0MwIX4jESGDYJ573rAvZswGjSGm1rY=
github.com/hashicorp/terraform-plugin-go v0.9.1/go.mod h1:ItjVSlQs70otlzcCwlPcU8FRXLdO973oYFRZwAOxy8M=
github.com/hashicorp/terraform-plugin-go v0.10.0 h1:FIQDt/AZDSOXnN+znBnLLZA9aFk4/GwL40rwMLnvuTk=
github.com/hashicorp/terraform-plugin-go v0.10.0/go.mod h1:aphXBG8qtQH0yF1waMRlaw/3G+ZFlR/6Artnvt1QEDE=
Expand Down
30 changes: 30 additions & 0 deletions helpers/pathutils/merge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package pathutils

import (
"github.com/hashicorp/terraform-plugin-framework/path"
)

// MergeExpressionsWithAttribute returns the given path.Expressions,
// but each has been merged with the given attribute path.Expression,
// and then resolved.
//
// Additionally, if the attribute path.Expression was not part of the initial slice,
// it is added to the result.
func MergeExpressionsWithAttribute(pathExps path.Expressions, attrPathExp path.Expression) path.Expressions {
result := make(path.Expressions, 0, len(pathExps)+1)

// First, add the attribute own path expression to the result
result = append(result, attrPathExp)

for _, pe := range pathExps {
mpe := attrPathExp.Merge(pe).Resolve()

// Include the merged path expression,
// only if it's not the same as the attribute
if !mpe.Equal(attrPathExp) {
result = append(result, mpe)
}
}

return result
}
detro marked this conversation as resolved.
Show resolved Hide resolved
47 changes: 47 additions & 0 deletions helpers/pathutils/resolve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package pathutils

import (
"context"
"fmt"

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

// PathMatchExpressionsAgainstAttributeConfig returns the path.Paths matching the given path.Expressions.
//
// Each path.Expression has been merged with the given attribute path.Expression
// (likely from the tfsdk.ValidateAttributeRequest), resolved,
// and then matched against the given attribute tfsdk.Config (also from the tfsdk.ValidateAttributeRequest).
//
// This is useful for tfsdk.AttributeValidator that accept path.Expressions, and validate the attributes matching
// to the expressions, in relation to the attribute the validator is applied to.
// For example usage, please look at the `schemavalidator` package in this repository.
func PathMatchExpressionsAgainstAttributeConfig(ctx context.Context, pathExps path.Expressions, attrPathExp path.Expression, attrConfig tfsdk.Config) (path.Paths, diag.Diagnostics) {
var resDiags diag.Diagnostics

pathExpressions := MergeExpressionsWithAttribute(pathExps, attrPathExp)

resPaths := make(path.Paths, 0, len(pathExpressions))

for _, pe := range pathExpressions {
// Retrieve all the attribute paths that match the given expressions
matchingPaths, diags := attrConfig.PathMatches(ctx, pe)
resDiags.Append(diags...)
if diags.HasError() {
return nil, resDiags
}

// Confirm at least one attribute was matched.
// If not, collect errors so that the callee can bubble the bugs up.
if len(matchingPaths) == 0 {
resDiags.Append(validatordiag.BugInProviderDiagnostic(fmt.Sprintf("Path expression %q matches no attribute", pe)))
}

resPaths = append(resPaths, matchingPaths...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the above comment, this makes me think we should have (path.Paths).Append(), if for nothing else but potential deduplication. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally on board if we want to add that to the types Paths and Expressions. And yes, it should do the extra bit of "deduplicating".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

return resPaths, resDiags
}
32 changes: 6 additions & 26 deletions helpers/validatordiag/diag.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ func InvalidAttributeValueMatchDiagnostic(path path.Path, description string, va
)
}

// InvalidAttributeSchemaDiagnostic returns an error Diagnostic to be used when a schemavalidator of attributes is invalid.
func InvalidAttributeSchemaDiagnostic(path path.Path, description string) diag.Diagnostic {
// InvalidAttributeCombinationDiagnostic returns an error Diagnostic to be used when a schemavalidator of attributes is invalid.
func InvalidAttributeCombinationDiagnostic(path path.Path, description string) diag.Diagnostic {
return diag.NewAttributeErrorDiagnostic(
path,
"Invalid Attribute Combination",
Expand All @@ -53,30 +53,10 @@ func InvalidAttributeTypeDiagnostic(path path.Path, description string, value st
)
}

// ErrorsCount returns the amount of diag.Diagnostic in diag.Diagnostics that are diag.SeverityError.
func ErrorsCount(diags diag.Diagnostics) int {
count := 0

for _, d := range diags {
if diag.SeverityError == d.Severity() {
count++
}
}

return count
}

// WarningsCount returns the amount of diag.Diagnostic in diag.Diagnostics that are diag.SeverityWarning.
func WarningsCount(diags diag.Diagnostics) int {
count := 0

for _, d := range diags {
if diag.SeverityWarning == d.Severity() {
count++
}
}

return count
func BugInProviderDiagnostic(summary string) diag.Diagnostic {
return diag.NewErrorDiagnostic(summary,
"This is a bug in the provider, which should be reported in the provider's own issue tracker",
)
}

// capitalize will uppercase the first letter in a UTF-8 string.
Expand Down
5 changes: 3 additions & 2 deletions int64validator/at_least_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ func TestAtLeastValidator(t *testing.T) {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: path.Root("test"),
AttributeConfig: test.val,
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
int64validator.AtLeast(test.min).Validate(context.TODO(), request, &response)
Expand Down
5 changes: 3 additions & 2 deletions int64validator/at_most_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ func TestAtMostValidator(t *testing.T) {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: path.Root("test"),
AttributeConfig: test.val,
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
int64validator.AtMost(test.max).Validate(context.TODO(), request, &response)
Expand Down
5 changes: 3 additions & 2 deletions int64validator/between_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ func TestBetweenValidator(t *testing.T) {
name, test := name, test
t.Run(name, func(t *testing.T) {
request := tfsdk.ValidateAttributeRequest{
AttributePath: path.Root("test"),
AttributeConfig: test.val,
AttributePath: path.Root("test"),
AttributePathExpression: path.MatchRoot("test"),
AttributeConfig: test.val,
}
response := tfsdk.ValidateAttributeResponse{}
int64validator.Between(test.min, test.max).Validate(context.TODO(), request, &response)
Expand Down
7 changes: 3 additions & 4 deletions int64validator/none_of_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
Expand Down Expand Up @@ -73,12 +72,12 @@ func TestNoneOfValidator(t *testing.T) {
t.Fatalf("expected %d error(s), got none", test.expErrors)
}

if test.expErrors > 0 && test.expErrors != validatordiag.ErrorsCount(res.Diagnostics) {
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
if test.expErrors > 0 && test.expErrors != res.Diagnostics.ErrorsCount() {
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, res.Diagnostics.ErrorsCount(), res.Diagnostics)
}

if test.expErrors == 0 && res.Diagnostics.HasError() {
t.Fatalf("expected no error(s), got %d: %v", validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
t.Fatalf("expected no error(s), got %d: %v", res.Diagnostics.ErrorsCount(), res.Diagnostics)
}
})
}
Expand Down
7 changes: 3 additions & 4 deletions int64validator/one_of_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"testing"

"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
Expand Down Expand Up @@ -73,12 +72,12 @@ func TestOneOfValidator(t *testing.T) {
t.Fatalf("expected %d error(s), got none", test.expErrors)
}

if test.expErrors > 0 && test.expErrors != validatordiag.ErrorsCount(res.Diagnostics) {
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
if test.expErrors > 0 && test.expErrors != res.Diagnostics.ErrorsCount() {
t.Fatalf("expected %d error(s), got %d: %v", test.expErrors, res.Diagnostics.ErrorsCount(), res.Diagnostics)
}

if test.expErrors == 0 && res.Diagnostics.HasError() {
t.Fatalf("expected no error(s), got %d: %v", validatordiag.ErrorsCount(res.Diagnostics), res.Diagnostics)
t.Fatalf("expected no error(s), got %d: %v", res.Diagnostics.ErrorsCount(), res.Diagnostics)
}
})
}
Expand Down
Loading