diff --git a/docs/common/oneofwithdescriptionifattributeisoneof.md b/docs/common/oneofwithdescriptionifattributeisoneof.md new file mode 100644 index 0000000..c7b4ae4 --- /dev/null +++ b/docs/common/oneofwithdescriptionifattributeisoneof.md @@ -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) diff --git a/docs/common/oneofwithdescriptionifattributeisoneof.png b/docs/common/oneofwithdescriptionifattributeisoneof.png new file mode 100644 index 0000000..3b63093 Binary files /dev/null and b/docs/common/oneofwithdescriptionifattributeisoneof.png differ diff --git a/docs/int64validator/index.md b/docs/int64validator/index.md index ba2a908..a76b502 100644 --- a/docs/int64validator/index.md +++ b/docs/int64validator/index.md @@ -18,6 +18,7 @@ import ( - [`NullIfAttributeIsOneOf`](../common/null_if_attribute_is_one_of.md) - This validator is used to verify the attribute value is null if another attribute is one of the given values. - [`NullIfAttributeIsSet`](../common/null_if_attribute_is_set.md) - This validator is used to verify the attribute value is null if another attribute is set. - [`OneOfWithDescription`](oneofwithdescription.md) - This validator is used to check if the string is one of the given values and format the description and the markdown description. +- [`OneOfWithDescriptionIfAttributeIsOneOf`](../common/oneofwithdescriptionifattributeisoneof.md) - 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. - [`AttributeIsDivisibleByAnInteger`](attribute_is_divisible_by_an_integer.md) - This validator is used to validate that the attribute is divisible by an integer. - [`ZeroRemainder`](zero_remainder.md) - This validator checks if the configured attribute is divisible by a specified integer X, and has zero remainder. diff --git a/docs/stringvalidator/index.md b/docs/stringvalidator/index.md index 13126a8..7af6212 100644 --- a/docs/stringvalidator/index.md +++ b/docs/stringvalidator/index.md @@ -18,6 +18,7 @@ import ( - [`NullIfAttributeIsOneOf`](../common/null_if_attribute_is_one_of.md) - This validator is used to verify the attribute value is null if another attribute is one of the given values. - [`NullIfAttributeIsSet`](../common/null_if_attribute_is_set.md) - This validator is used to verify the attribute value is null if another attribute is set. - [`OneOfWithDescription`](oneofwithdescription.md) - This validator is used to check if the string is one of the given values and format the description and the markdown description. +- [`OneOfWithDescriptionIfAttributeIsOneOf`](../common/oneofwithdescriptionifattributeisoneof.md) - 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. ### Network diff --git a/int64validator/one_of_with_description_if_attribute_is_one_of.go b/int64validator/one_of_with_description_if_attribute_is_one_of.go new file mode 100644 index 0000000..d83e9ca --- /dev/null +++ b/int64validator/one_of_with_description_if_attribute_is_one_of.go @@ -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, + } +} diff --git a/internal/one_of_with_description_if_attribute_is_one_of.go b/internal/one_of_with_description_if_attribute_is_one_of.go new file mode 100644 index 0000000..ead8026 --- /dev/null +++ b/internal/one_of_with_description_if_attribute_is_one_of.go @@ -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
", 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...) +} diff --git a/internal/one_of_with_description_if_attribute_is_one_of_test.go b/internal/one_of_with_description_if_attribute_is_one_of_test.go new file mode 100644 index 0000000..295112e --- /dev/null +++ b/internal/one_of_with_description_if_attribute_is_one_of_test.go @@ -0,0 +1,203 @@ +package internal_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/FrangipaneTeam/terraform-plugin-framework-validators/internal" +) + +func TestOneOfWithDescriptionIfAttributeIsOneOfValidator(t *testing.T) { + t.Parallel() + + type testCase struct { + req internal.OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest + in path.Expression + inPath path.Path + expectedValues []attr.Value + expError bool + } + + testCases := map[string]testCase{ + // If attrOther is set and the value is one of ExceptedValues the value of attrToCheck is one of Values + // This test case return an error because the value of attrOther is one of the + // expected values and the value of attrToCheck is not one of the Values + "baseString": { + req: internal.OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ + ConfigValue: types.StringValue("another value"), + Path: path.Root("attrToCheck"), + PathExpression: path.MatchRoot("attrToCheck"), + Values: []internal.OneOfWithDescriptionIfAttributeIsOneOf{ + { + Value: types.StringValue("expected value"), + Description: "expected value", + }, + }, + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "attrToCheck": schema.StringAttribute{}, + "attrOther": schema.StringAttribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "attrToCheck": tftypes.String, + "attrOther": tftypes.String, + }, + }, map[string]tftypes.Value{ + "attrToCheck": tftypes.NewValue(tftypes.String, "another value"), + "attrOther": tftypes.NewValue(tftypes.String, "value"), + }), + }, + }, + in: path.MatchRoot("attrOther"), + inPath: path.Root("attrOther"), + expectedValues: []attr.Value{ + types.StringValue("value"), + }, + expError: true, + }, + "extendedString": { + req: internal.OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ + ConfigValue: types.StringValue("another value"), + Path: path.Root("foobar").AtListIndex(0).AtName("attrToCheck"), + PathExpression: path.MatchRoot("foobar").AtListIndex(0).AtName("attrToCheck"), + Values: []internal.OneOfWithDescriptionIfAttributeIsOneOf{ + { + Value: types.StringValue("expected value"), + Description: "expected value", + }, + }, + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "foo": schema.StringAttribute{}, + "bar": schema.StringAttribute{}, + "foobar": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attrOther": schema.StringAttribute{}, + "attrToCheck": schema.StringAttribute{}, + }, + }, + }, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "foo": tftypes.String, + "bar": tftypes.String, + "foobar": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "attrOther": tftypes.String, + "attrToCheck": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "foo": tftypes.NewValue(tftypes.String, "foo value"), + "bar": tftypes.NewValue(tftypes.String, "bar value"), + "foobar": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "attrOther": tftypes.String, + "attrToCheck": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "attrOther": tftypes.String, + "attrToCheck": tftypes.String, + }, + }, map[string]tftypes.Value{ + "attrToCheck": tftypes.NewValue(tftypes.String, "another value"), + "attrOther": tftypes.NewValue(tftypes.String, "value"), + }), + }, + ), + }), + }, + }, + in: path.MatchRoot("foobar").AtListIndex(0).AtName("attrOther"), + inPath: path.Root("foobar").AtListIndex(0).AtName("attrOther"), + expectedValues: []attr.Value{ + types.StringValue("value"), + }, + expError: true, + }, + "baseInt64": { + req: internal.OneOfWithDescriptionIfAttributeIsOneOfValidatorRequest{ + ConfigValue: types.StringValue("another value"), + Path: path.Root("attrToCheck"), + PathExpression: path.MatchRoot("attrToCheck"), + Values: []internal.OneOfWithDescriptionIfAttributeIsOneOf{ + { + Value: types.Int64Value(20), + Description: "20 is better", + }, + }, + Config: tfsdk.Config{ + Schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "attrToCheck": schema.StringAttribute{}, + "attrOther": schema.StringAttribute{}, + }, + }, + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "attrToCheck": tftypes.Number, + "attrOther": tftypes.String, + }, + }, map[string]tftypes.Value{ + "attrToCheck": tftypes.NewValue(tftypes.Number, int64(10)), + "attrOther": tftypes.NewValue(tftypes.String, "value"), + }), + }, + }, + in: path.MatchRoot("attrOther"), + inPath: path.Root("attrOther"), + expectedValues: []attr.Value{ + types.StringValue("value"), + }, + expError: true, + }, + } + + for name, test := range testCases { + name, test := name, test + t.Run(name, func(t *testing.T) { + t.Parallel() + + res := &internal.OneOfWithDescriptionIfAttributeIsOneOfValidatorResponse{} + + internal.OneOfWithDescriptionIfAttributeIsOneOfValidator{ + PathExpression: test.in, + ExceptedValues: test.expectedValues, + Values: test.req.Values, + }.Validate( + context.Background(), + test.req, + res, + ) + + if !test.expError && res.Diagnostics.HasError() { + t.Fatalf("expected no error, got %v", res.Diagnostics) + } + + if test.expError && !res.Diagnostics.HasError() { + t.Fatalf("expected error, got none") + } + }) + } +} diff --git a/stringvalidator/one_of_with_description_if_attribute_is_one_of.go b/stringvalidator/one_of_with_description_if_attribute_is_one_of.go new file mode 100644 index 0000000..a48b6bc --- /dev/null +++ b/stringvalidator/one_of_with_description_if_attribute_is_one_of.go @@ -0,0 +1,35 @@ +package stringvalidator + +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 string + Description string +} + +// OneOfWithDescriptionIfAttributeIsOneOf checks that the String 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.StringValue(v.Value), + Description: v.Description, + }) + } + + return internal.OneOfWithDescriptionIfAttributeIsOneOfValidator{ + Values: frameworkValues, + ExceptedValues: exceptedValue, + PathExpression: path, + } +}