This repository has been archived by the owner on Dec 27, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: string/int64 validator OneOfWithDescriptionIfAttributeIsOneOf
- Loading branch information
Showing
8 changed files
with
606 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# `OneOfWithDescription` | ||
|
||
!!! quote inline end "Released in v1.9.0" | ||
|
||
This validator is used to check if the string is one of the given values if the attribute is one of and format the description and the markdown description. | ||
|
||
## How to use it | ||
|
||
```go | ||
// Schema defines the schema for the resource. | ||
func (r *xResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
(...) | ||
"foo": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "foo ...", | ||
Validators: []validator.String{ | ||
fstringvalidator.OneOf("VM_NAME", "VM_TAGS"), | ||
}, | ||
}, | ||
"bar": schema.StringAttribute{ | ||
Optional: true, | ||
MarkdownDescription: "bar of ...", | ||
Validators: []validator.String{ | ||
fstringvalidator.OneOfWithDescriptionIfAttributeIsOneOf( | ||
path.MatchRelative().AtParent().AtName("foo"), | ||
[]attr.Value{types.StringValue("VM_NAME")}, | ||
func() []fstringvalidator.OneOfWithDescriptionIfAttributeIsOneOfValues { | ||
return []fstringvalidator.OneOfWithDescriptionIfAttributeIsOneOfValues{ | ||
{ | ||
Value: "CONTAINS", | ||
Description: "The `value` must be contained in the VM name.", | ||
}, | ||
{ | ||
Value: "STARTS_WITH", | ||
Description: "The VM name must start with the `value`.", | ||
}, | ||
{ | ||
Value: "ENDS_WITH", | ||
Description: "The VM name must end with the `value`.", | ||
}, | ||
{ | ||
Value: "EQUALS", | ||
Description: "The VM name must be equal to the `value`.", | ||
}, | ||
} | ||
}()...), | ||
}, | ||
}, | ||
``` | ||
## Description and Markdown description | ||
* **Description:** | ||
If the value of attribute <.type is "VM_NAME" the allowed values are : "CONTAINS" (The `value` must be contained in the VM name.), "STARTS_WITH" (The VM name must start with the `value`.), "ENDS_WITH" (The VM name must end with the `value`.), "EQUALS" (The VM name must be equal to the `value`.) | ||
* **Markdown description:** | ||
![oneofwithdescriptionifattributeisoneof](oneofwithdescriptionifattributeisoneof.png) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
int64validator/one_of_with_description_if_attribute_is_one_of.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package int64validator | ||
|
||
import ( | ||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/path" | ||
"github.com/hashicorp/terraform-plugin-framework/schema/validator" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
|
||
"github.com/FrangipaneTeam/terraform-plugin-framework-validators/internal" | ||
) | ||
|
||
type OneOfWithDescriptionIfAttributeIsOneOfValues struct { | ||
Value int64 | ||
Description string | ||
} | ||
|
||
// OneOfWithDescriptionIfAttributeIsOneOf checks that the value is one of the expected values if the attribute is one of the exceptedValue. | ||
// The description of the value is used to generate advanced | ||
// Description and MarkdownDescription messages. | ||
func OneOfWithDescriptionIfAttributeIsOneOf(path path.Expression, exceptedValue []attr.Value, values ...OneOfWithDescriptionIfAttributeIsOneOfValues) validator.String { | ||
frameworkValues := make([]internal.OneOfWithDescriptionIfAttributeIsOneOf, 0, len(values)) | ||
|
||
for _, v := range values { | ||
frameworkValues = append(frameworkValues, internal.OneOfWithDescriptionIfAttributeIsOneOf{ | ||
Value: types.Int64Value(v.Value), | ||
Description: v.Description, | ||
}) | ||
} | ||
|
||
return internal.OneOfWithDescriptionIfAttributeIsOneOfValidator{ | ||
Values: frameworkValues, | ||
ExceptedValues: exceptedValue, | ||
PathExpression: path, | ||
} | ||
} |
273 changes: 273 additions & 0 deletions
273
internal/one_of_with_description_if_attribute_is_one_of.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,273 @@ | ||
package internal | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/diag" | ||
"github.com/hashicorp/terraform-plugin-framework/path" | ||
"github.com/hashicorp/terraform-plugin-framework/schema/validator" | ||
"github.com/hashicorp/terraform-plugin-framework/tfsdk" | ||
) | ||
|
||
const oneOfWithDescriptionIfAttributeIsOneOfValidatorDescription = "Value must be one of:" | ||
|
||
// This type of validator must satisfy all types. | ||
var ( | ||
_ validator.Float64 = OneOfWithDescriptionValidator{} | ||
_ validator.Int64 = OneOfWithDescriptionValidator{} | ||
_ validator.List = OneOfWithDescriptionValidator{} | ||
_ validator.Map = OneOfWithDescriptionValidator{} | ||
_ validator.Number = OneOfWithDescriptionValidator{} | ||
_ validator.Set = OneOfWithDescriptionValidator{} | ||
_ validator.String = OneOfWithDescriptionValidator{} | ||
) | ||
|
||
type OneOfWithDescriptionIfAttributeIsOneOf struct { | ||
Value attr.Value | ||
Description string | ||
} | ||
|
||
// OneOfWithDescriptionValidator validates that the value matches one of expected values. | ||
type OneOfWithDescriptionIfAttributeIsOneOfValidator struct { | ||
PathExpression path.Expression | ||
Values []OneOfWithDescriptionIfAttributeIsOneOf | ||
ExceptedValues []attr.Value | ||
} | ||
|
||
type OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest struct { | ||
Config tfsdk.Config | ||
ConfigValue attr.Value | ||
Path path.Path | ||
PathExpression path.Expression | ||
Values []OneOfWithDescriptionIfAttributeIsOneOf | ||
ExceptedValues []attr.Value | ||
} | ||
|
||
type OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse struct { | ||
Diagnostics diag.Diagnostics | ||
} | ||
|
||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) Description(_ context.Context) string { | ||
var expectedValueDescritpion string | ||
for i, expectedValue := range v.ExceptedValues { | ||
// remove the quotes around the string | ||
if i == len(v.ExceptedValues)-1 { | ||
expectedValueDescritpion += expectedValue.String() | ||
break | ||
} | ||
expectedValueDescritpion += fmt.Sprintf("%s, ", expectedValue.String()) | ||
} | ||
|
||
var valuesDescription string | ||
for i, value := range v.Values { | ||
if i == len(v.Values)-1 { | ||
valuesDescription += fmt.Sprintf("%s (%s)", value.Value.String(), value.Description) | ||
break | ||
} | ||
valuesDescription += fmt.Sprintf("%s (%s), ", value.Value.String(), value.Description) | ||
} | ||
|
||
switch len(v.ExceptedValues) { | ||
case 1: | ||
return fmt.Sprintf("If the value of attribute %s is %s the allowed values are : %s", v.PathExpression.String(), expectedValueDescritpion, valuesDescription) | ||
default: | ||
return fmt.Sprintf("If the value of attribute %s is one of %s the allowed are : %s", v.PathExpression.String(), expectedValueDescritpion, valuesDescription) | ||
} | ||
} | ||
|
||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) MarkdownDescription(_ context.Context) string { | ||
var expectedValueDescritpion string | ||
for i, expectedValue := range v.ExceptedValues { | ||
// remove the quotes around the string | ||
x := strings.Trim(expectedValue.String(), "\"") | ||
|
||
switch i { | ||
case len(v.ExceptedValues) - 1: | ||
expectedValueDescritpion += fmt.Sprintf("`%s`", x) | ||
case len(v.ExceptedValues) - 2: | ||
expectedValueDescritpion += fmt.Sprintf("`%s` or ", x) | ||
default: | ||
expectedValueDescritpion += fmt.Sprintf("`%s`, ", x) | ||
} | ||
} | ||
|
||
valuesDescription := "" | ||
for _, value := range v.Values { | ||
valuesDescription += fmt.Sprintf("- `%s` - %s<br>", value.Value.String(), value.Description) | ||
} | ||
|
||
switch len(v.ExceptedValues) { | ||
case 1: | ||
return fmt.Sprintf("\n\n-> **If the value of the attribute [`%s`](#%s) is %s the value is one of** %s", v.PathExpression, v.PathExpression, expectedValueDescritpion, valuesDescription) | ||
default: | ||
return fmt.Sprintf("\n\n-> **If the value of the attribute [`%s`](#%s) is one of %s** : %s", v.PathExpression, v.PathExpression, expectedValueDescritpion, valuesDescription) | ||
} | ||
} | ||
|
||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) Validate(ctx context.Context, req OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest, res *OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse) { | ||
// Here attribute configuration is null or unknown, so we need to check if attribute in the path | ||
// is equal to one of the excepted values | ||
paths, diags := req.Config.PathMatches(ctx, req.PathExpression.Merge(v.PathExpression)) | ||
if diags.HasError() { | ||
res.Diagnostics.Append(diags...) | ||
return | ||
} | ||
|
||
if len(paths) == 0 { | ||
res.Diagnostics.AddError( | ||
fmt.Sprintf("Invalid configuration for attribute %s", req.Path), | ||
"Path must be set", | ||
) | ||
return | ||
} | ||
|
||
res.Diagnostics.AddWarning("Paths", fmt.Sprintf("%v", paths)) | ||
|
||
path := paths[0] | ||
|
||
// mpVal is the value of the attribute in the path | ||
var mpVal attr.Value | ||
res.Diagnostics.Append(req.Config.GetAttribute(ctx, path, &mpVal)...) | ||
if res.Diagnostics.HasError() { | ||
res.Diagnostics.AddError( | ||
fmt.Sprintf("Invalid configuration for attribute %s", req.Path), | ||
fmt.Sprintf("Unable to retrieve attribute path: %q", path), | ||
) | ||
return | ||
} | ||
|
||
// If the target attribute configuration is unknown or null, there is nothing else to validate | ||
if mpVal.IsNull() || mpVal.IsUnknown() { | ||
return | ||
} | ||
|
||
for _, expectedValue := range v.ExceptedValues { | ||
// If the value of the target attribute is equal to one of the expected values, we need to validate the value of the current attribute | ||
if mpVal.Equal(expectedValue) || mpVal.String() == expectedValue.String() { | ||
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { | ||
res.Diagnostics.AddAttributeError( | ||
path, | ||
fmt.Sprintf("Invalid configuration for attribute %s", req.Path), | ||
fmt.Sprintf("Value is empty. %s", v.Description(ctx)), | ||
) | ||
return | ||
} | ||
|
||
for _, value := range v.Values { | ||
if req.ConfigValue.Equal(value.Value) { | ||
// Ok the value is valid | ||
return | ||
} | ||
} | ||
|
||
// The value is not valid | ||
res.Diagnostics.AddAttributeError( | ||
path, | ||
fmt.Sprintf("Invalid configuration for attribute %s", req.Path), | ||
fmt.Sprintf("Invalid value %s. %s", req.ConfigValue.String(), v.Description(ctx)), | ||
) | ||
return | ||
} | ||
} | ||
} | ||
|
||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { | ||
validateReq := OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ | ||
Config: req.Config, | ||
ConfigValue: req.ConfigValue, | ||
Path: req.Path, | ||
PathExpression: req.PathExpression, | ||
} | ||
validateResp := &OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse{} | ||
|
||
v.Validate(ctx, validateReq, validateResp) | ||
|
||
resp.Diagnostics.Append(validateResp.Diagnostics...) | ||
} | ||
|
||
// Float64 validates that the value matches one of expected values. | ||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) ValidateFloat64(ctx context.Context, req validator.Float64Request, resp *validator.Float64Response) { | ||
validateReq := OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ | ||
Config: req.Config, | ||
ConfigValue: req.ConfigValue, | ||
Path: req.Path, | ||
} | ||
validateResp := &OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse{} | ||
|
||
v.Validate(ctx, validateReq, validateResp) | ||
|
||
resp.Diagnostics.Append(validateResp.Diagnostics...) | ||
} | ||
|
||
// Int64 validates that the value matches one of expected values. | ||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { | ||
validateReq := OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ | ||
Config: req.Config, | ||
ConfigValue: req.ConfigValue, | ||
Path: req.Path, | ||
} | ||
validateResp := &OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse{} | ||
|
||
v.Validate(ctx, validateReq, validateResp) | ||
|
||
resp.Diagnostics.Append(validateResp.Diagnostics...) | ||
} | ||
|
||
// Number validates that the value matches one of expected values. | ||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) ValidateNumber(ctx context.Context, req validator.NumberRequest, resp *validator.NumberResponse) { | ||
validateReq := OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ | ||
Config: req.Config, | ||
ConfigValue: req.ConfigValue, | ||
Path: req.Path, | ||
} | ||
validateResp := &OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse{} | ||
|
||
v.Validate(ctx, validateReq, validateResp) | ||
|
||
resp.Diagnostics.Append(validateResp.Diagnostics...) | ||
} | ||
|
||
// List validates that the value matches one of expected values. | ||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { | ||
validateReq := OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ | ||
Config: req.Config, | ||
ConfigValue: req.ConfigValue, | ||
Path: req.Path, | ||
} | ||
validateResp := &OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse{} | ||
|
||
v.Validate(ctx, validateReq, validateResp) | ||
|
||
resp.Diagnostics.Append(validateResp.Diagnostics...) | ||
} | ||
|
||
// Set validates that the value matches one of expected values. | ||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { | ||
validateReq := OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ | ||
Config: req.Config, | ||
ConfigValue: req.ConfigValue, | ||
Path: req.Path, | ||
} | ||
validateResp := &OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse{} | ||
|
||
v.Validate(ctx, validateReq, validateResp) | ||
|
||
resp.Diagnostics.Append(validateResp.Diagnostics...) | ||
} | ||
|
||
// Map validates that the value matches one of expected values. | ||
func (v OneOfWithDescriptionIfAttributeIsOneOfValidator) ValidateMap(ctx context.Context, req validator.MapRequest, resp *validator.MapResponse) { | ||
validateReq := OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ | ||
Config: req.Config, | ||
ConfigValue: req.ConfigValue, | ||
Path: req.Path, | ||
} | ||
validateResp := &OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse{} | ||
|
||
v.Validate(ctx, validateReq, validateResp) | ||
|
||
resp.Diagnostics.Append(validateResp.Diagnostics...) | ||
} |
Oops, something went wrong.