Blocks - How to convert from struct to state (terraform-plugin-framework)

Hi,

I’m working on porting terraform-provider-ec to terraform-plugin-framework.
The datasources/resources there have a lot of nested blocks. It is not clear to me what’s the best way of converting from models (structs) to state.

My current code looks like below. Is there a way to get rid of the type definition in ValueFrom(), or somehow use the block definition from the schema here?

Is it correct to use ValueFrom() here, or is there a way of assigning []trafficFilterRuleModelV0 to model.Rule and let the framework take care of the conversion based on the schema definition?

If I do not use ValueFrom() but just assign rules, the rules in state end up empty for me.

var model modelV0
var rules []trafficFilterRuleModelV0{
    …
}
_ := tfsdk.ValueFrom(ctx, rules, trafficFilterRuleSetType(), &model.Rule)...)
_ := response.State.Set(ctx, model)

The relevant structs / functions:

type modelV0 struct {
	Rule             types.Set    `tfsdk:"rule"` //< trafficFilterRuleModelV0
	…
}

type trafficFilterRuleModelV0 struct {
	Source            types.String `tfsdk:"source"`
	Description       types.String `tfsdk:"description"`
	…
}


func (r *Resource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
	return tfsdk.Schema{
		Attributes: map[string]tfsdk.Attribute{
			…
		Blocks: map[string]tfsdk.Block{
			"rule": {
				Description: "Required set of rules, which the ruleset is made of.",
				NestingMode: tfsdk.BlockNestingModeSet,
				MinItems:    1,
				Attributes: map[string]tfsdk.Attribute{
					"source": {
						Type:        types.StringType,
						Description: "Optional traffic filter source: IP address, CIDR mask, or VPC endpoint ID, not required when the type is azure_private_endpoint",
						Optional:    true,
					},
					"description": {
						Type:        types.StringType,
						Description: "Optional rule description",
						Optional:    true,
					},
					…
				},
			},
		},
	}, nil
}

func trafficFilterRuleSetType() attr.Type {
	return types.SetType{ElemType: trafficFilterRuleElemType()}
}

func trafficFilterRuleElemType() attr.Type {
	return types.ObjectType{
		AttrTypes: trafficFilterRuleAttrTypes(),
	}
}

func trafficFilterRuleAttrTypes() map[string]attr.Type {
	return map[string]attr.Type{
		"source":              types.StringType, // Required traffic filter source: IP address, CIDR mask, or VPC endpoint ID, not required when the type is azure_private_endpoint
		"description":         types.StringType, // Optional rule description
		"azure_endpoint_name": types.StringType, // Optional Azure endpoint name
		"azure_endpoint_guid": types.StringType, // Optional Azure endpoint GUID
		"id":                  types.StringType, // Computed rule ID
	}
}

I found a way to avoid the duplicate definition of attributes.

func (r *Resource) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) {
	return tfsdk.Schema{
		Attributes: map[string]tfsdk.Attribute{
			…
		Blocks: map[string]tfsdk.Block{
			"rule": trafficFilterRuleSchema(),
		},
	}, nil
}


func trafficFilterRuleSchema() tfsdk.Block {
	return tfsdk.Block{
		Description: "Required set of rules, which the ruleset is made of.",
		NestingMode: tfsdk.BlockNestingModeSet,
		MinItems:    1,
		Attributes: map[string]tfsdk.Attribute{
			"source": {
				Type:        types.StringType,
				Description: "Optional traffic filter source: IP address, CIDR mask, or VPC endpoint ID, not required when the type is azure_private_endpoint",
				Optional:    true,
			},
			"description": {
				Type:        types.StringType,
				Description: "Optional rule description",
				Optional:    true,
			},
			…
		},
	}
}

func trafficFilterRuleSetType() attr.Type {
	return trafficFilterRuleSchema().Type()
}

// the functions below are used in tests
func trafficFilterRuleElemType() attr.Type {
	return trafficFilterRuleSchema().Type().(types.SetType).ElemType
}

func trafficFilterRuleAttrTypes() map[string]attr.Type {
	return trafficFilterRuleSchema().Type().(types.SetType).ElemType.(types.ObjectType).AttrTypes
}