Defining reusable NestedAttributes

I have a provider which uses lots of reusable nested attributes

this used to work (framework 0.5.0):

func listJdbcAttributeSource() tfsdk.NestedAttributes {
	return tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
		"attribute_contract_fulfillment": {
			Description: `A list of mappings from attribute names to their fulfillment values. This field is only valid for the SP Connection's Browser SSO mappings`,
			Optional:    true,
			Attributes:  mapAttributeFulfillmentValue(),
		},
        ...
	})
}

however with framework 0.11.1 tfsdk.ListNestedAttributes returns internal interfaces, is there a way to do this???

One option might be to define functions that return map[string]tfsdk.Attribute{}.

example.tf
Following contains configuration for a provider and a resource that share the same attribute structure for “list_nested_attribute”.

provider "attributes" {
  list_nested_attribute = [
    {
      int64_attribute       = 9223372036854775807
      list_attribute        = ["list-element", "list-element"]
      list_nested_nested_attribute = [
        {
          int64_attribute_nested = 9223372036854775807
        }
      ]
    },
    {
      int64_attribute = 9223372036854775807
      list_attribute  = ["list-element", "list-element"]
      list_nested_nested_attribute = [
        {
          int64_attribute_nested = 9223372036854775807
        }
      ]
    }
  ]
}

resource "attributes_example" "example" {
  configurable_attribute = "some-value"

  list_nested_attribute = [
    {
      int64_attribute       = 9223372036854775807
      list_attribute        = ["list-element", "list-element"]
      list_nested_nested_attribute = [
        {
          int64_attribute_nested = 9223372036854775807
        }
      ]
    },
    {
      int64_attribute = 9223372036854775807
      list_attribute  = ["list-element", "list-element"]
      list_nested_nested_attribute = [
        {
          int64_attribute_nested = 9223372036854775807
        }
      ]
    }
  ]
}

provider.GetSchema
GetSchema uses 3 functions to compose the schema structure.

func (p *scaffoldingProvider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
	return tfsdk.Schema{
		Attributes: getAttributes(),
	}, nil
}

func getAttributes() map[string]tfsdk.Attribute {
	return map[string]tfsdk.Attribute{
		"list_nested_attribute": {
			Optional:   true,
			Attributes: tfsdk.ListNestedAttributes(getNestedAttributes()),
		},
	}
}

func getNestedAttributes() map[string]tfsdk.Attribute {
	return map[string]tfsdk.Attribute{
		"int64_attribute": {
			Optional: true,
			Type:     types.Int64Type,
		},
		"list_attribute": {
			Optional: true,
			Type:     types.ListType{ElemType: types.StringType},
		},
		"list_nested_nested_attribute": {
			Optional:   true,
			Attributes: tfsdk.ListNestedAttributes(getNestedNestedAttributes()),
		},
	}
}

func getNestedNestedAttributes() map[string]tfsdk.Attribute {
	return map[string]tfsdk.Attribute{
		"int64_attribute_nested": {
			Optional: true,
			Type:     types.Int64Type,
		},
	}
}

provider data model
The providerData model provides the necessary structure to unmarshall the provider configuration onto.

type providerData struct {
	ListNestedAttribute []ListNestedAttribute `tfsdk:"list_nested_attribute"`
}

type ListNestedAttribute struct {
	Int64Attribute            types.Int64                 `tfsdk:"int64_attribute"`
	ListAttribute             types.List                  `tfsdk:"list_attribute"`
	ListNestedNestedAttribute []ListNestedNestedAttribute `tfsdk:"list_nested_nested_attribute"`
}

type ListNestedNestedAttribute struct {
	Int64Attribute types.Int64 `tfsdk:"int64_attribute_nested"`
}

provider.Configure
Configure unmarshalls onto providerData.

func (p *scaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
	var data providerData
	diags := req.Config.Get(ctx, &data)
	resp.Diagnostics.Append(diags...)

	if resp.Diagnostics.HasError() {
		return
	}
	/* .... */

resource.GetSchema
resource.GetSchema uses the same getAttributes func that provider.GetSchema uses and then adds the additional attributes that are required for the resource.

func (t *exampleResource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
	attrs := getAttributes()

	attrs["configurable_attribute"] = tfsdk.Attribute{
		MarkdownDescription: "Example configurable attribute",
		Optional:            true,
		Type:                types.StringType,
	}

	attrs["id"] = tfsdk.Attribute{
		Computed:            true,
		MarkdownDescription: "Example identifier",
		PlanModifiers: tfsdk.AttributePlanModifiers{
			resource.UseStateForUnknown(),
		},
		Type: types.StringType,
	}

	return tfsdk.Schema{
		Attributes: attrs,
	}, nil
}

resource data model
The exampleResourceData model provides the necessary structure to unmarshall the resource configuration onto.

type exampleResourceData struct {
	ConfigurableAttribute types.String          `tfsdk:"configurable_attribute"`
	Id                    types.String          `tfsdk:"id"`
	ListNestedAttribute   []ListNestedAttribute `tfsdk:"list_nested_attribute"`
}

resource.Create
Create unmarshalls onto exampleResourceData.

func (t exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
	var data exampleResourceData

	diags := req.Config.Get(ctx, &data)
	resp.Diagnostics.Append(diags...)

	if resp.Diagnostics.HasError() {
		return
	}

	data.Id = types.String{Value: "example-id"}

	tflog.Trace(ctx, "created a resource")

	diags = resp.State.Set(ctx, &data)
	resp.Diagnostics.Append(diags...)
}

Just to followup here, terraform-plugin-framework v0.17.0+ split schemas (datasource/schema, provider/schema, resource/schema) should now support this use case better. :+1: