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

Consider Defining Ordered Set Type #99

Closed
bflad opened this issue Aug 13, 2021 · 2 comments
Closed

Consider Defining Ordered Set Type #99

bflad opened this issue Aug 13, 2021 · 2 comments
Labels
enhancement New feature or request types Issues and pull requests about our types abstraction and implementations.

Comments

@bflad
Copy link
Contributor

bflad commented Aug 13, 2021

Module version

v0.2.0

Use-cases

Certain providers may wish to implement a native "ordered set" type (e.g. a list with ordering requirements, but also element uniqueness constraints). While this type implementation does not necessarily need to live in this framework codebase, it might be good to implement somewhere accessible for provider developers needing it.

Attempted Solutions

A naive implementation, based off the current framework code and types.ListType:

package types

import (
	"context"
	"fmt"
	"strings"

	"github.com/hashicorp/terraform-plugin-framework/attr"
	"github.com/hashicorp/terraform-plugin-go/tfprotov6"
	"github.com/hashicorp/terraform-plugin-go/tftypes"
)

var (
	_ attr.TypeWithValidate = OrderedSetType{}
)

type OrderedSetType struct {
	ListType
}

func (t OrderedSetType) Validate(ctx context.Context, in tftypes.Value) []*tfprotov6.Diagnostic {
	var diags []*tfprotov6.Diagnostic

	if !in.Type().Is(tftypes.List{}) {
		err := fmt.Errorf("expected List value, received %T with value: %v", in, in)
		return append(diags, &tfprotov6.Diagnostic{
			Severity: tfprotov6.DiagnosticSeverityError,
			Summary:  "OrderedSet Type Validation Error",
			Detail:   "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(),
		})
	}

	var vals []tftypes.Value

	if err := in.As(&vals); err != nil {
		return append(diags, &tfprotov6.Diagnostic{
			Severity: tfprotov6.DiagnosticSeverityError,
			Summary:  "OrderedSet Type Validation Error",
			Detail:   "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. Please report the following to the provider developer:\n\n" + err.Error(),
		})
	}

	duplicatesMap := make(map[string]struct{})
	valsMap := make(map[string]struct{})

	for _, val := range vals {
		if _, ok := valsMap[val.String()]; ok {
			if _, ok := duplicatesMap[val.String()]; ok {
				continue
			}

			duplicatesMap[val.String()] = struct{}{}
			continue
		}
		valsMap[val.String()] = struct{}{}
	}

	if len(duplicatesMap) > 0 {
		var duplicates []string

		for duplicate := range duplicatesMap {
			duplicates = append(duplicates, duplicate)
		}

		return append(diags, &tfprotov6.Diagnostic{
			Severity: tfprotov6.DiagnosticSeverityError,
			Summary:  "Duplicate Set Elements",
			Detail:   fmt.Sprintf("This attribute contains duplicate elements of:\n\n%s", strings.Join(duplicates, "\n")),
		})
	}

	return nil
}

For completeness, the unit testing code added to TestAttributeValidate:

		"orderedset-errors": {
			req: ValidateAttributeRequest{
				AttributePath: tftypes.NewAttributePath().WithAttributeName("test"),
				Config: Config{
					Raw: tftypes.NewValue(
						tftypes.Object{
							AttributeTypes: map[string]tftypes.Type{
								"test": tftypes.List{
									ElementType: tftypes.String,
								},
							},
						},
						map[string]tftypes.Value{
							"test": tftypes.NewValue(
								tftypes.List{
									ElementType: tftypes.String,
								},
								[]tftypes.Value{
									tftypes.NewValue(tftypes.String, "testvalue"),
									tftypes.NewValue(tftypes.String, "testvalue"),
								},
							),
						},
					),
					Schema: Schema{
						Attributes: map[string]Attribute{
							"test": {
								Type:     types.OrderedSetType{},
								Required: true,
							},
						},
					},
				},
			},
			resp: ValidateAttributeResponse{
				Diagnostics: []*tfprotov6.Diagnostic{
					{
						Severity: tfprotov6.DiagnosticSeverityError,
						Summary:  "Duplicate Set Elements",
						Detail:   "This attribute contains duplicate elements of:\n\ntftypes.String<\"testvalue\">",
					},
				},
			},
		},

Proposal

Implement OrderedSetType (with a less naive approach) either in this framework's types or another well-known location.

References

@bflad
Copy link
Contributor Author

bflad commented Sep 23, 2022

Rather than introduce our own new type in the framework, which does not align well with the type abstractions we have, we can potentially introduce this as a list type validator over in terraform-plugin-framework-validators. To that end I have created hashicorp/terraform-plugin-framework-validators#67 which we can use to track this further. 👍

@bflad bflad closed this as completed Sep 23, 2022
@github-actions
Copy link

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 24, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request types Issues and pull requests about our types abstraction and implementations.
Projects
None yet
Development

No branches or pull requests

2 participants