Skip to content

Commit

Permalink
New package dynamicvalidator (#249)
Browse files Browse the repository at this point in the history
* Support dynamicvalidator

* Pass tests

* docs updates + changelog
  • Loading branch information
magodo authored Dec 12, 2024
1 parent 7d19faa commit 16687f3
Show file tree
Hide file tree
Showing 20 changed files with 570 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changes/unreleased/FEATURES-20241212-144210.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: FEATURES
body: 'dynamicvalidator: New package which contains `types.Dynamic` specific validators'
time: 2024-12-12T14:42:10.189818-05:00
custom:
Issue: "249"
57 changes: 57 additions & 0 deletions dynamicvalidator/all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

// All returns a validator which ensures that any configured attribute value
// attribute value validates against all the given validators.
//
// Use of All is only necessary when used in conjunction with Any or AnyWithAllWarnings
// as the Validators field automatically applies a logical AND.
func All(validators ...validator.Dynamic) validator.Dynamic {
return allValidator{
validators: validators,
}
}

var _ validator.Dynamic = allValidator{}

// allValidator implements the validator.
type allValidator struct {
validators []validator.Dynamic
}

// Description describes the validation in plain text formatting.
func (v allValidator) Description(ctx context.Context) string {
var descriptions []string

for _, subValidator := range v.validators {
descriptions = append(descriptions, subValidator.Description(ctx))
}

return fmt.Sprintf("Value must satisfy all of the validations: %s", strings.Join(descriptions, " + "))
}

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

// ValidateDynamic performs the validation.
func (v allValidator) ValidateDynamic(ctx context.Context, req validator.DynamicRequest, resp *validator.DynamicResponse) {
for _, subValidator := range v.validators {
validateResp := &validator.DynamicResponse{}

subValidator.ValidateDynamic(ctx, req, validateResp)

resp.Diagnostics.Append(validateResp.Diagnostics...)
}
}
27 changes: 27 additions & 0 deletions dynamicvalidator/all_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator_test

import (
"github.com/hashicorp/terraform-plugin-framework-validators/dynamicvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

func ExampleAll() {
// Used within a Schema method of a DataSource, Provider, or Resource
_ = schema.Schema{
Attributes: map[string]schema.Attribute{
"example_attr": schema.DynamicAttribute{
Required: true,
Validators: []validator.Dynamic{
dynamicvalidator.Any(
dynamicvalidator.Any( /* ... */ ),
dynamicvalidator.All( /* ... */ ),
),
},
},
},
}
}
26 changes: 26 additions & 0 deletions dynamicvalidator/also_requires.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator

import (
"github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

// AlsoRequires checks that a set of path.Expression has a non-null value,
// if the current attribute or block also has a non-null value.
//
// This implements the validation logic declaratively within the schema.
// Refer to [datasourcevalidator.RequiredTogether],
// [providervalidator.RequiredTogether], or [resourcevalidator.RequiredTogether]
// for declaring this type of validation outside the schema definition.
//
// Relative path.Expression will be resolved using the attribute or block
// being validated.
func AlsoRequires(expressions ...path.Expression) validator.Dynamic {
return schemavalidator.AlsoRequiresValidator{
PathExpressions: expressions,
}
}
31 changes: 31 additions & 0 deletions dynamicvalidator/also_requires_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator_test

import (
"github.com/hashicorp/terraform-plugin-framework-validators/dynamicvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

func ExampleAlsoRequires() {
// Used within a Schema method of a DataSource, Provider, or Resource
_ = schema.Schema{
Attributes: map[string]schema.Attribute{
"example_attr": schema.DynamicAttribute{
Optional: true,
Validators: []validator.Dynamic{
// Validate this attribute must be configured with other_attr.
dynamicvalidator.AlsoRequires(path.Expressions{
path.MatchRoot("other_attr"),
}...),
},
},
"other_attr": schema.DynamicAttribute{
Optional: true,
},
},
}
}
65 changes: 65 additions & 0 deletions dynamicvalidator/any.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

// Any returns a validator which ensures that any configured attribute value
// passes at least one of the given validators.
//
// To prevent practitioner confusion should non-passing validators have
// conflicting logic, only warnings from the passing validator are returned.
// Use AnyWithAllWarnings() to return warnings from non-passing validators
// as well.
func Any(validators ...validator.Dynamic) validator.Dynamic {
return anyValidator{
validators: validators,
}
}

var _ validator.Dynamic = anyValidator{}

// anyValidator implements the validator.
type anyValidator struct {
validators []validator.Dynamic
}

// Description describes the validation in plain text formatting.
func (v anyValidator) Description(ctx context.Context) string {
var descriptions []string

for _, subValidator := range v.validators {
descriptions = append(descriptions, subValidator.Description(ctx))
}

return fmt.Sprintf("Value must satisfy at least one of the validations: %s", strings.Join(descriptions, " + "))
}

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

// ValidateDynamic performs the validation.
func (v anyValidator) ValidateDynamic(ctx context.Context, req validator.DynamicRequest, resp *validator.DynamicResponse) {
for _, subValidator := range v.validators {
validateResp := &validator.DynamicResponse{}

subValidator.ValidateDynamic(ctx, req, validateResp)

if !validateResp.Diagnostics.HasError() {
resp.Diagnostics = validateResp.Diagnostics

return
}

resp.Diagnostics.Append(validateResp.Diagnostics...)
}
}
26 changes: 26 additions & 0 deletions dynamicvalidator/any_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator_test

import (
"github.com/hashicorp/terraform-plugin-framework-validators/dynamicvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

func ExampleAny() {
// Used within a Schema method of a DataSource, Provider, or Resource
_ = schema.Schema{
Attributes: map[string]schema.Attribute{
"example_attr": schema.DynamicAttribute{
Required: true,
Validators: []validator.Dynamic{
dynamicvalidator.Any(
dynamicvalidator.Any( /* ... */ ),
),
},
},
},
}
}
67 changes: 67 additions & 0 deletions dynamicvalidator/any_with_all_warnings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

// AnyWithAllWarnings returns a validator which ensures that any configured
// attribute value passes at least one of the given validators. This validator
// returns all warnings, including failed validators.
//
// Use Any() to return warnings only from the passing validator.
func AnyWithAllWarnings(validators ...validator.Dynamic) validator.Dynamic {
return anyWithAllWarningsValidator{
validators: validators,
}
}

var _ validator.Dynamic = anyWithAllWarningsValidator{}

// anyWithAllWarningsValidator implements the validator.
type anyWithAllWarningsValidator struct {
validators []validator.Dynamic
}

// Description describes the validation in plain text formatting.
func (v anyWithAllWarningsValidator) Description(ctx context.Context) string {
var descriptions []string

for _, subValidator := range v.validators {
descriptions = append(descriptions, subValidator.Description(ctx))
}

return fmt.Sprintf("Value must satisfy at least one of the validations: %s", strings.Join(descriptions, " + "))
}

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

// ValidateDynamic performs the validation.
func (v anyWithAllWarningsValidator) ValidateDynamic(ctx context.Context, req validator.DynamicRequest, resp *validator.DynamicResponse) {
anyValid := false

for _, subValidator := range v.validators {
validateResp := &validator.DynamicResponse{}

subValidator.ValidateDynamic(ctx, req, validateResp)

if !validateResp.Diagnostics.HasError() {
anyValid = true
}

resp.Diagnostics.Append(validateResp.Diagnostics...)
}

if anyValid {
resp.Diagnostics = resp.Diagnostics.Warnings()
}
}
26 changes: 26 additions & 0 deletions dynamicvalidator/any_with_all_warnings_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator_test

import (
"github.com/hashicorp/terraform-plugin-framework-validators/dynamicvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

func ExampleAnyWithAllWarnings() {
// Used within a Schema method of a DataSource, Provider, or Resource
_ = schema.Schema{
Attributes: map[string]schema.Attribute{
"example_attr": schema.DynamicAttribute{
Required: true,
Validators: []validator.Dynamic{
dynamicvalidator.AnyWithAllWarnings(
dynamicvalidator.AnyWithAllWarnings( /* ... */ ),
),
},
},
},
}
}
27 changes: 27 additions & 0 deletions dynamicvalidator/at_least_one_of.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator

import (
"github.com/hashicorp/terraform-plugin-framework-validators/internal/schemavalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

// AtLeastOneOf checks that of a set of path.Expression,
// including the attribute this validator is applied to,
// at least one has a non-null value.
//
// This implements the validation logic declaratively within the tfsdk.Schema.
// Refer to [datasourcevalidator.AtLeastOneOf],
// [providervalidator.AtLeastOneOf], or [resourcevalidator.AtLeastOneOf]
// for declaring this type of validation outside the schema definition.
//
// Any relative path.Expression will be resolved using the attribute being
// validated.
func AtLeastOneOf(expressions ...path.Expression) validator.Dynamic {
return schemavalidator.AtLeastOneOfValidator{
PathExpressions: expressions,
}
}
31 changes: 31 additions & 0 deletions dynamicvalidator/at_least_one_of_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package dynamicvalidator_test

import (
"github.com/hashicorp/terraform-plugin-framework-validators/dynamicvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)

func ExampleAtLeastOneOf() {
// Used within a Schema method of a DataSource, Provider, or Resource
_ = schema.Schema{
Attributes: map[string]schema.Attribute{
"example_attr": schema.DynamicAttribute{
Optional: true,
Validators: []validator.Dynamic{
// Validate at least this attribute or other_attr should be configured.
dynamicvalidator.AtLeastOneOf(path.Expressions{
path.MatchRoot("other_attr"),
}...),
},
},
"other_attr": schema.DynamicAttribute{
Optional: true,
},
},
}
}
Loading

0 comments on commit 16687f3

Please sign in to comment.