How do I represent a heterogenous list in a TF provider schema?

I am creating a provider for the Gravitee API gateway that uses its backend management API. Some of the configuration of the resources is extremely complicated, e.g. here is the call to create an API: API Reference .

Note how the OpenApi makes use of heterogeneous arrays, e.g. the listeners field is an array of objects, each of which is one of 3 types. I don’t believe I can represent this inside a TF schema. Am I wrong and if so, how should I handle this? Or do I need to have a schema with 3 arrays, one for each type?

Hi @john_tipper :wave:

I’ve provided a potential schema which could accommodate your requirements in the thread on Does a TF provider support state shared between resources within a provider?.

Something along the following lines should work:

func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"listeners": schema.ListNestedAttribute{
				NestedObject: schema.NestedAttributeObject{
					Attributes: map[string]schema.Attribute{
						"type": schema.StringAttribute{},
						"entry_points": schema.ListAttribute{
							ElementType: types.StringType, // assuming all strings ,
						},
						"servers": schema.ListAttribute{
							ElementType: types.StringType, // assuming all strings ,
						},
						"paths": schema.ListAttribute{
							ElementType: types.StringType, // assuming all strings ,
						},
						"path_mappings": schema.ListAttribute{
							ElementType: types.StringType, // assuming all strings ,
						},
						"cors": schema.SingleNestedAttribute{
							Attributes: map[string]schema.Attribute{
								"allow_credentials": schema.BoolAttribute{}, // assuming this is a bool,
								"allow_headers":     schema.BoolAttribute{}, // assuming this is a bool,
							},
						},
					},
				},
			},
		},
	}
}

It’s possible to use an object type as a representation of a tagged union if you combine it with a validation rule that raises an error unless exactly one of the attributes is set. The chosen attribute name then acts as the selector (“tag”) for which type the author intended to use.

For example, with a design like I described above an author might specify the listeners list using a pattern like this:

  listeners = [
    {
      http = {
        entrypoints = [
          # ...
        ]
      }
    },
    {
      tcp = {
        qos = "none"
      }
    },
  ]

This is semantically equivalent to using a separate type attribute, but puts the type in the attribute name instead, which therefore allows each tag’s nested attributes to be clearly associated with that tag, in such a way that Terraform can understand the relationships.

As far as I know the framework doesn’t currently have a built-in helper for this pattern, but it’s essentially just a normal object type aside from the additional validation rules that at least one attribute must be set and that only one may be set per object.