diff --git a/datasource/schema/schema.go b/datasource/schema/schema.go index 90d1096c6..4e7e17e1a 100644 --- a/datasource/schema/schema.go +++ b/datasource/schema/schema.go @@ -61,6 +61,10 @@ type Schema struct { DeprecationMessage string } +func (s Schema) EmptyValue(ctx context.Context) tftypes.Value { + return fwschema.EmptySchemaValue(ctx, s) +} + // ApplyTerraform5AttributePathStep applies the given AttributePathStep to the // schema. func (s Schema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) { diff --git a/ephemeral/schema/schema.go b/ephemeral/schema/schema.go index 3c92269fb..091c8ad69 100644 --- a/ephemeral/schema/schema.go +++ b/ephemeral/schema/schema.go @@ -61,6 +61,10 @@ type Schema struct { DeprecationMessage string } +func (s Schema) EmptyValue(ctx context.Context) tftypes.Value { + return fwschema.EmptySchemaValue(ctx, s) +} + // ApplyTerraform5AttributePathStep applies the given AttributePathStep to the // schema. func (s Schema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) { diff --git a/internal/fwschema/nested_block_object.go b/internal/fwschema/nested_block_object.go index bc93a0992..dcdc5c528 100644 --- a/internal/fwschema/nested_block_object.go +++ b/internal/fwschema/nested_block_object.go @@ -6,10 +6,11 @@ package fwschema import ( "fmt" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "github.com/hashicorp/terraform-plugin-go/tftypes" ) // NestedBlockObject represents the Object inside a Block. diff --git a/internal/fwschema/schema.go b/internal/fwschema/schema.go index cc51acd8e..4a6a9d1cc 100644 --- a/internal/fwschema/schema.go +++ b/internal/fwschema/schema.go @@ -73,6 +73,9 @@ type Schema interface { // AttributeTypeAtPath should return the framework type of the Attribute at // the given Terraform path or return an error. TypeAtTerraformPath(context.Context, *tftypes.AttributePath) (attr.Type, error) + + // EmptyValue should return an empty tftypes.Value of the schema's TerraformType + EmptyValue(context.Context) tftypes.Value } // SchemaApplyTerraform5AttributePathStep is a helper function to perform base @@ -195,6 +198,19 @@ func SchemaTypeAtPath(ctx context.Context, s Schema, p path.Path) (attr.Type, di return attrType, diags } +func EmptySchemaValue(ctx context.Context, s Schema) tftypes.Value { + vals := make(map[string]tftypes.Value) + for name, attr := range s.GetAttributes() { + attr.GetType() + vals[name] = tftypes.NewValue(attr.GetType().TerraformType(ctx), nil) + } + for name, block := range s.GetBlocks() { + vals[name] = tftypes.NewValue(block.Type().TerraformType(ctx), nil) + } + + return tftypes.NewValue(s.Type().TerraformType(ctx), vals) +} + // SchemaTypeAtTerraformPath is a helper function to perform base type handling // using the tftypes.AttributePathStepper interface. func SchemaTypeAtTerraformPath(ctx context.Context, s Schema, p *tftypes.AttributePath) (attr.Type, error) { diff --git a/internal/fwserver/schema_propose_new_plan.go b/internal/fwserver/schema_propose_new_plan.go new file mode 100644 index 000000000..560c8c899 --- /dev/null +++ b/internal/fwserver/schema_propose_new_plan.go @@ -0,0 +1,532 @@ +package fwserver + +import ( + "context" + "errors" + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +type ProposeNewStateRequest struct { + PriorState tfsdk.State + Config tfsdk.Config +} + +type ProposeNewStateResponse struct { + ProposedNewState tfsdk.Plan + Diagnostics diag.Diagnostics +} + +func SchemaProposeNewState(ctx context.Context, s fwschema.Schema, req ProposeNewStateRequest, resp *ProposeNewStateResponse) { + // TODO: This is in core's logic, but I'm not sure what how this scenario would be triggered + // Need to verify if it's relevant... + if req.Config.Raw.IsNull() && req.PriorState.Raw.IsNull() { + resp.ProposedNewState = stateToPlan(req.PriorState) + return + } + + if req.PriorState.Raw.IsNull() { + // Populate prior state with a top-level round of nulls from the schema + req.PriorState = tfsdk.State{ + Raw: s.EmptyValue(ctx), + Schema: s, + } + } + + proposedNewState := proposedNew(ctx, s, tftypes.NewAttributePath(), req.PriorState.Raw, req.Config.Raw) + + resp.ProposedNewState = tfsdk.Plan{ + Raw: proposedNewState, + Schema: s, + } +} + +func proposedNew(ctx context.Context, s fwschema.Schema, path *tftypes.AttributePath, prior, config tftypes.Value) tftypes.Value { + // TODO: This is in core's logic, but I'm not sure what how this scenario would be triggered + // Need to verify if it's relevant... + if config.IsNull() || !config.IsKnown() { + return prior + } + + if (!prior.Type().Is(tftypes.Object{})) || (!config.Type().Is(tftypes.Object{})) { + // TODO: switch to non-panics + panic("proposedNew only supports object-typed values") + } + + newAttrs := proposedNewAttributes(ctx, s, s.GetAttributes(), path, prior, config) + + // TODO: add block logic + for name, blockType := range s.GetBlocks() { + attrVal, _ := prior.ApplyTerraform5AttributePathStep(tftypes.AttributeName(name)) + priorVal := attrVal.(tftypes.Value) + + attrVal, _ = config.ApplyTerraform5AttributePathStep(tftypes.AttributeName(name)) + configVal := attrVal.(tftypes.Value) + newAttrs[name] = proposeNewNestedBlock(ctx, s, blockType, path.WithAttributeName(name), priorVal, configVal) + } + + // TODO: validate before doing this? To avoid panic + return tftypes.NewValue(s.Type().TerraformType(ctx), newAttrs) +} + +func proposedNewAttributes(ctx context.Context, s fwschema.Schema, attrs map[string]fwschema.Attribute, path *tftypes.AttributePath, priorObj, configObj tftypes.Value) map[string]tftypes.Value { + newAttrs := make(map[string]tftypes.Value, len(attrs)) + for name, attr := range attrs { + attrPath := path.WithAttributeName(name) + + var priorVal tftypes.Value + if priorObj.IsNull() { + priorObjType := priorObj.Type().(tftypes.Object) //nolint + // TODO: validate before doing this? To avoid panic + priorVal = tftypes.NewValue(priorObjType.AttributeTypes[name], nil) + } else { + // TODO: handle error + attrVal, err := priorObj.ApplyTerraform5AttributePathStep(tftypes.AttributeName(name)) + if err != nil { + panic(err) + } + priorVal = attrVal.(tftypes.Value) //nolint + } + + // TODO: handle error + configIface, err := configObj.ApplyTerraform5AttributePathStep(tftypes.AttributeName(name)) + if err != nil { + panic(err) + } + configVal := configIface.(tftypes.Value) //nolint + + var newVal tftypes.Value + if attr.IsComputed() && configVal.IsNull() { + newVal = priorVal + + if optionalValueNotComputable(ctx, s, attrPath, priorVal) { + newVal = configVal + } + } else if nestedAttr, isNested := attr.(fwschema.NestedAttribute); isNested { + newVal = proposeNewNestedAttribute(ctx, s, nestedAttr, attrPath, priorVal, configVal) + } else { + newVal = configVal + } + + newAttrs[name] = newVal + } + + return newAttrs +} + +func proposeNewNestedBlock(ctx context.Context, s fwschema.Schema, block fwschema.Block, path *tftypes.AttributePath, prior, config tftypes.Value) tftypes.Value { + // if the config isn't known at all, then we must use that value + if !config.IsKnown() { + return config + } + + newVal := config + + switch block.GetNestingMode() { + case fwschema.BlockNestingModeSingle: + if config.IsNull() { + break + } + newVal = proposedNewBlockObjectAttributes(ctx, s, block, path, prior, config) + case fwschema.BlockNestingModeList: + newVal = proposedNewBlockListNested(ctx, s, block, path, prior, config) + case fwschema.BlockNestingModeSet: + newVal = proposedNewBlockSetNested(ctx, s, block, path, prior, config) + default: + // TODO: Shouldn't happen, return diag + panic(fmt.Sprintf("unsupported attribute nesting mode %d", block.GetNestingMode())) + } + + return newVal +} + +func proposeNewNestedBlockObject(ctx context.Context, s fwschema.Schema, nestedBlock fwschema.NestedBlockObject, path *tftypes.AttributePath, prior, config tftypes.Value) tftypes.Value { + if config.IsNull() { + return config + } + valuesMap := proposedNewAttributes(ctx, s, nestedBlock.GetAttributes(), path, prior, config) + + for name, blockType := range nestedBlock.GetBlocks() { + var priorVal tftypes.Value + if prior.IsNull() { + priorObjType := prior.Type().(tftypes.Object) //nolint + // TODO: validate before doing this? To avoid panic + priorVal = tftypes.NewValue(priorObjType.AttributeTypes[name], nil) + } else { + // TODO: handle error + attrVal, err := prior.ApplyTerraform5AttributePathStep(tftypes.AttributeName(name)) + if err != nil { + panic(err) + } + priorVal = attrVal.(tftypes.Value) //nolint + } + + attrVal, _ := config.ApplyTerraform5AttributePathStep(tftypes.AttributeName(name)) + configVal := attrVal.(tftypes.Value) + valuesMap[name] = proposeNewNestedBlock(ctx, s, blockType, path.WithAttributeName(name), priorVal, configVal) + } + + // TODO: validate before doing this? To avoid panic + return tftypes.NewValue( + nestedBlock.Type().TerraformType(ctx), + valuesMap, + ) +} + +func proposeNewNestedAttribute(ctx context.Context, s fwschema.Schema, attr fwschema.NestedAttribute, path *tftypes.AttributePath, prior, config tftypes.Value) tftypes.Value { + // if the config isn't known at all, then we must use that value + if !config.IsKnown() { + return config + } + + newVal := config + + switch attr.GetNestingMode() { + case fwschema.NestingModeSingle: + if config.IsNull() { + break + } + newVal = proposedNewObjectAttributes(ctx, s, attr, path, prior, config) + case fwschema.NestingModeList: + newVal = proposedNewListNested(ctx, s, attr, path, prior, config) + case fwschema.NestingModeMap: + // TODO: handle map + case fwschema.NestingModeSet: + // TODO: handle set + default: + // TODO: Shouldn't happen, return diag + panic(fmt.Sprintf("unsupported attribute nesting mode %d", attr.GetNestingMode())) + } + + return newVal +} + +func proposedNewBlockListNested(ctx context.Context, s fwschema.Schema, block fwschema.Block, path *tftypes.AttributePath, prior, config tftypes.Value) tftypes.Value { + newVal := config + + configVals := make([]tftypes.Value, 0) + priorVals := make([]tftypes.Value, 0) + + configValLen := 0 + if !config.IsNull() { + err := config.As(&configVals) + // TODO: handle err + if err != nil { + panic(err) + } + configValLen = len(configVals) + } + + if !prior.IsNull() { + err := prior.As(&priorVals) + // TODO: handle err + if err != nil { + panic(err) + } + } + + if configValLen > 0 { + newVals := make([]tftypes.Value, 0, configValLen) + for idx, configEV := range configVals { + if prior.IsKnown() && (prior.IsNull() || idx >= len(priorVals)) { + // No corresponding prior element, take config val + newVals = append(newVals, configEV) + continue + } + + priorEV := priorVals[idx] + newVals = append(newVals, proposedNewBlockObjectAttributes(ctx, s, block, path.WithElementKeyInt(idx), priorEV, configEV)) + } + + // TODO: should work for tuples + lists + newVal = tftypes.NewValue(config.Type(), newVals) + } + + return newVal +} + +func proposedNewBlockSetNested(ctx context.Context, s fwschema.Schema, block fwschema.Block, path *tftypes.AttributePath, prior, config tftypes.Value) tftypes.Value { + newVal := config + + configVals := make([]tftypes.Value, 0) + priorVals := make([]tftypes.Value, 0) + + configValLen := 0 + if !config.IsNull() { + err := config.As(&configVals) + // TODO: handle err + if err != nil { + panic(err) + } + configValLen = len(configVals) + } + + if !prior.IsNull() { + err := prior.As(&priorVals) + // TODO: handle err + if err != nil { + panic(err) + } + } + + if configValLen > 0 { + // track which prior elements have been used + used := make([]bool, len(priorVals)) + newVals := make([]tftypes.Value, 0, configValLen) + for _, configEV := range configVals { + var priorEV tftypes.Value + for i, priorCmp := range priorVals { + if used[i] { + continue + } + + // It is possible that multiple prior elements could be valid + // matches for a configuration value, in which case we will end up + // picking the first match encountered (but it will always be + // consistent due to cty's iteration order). Because configured set + // elements must also be entirely unique in order to be included in + // the set, these matches either will not matter because they only + // differ by computed values, or could not have come from a valid + // config with all unique set elements. + if validPriorFromConfig(ctx, s, path, priorCmp, configEV) { + priorEV = priorCmp + used[i] = true + break + } + } + + if priorEV.IsNull() { + // TODO might have to come back to figure out how to get elem type + priorEV = tftypes.NewValue(block.GetNestedObject().Type().TerraformType(ctx), nil) + } + //block.GetNestedObject().GetAttributes() + // TODO create proposed new nested block object + newVals = append(newVals, proposeNewNestedBlockObject(ctx, s, block.GetNestedObject(), path.WithElementKeyValue(priorEV), priorEV, configEV)) + } + + // TODO: should work for tuples + lists + newVal = tftypes.NewValue(config.Type(), newVals) + } + + return newVal +} + +func proposedNewListNested(ctx context.Context, s fwschema.Schema, attr fwschema.NestedAttribute, path *tftypes.AttributePath, prior, config tftypes.Value) tftypes.Value { + newVal := config + + configVals := make([]tftypes.Value, 0) + priorVals := make([]tftypes.Value, 0) + + configValLen := 0 + if !config.IsNull() { + err := config.As(&configVals) + // TODO: handle err + if err != nil { + panic(err) + } + configValLen = len(configVals) + } + + if !prior.IsNull() { + err := prior.As(&priorVals) + // TODO: handle err + if err != nil { + panic(err) + } + } + + if configValLen > 0 { + newVals := make([]tftypes.Value, 0, configValLen) + for idx, configEV := range configVals { + if prior.IsKnown() && (prior.IsNull() || idx > len(priorVals)) { + // No corresponding prior element, take config val + newVals = append(newVals, configEV) + continue + } + + priorEV := priorVals[idx] + newVals = append(newVals, proposedNewObjectAttributes(ctx, s, attr, path.WithElementKeyInt(idx), priorEV, configEV)) + } + + // TODO: should work for tuples + lists + newVal = tftypes.NewValue(config.Type(), newVals) + } + + return newVal +} + +func proposedNewObjectAttributes(ctx context.Context, s fwschema.Schema, attr fwschema.NestedAttribute, path *tftypes.AttributePath, prior, config tftypes.Value) tftypes.Value { + if config.IsNull() { + return config + } + + // TODO: validate before doing this? To avoid panic + return tftypes.NewValue( + attr.GetNestedObject().Type().TerraformType(ctx), + proposedNewAttributes(ctx, s, attr.GetNestedObject().GetAttributes(), path, prior, config), + ) +} + +func proposedNewBlockObjectAttributes(ctx context.Context, s fwschema.Schema, block fwschema.Block, path *tftypes.AttributePath, prior, config tftypes.Value) tftypes.Value { + if config.IsNull() { + return config + } + valuesMap := proposedNewAttributes(ctx, s, block.GetNestedObject().GetAttributes(), path, prior, config) + + for name, blockType := range block.GetNestedObject().GetBlocks() { + //maps.Copy(valuesMap, proposedNewAttributes(ctx, s, blockType.GetNestedObject().GetAttributes(), tftypes.NewAttributePath().WithAttributeName(name).WithElementKeyInt(0), prior, config)) + attrVal, err := prior.ApplyTerraform5AttributePathStep(tftypes.AttributeName(name)) + //TODO handle panic + if err != nil { + panic(err) + } + priorVal := attrVal.(tftypes.Value) + + attrVal, _ = config.ApplyTerraform5AttributePathStep(tftypes.AttributeName(name)) + configVal := attrVal.(tftypes.Value) + valuesMap[name] = proposeNewNestedBlock(ctx, s, blockType, tftypes.NewAttributePath().WithAttributeName(name).WithElementKeyInt(0), priorVal, configVal) + } + + // TODO: validate before doing this? To avoid panic + return tftypes.NewValue( + block.GetNestedObject().Type().TerraformType(ctx), + valuesMap, + ) +} + +func optionalValueNotComputable(ctx context.Context, s fwschema.Schema, absPath *tftypes.AttributePath, val tftypes.Value) bool { + // TODO: handle error + attr, err := s.AttributeAtTerraformPath(ctx, absPath) + if err != nil { + panic(err) + } + + if !attr.IsOptional() { //nolint + return false + } + + _, nested := attr.(fwschema.NestedAttribute) + if !nested { + return false + } + + foundNonComputedAttr := false + err = tftypes.Walk(val, func(path *tftypes.AttributePath, v tftypes.Value) (bool, error) { //nolint + if v.IsNull() { + return true, nil + } + + // Continue past the root + if len(path.Steps()) < 1 { + return true, nil + } + + attrPath := tftypes.NewAttributePathWithSteps(append(absPath.Steps(), path.Steps()...)) + attrSchema, err := s.AttributeAtTerraformPath(ctx, attrPath) + if err != nil { + return true, nil //nolint + } + + if !attrSchema.IsComputed() { + foundNonComputedAttr = true + return false, nil + } + + return true, nil + }) + if err != nil { + //TODO handle panic + panic(err) + } + + return foundNonComputedAttr +} + +// validPriorFromConfig returns true if the prior object could have been +// derived from the configuration. We do this by walking the prior value to +// determine if it is a valid superset of the config, and only computable +// values have been added. This function is only used to correlated +// configuration with possible valid prior values within sets. +func validPriorFromConfig(ctx context.Context, s fwschema.Schema, absPath *tftypes.AttributePath, prior, config tftypes.Value) bool { + if config.Equal(prior) { + return true + } + + // error value to halt the walk + stop := errors.New("stop") + + valid := true + _ = tftypes.Walk(prior, func(path *tftypes.AttributePath, priorV tftypes.Value) (bool, error) { + if priorV.IsNull() { + return true, nil + } + + // Continue past the root + if len(path.Steps()) < 1 { + return true, nil + } + + configIface, _, err := tftypes.WalkAttributePath(config, path) + if err != nil { + // most likely dynamic objects with different types + valid = false + return false, stop + } + configV := configIface.(tftypes.Value) + + // we don't need to know the schema if both are equal + if configV.Equal(priorV) { + // we know they are equal, so no need to descend further + return false, nil + } + + // We can't descend into nested sets to correlate configuration, so the + // overall values must be equal. + if configV.Type().Is(tftypes.Set{}) { + valid = false + return false, stop + } + setValPath := tftypes.NewAttributePath().WithElementKeyValue(prior) + + attrPath := tftypes.NewAttributePathWithSteps(append(absPath.Steps(), append(setValPath.Steps(), path.Steps()...)...)) + attrSchema, err := s.AttributeAtTerraformPath(ctx, attrPath) + if err != nil { + // Not at a schema attribute, so we can continue until we find leaf + // attributes. + return true, nil //nolint + } + + // If we have nested object attributes we'll be descending into those + // to compare the individual values and determine why this level is not + // equal + _, isNestedType := attrSchema.GetType().(attr.TypeWithAttributeTypes) + if isNestedType { + return true, nil + } + + // This is a leaf attribute, so it must be computed in order to differ + // from config. + if !attrSchema.IsComputed() { + valid = false + return false, stop + } + + // And if it is computed, the config must be null to allow a change. + if !configV.IsNull() { + valid = false + return false, stop + } + + // We sill stop here. The cty value could be far larger, but this was + // the last level of prescribed schema. + return false, nil + }) + + return valid +} diff --git a/internal/fwserver/schema_propose_new_plan_test.go b/internal/fwserver/schema_propose_new_plan_test.go new file mode 100644 index 000000000..82435dc7f --- /dev/null +++ b/internal/fwserver/schema_propose_new_plan_test.go @@ -0,0 +1,5802 @@ +package fwserver + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +func TestSchemaProposeNewState(t *testing.T) { + t.Parallel() + tests := map[string]struct { + schema fwschema.Schema + priorVal map[string]tftypes.Value + configVal map[string]tftypes.Value + expectedVal map[string]tftypes.Value + }{ + "empty": { + schema: schema.Schema{}, + priorVal: map[string]tftypes.Value{}, + configVal: map[string]tftypes.Value{}, + expectedVal: map[string]tftypes.Value{}, + }, + "no prior": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "optional_attribute": schema.StringAttribute{ + Optional: true, + }, + "computed_attribute": schema.StringAttribute{ + Computed: true, + }, + "single_nested_attribute": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "optional_computed_attributeA": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "optional_computed_attributeB": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + priorVal: nil, + configVal: map[string]tftypes.Value{ + "optional_attribute": tftypes.NewValue(tftypes.String, "hello"), + "computed_attribute": tftypes.NewValue(tftypes.String, nil), + "single_nested_attribute": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{"required_nested_attribute": tftypes.String}}, nil), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attributeA": tftypes.String, + "optional_computed_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_attributeA": tftypes.NewValue(tftypes.String, "world"), + // An unknown in the config represents a situation where + // an argument is explicitly set to an expression result + // that is derived from an unknown value. This is distinct + // from leaving it null, which allows the provider itself + // to decide the value during PlanResourceChange. + "optional_computed_attributeB": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + }, + expectedVal: map[string]tftypes.Value{ + "optional_attribute": tftypes.NewValue(tftypes.String, "hello"), + // unset computed attributes are null in the proposal; provider + // usually changes them to "unknown" during PlanResourceChange, + // to indicate that the value will be decided during apply. + "computed_attribute": tftypes.NewValue(tftypes.String, nil), + "single_nested_attribute": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{"required_nested_attribute": tftypes.String}}, nil), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attributeA": tftypes.String, + "optional_computed_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_attributeA": tftypes.NewValue(tftypes.String, "world"), + "optional_computed_attributeB": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), // explicit unknown preserved from config + }), + }, + }, + "null block remains null": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "optional_attribute": schema.StringAttribute{ + Optional: true, + }, + "single_nested_attribute": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "optional_computed_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + priorVal: nil, + configVal: map[string]tftypes.Value{ + "optional_attribute": tftypes.NewValue(tftypes.String, "bar"), + "single_nested_attribute": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{"required_nested_attribute": tftypes.String}}, nil), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute": tftypes.String, + }, + }, nil), + }, + expectedVal: map[string]tftypes.Value{ + "optional_attribute": tftypes.NewValue(tftypes.String, "bar"), + "single_nested_attribute": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{"required_nested_attribute": tftypes.String}}, nil), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute": tftypes.String, + }, + }, nil), + }, + }, + "no prior with set": { + // This one is here because our handling of sets is more complex + // than others (due to the fuzzy correlation heuristic) and + // historically that caused us some panic-related grief. + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "optional_computed_nested_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: nil, + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "world"), + }), + }, + ), + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "blub"), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "world"), + }), + }, + ), + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "blub"), + }), + }, + ), + }, + }, + "prior attributes": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "optional_attribute": schema.StringAttribute{ + Optional: true, + }, + "computed_attribute": schema.StringAttribute{ + Computed: true, + }, + "optional_computed_attributeA": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "optional_computed_attributeB": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "single_nested_attribute": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "optional_attribute": tftypes.NewValue(tftypes.String, "bonjour"), + "computed_attribute": tftypes.NewValue(tftypes.String, "petit dejeuner"), + "optional_computed_attributeA": tftypes.NewValue(tftypes.String, "grande dejeuner"), + "optional_computed_attributeB": tftypes.NewValue(tftypes.String, "a la monde"), + "single_nested_attribute": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{"required_nested_attribute": tftypes.String}}, + map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + }), + }, + configVal: map[string]tftypes.Value{ + "optional_attribute": tftypes.NewValue(tftypes.String, "hello"), + "computed_attribute": tftypes.NewValue(tftypes.String, nil), + "optional_computed_attributeA": tftypes.NewValue(tftypes.String, nil), + "optional_computed_attributeB": tftypes.NewValue(tftypes.String, "world"), + "single_nested_attribute": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{"required_nested_attribute": tftypes.String}}, + map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "bleep"), + }), + }, + expectedVal: map[string]tftypes.Value{ + "optional_attribute": tftypes.NewValue(tftypes.String, "hello"), + "computed_attribute": tftypes.NewValue(tftypes.String, "petit dejeuner"), + "optional_computed_attributeA": tftypes.NewValue(tftypes.String, "grande dejeuner"), + "optional_computed_attributeB": tftypes.NewValue(tftypes.String, "world"), + "single_nested_attribute": tftypes.NewValue(tftypes.Object{AttributeTypes: map[string]tftypes.Type{"required_nested_attribute": tftypes.String}}, + map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "bleep"), + }), + }, + }, + "prior nested single": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "optional_computed_attributeA": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "optional_computed_attributeB": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attributeA": tftypes.String, + "optional_computed_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_attributeA": tftypes.NewValue(tftypes.String, "bleep"), + "optional_computed_attributeB": tftypes.NewValue(tftypes.String, "boop"), + }), + }, + configVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "beep"), + }), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attributeA": tftypes.String, + "optional_computed_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_attributeA": tftypes.NewValue(tftypes.String, "bap"), + "optional_computed_attributeB": tftypes.NewValue(tftypes.String, nil), + }), + }, + expectedVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "beep"), + }), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attributeA": tftypes.String, + "optional_computed_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_attributeA": tftypes.NewValue(tftypes.String, "bap"), + "optional_computed_attributeB": tftypes.NewValue(tftypes.String, "boop"), + }), + }, + }, + "prior nested single to null": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "optional_computed_attributeA": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "optional_computed_attributeB": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attributeA": tftypes.String, + "optional_computed_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_attributeA": tftypes.NewValue(tftypes.String, "bleep"), + "optional_computed_attributeB": tftypes.NewValue(tftypes.String, "boop"), + }), + }, + configVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, nil), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attributeA": tftypes.String, + "optional_computed_attributeB": tftypes.String, + }, + }, nil), + }, + expectedVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, nil), + "single_nested_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attributeA": tftypes.String, + "optional_computed_attributeB": tftypes.String, + }, + }, nil), + }, + }, + "prior optional computed nested single to null": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + configVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, nil), + }, + expectedVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, nil), + }, + }, + "prior nested list": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "list_nested_attribute": schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "list_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "optional_computed_nested_attributeA": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "optional_computed_nested_attributeB": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "bar"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "baz"), + }), + }, + ), + "list_nested_block": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attributeA": tftypes.String, + "optional_computed_nested_attributeB": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attributeA": tftypes.String, + "optional_computed_nested_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attributeA": tftypes.NewValue(tftypes.String, "beep"), + "optional_computed_nested_attributeB": tftypes.NewValue(tftypes.String, "boop"), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "bar"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "baz"), + }), + }, + ), + "list_nested_block": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attributeA": tftypes.String, + "optional_computed_nested_attributeB": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attributeA": tftypes.String, + "optional_computed_nested_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attributeA": tftypes.NewValue(tftypes.String, "bap"), + "optional_computed_nested_attributeB": tftypes.NewValue(tftypes.String, nil), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attributeA": tftypes.String, + "optional_computed_nested_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attributeA": tftypes.NewValue(tftypes.String, "blep"), + "optional_computed_nested_attributeB": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "bar"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "baz"), + }), + }, + ), + "list_nested_block": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attributeA": tftypes.String, + "optional_computed_nested_attributeB": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attributeA": tftypes.String, + "optional_computed_nested_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attributeA": tftypes.NewValue(tftypes.String, "bap"), + "optional_computed_nested_attributeB": tftypes.NewValue(tftypes.String, "boop"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attributeA": tftypes.String, + "optional_computed_nested_attributeB": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attributeA": tftypes.NewValue(tftypes.String, "blep"), + "optional_computed_nested_attributeB": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + }, + "prior nested list with dynamic": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "list_nested_attribute": schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "required_nested_dynamic_attributeA": schema.DynamicAttribute{ + Required: true, + }, + "required_nested_dynamic_attributeB": schema.DynamicAttribute{ + Required: true, + }, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "list_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "optional_computed_nested_string_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "optional_computed_nested_dynamic_attribute": schema.DynamicAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_dynamic_attributeA": tftypes.DynamicPseudoType, + "required_nested_dynamic_attributeB": tftypes.DynamicPseudoType, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_dynamic_attributeA": tftypes.DynamicPseudoType, + "required_nested_dynamic_attributeB": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_dynamic_attributeA": tftypes.NewValue(tftypes.String, "bar"), + "required_nested_dynamic_attributeB": tftypes.NewValue(tftypes.String, "glup"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_dynamic_attributeA": tftypes.DynamicPseudoType, + "required_nested_dynamic_attributeB": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_dynamic_attributeA": tftypes.NewValue(tftypes.String, "baz"), + "required_nested_dynamic_attributeB": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + "list_nested_block": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_string_attribute": tftypes.String, + "optional_computed_nested_dynamic_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_string_attribute": tftypes.String, + "optional_computed_nested_dynamic_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_string_attribute": tftypes.NewValue(tftypes.String, "beep"), + "optional_computed_nested_dynamic_attribute": tftypes.NewValue(tftypes.String, "boop"), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_dynamic_attributeA": tftypes.DynamicPseudoType, + "required_nested_dynamic_attributeB": tftypes.DynamicPseudoType, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_dynamic_attributeA": tftypes.DynamicPseudoType, + "required_nested_dynamic_attributeB": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_dynamic_attributeA": tftypes.NewValue(tftypes.String, "bar"), + "required_nested_dynamic_attributeB": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + "list_nested_block": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_string_attribute": tftypes.String, + "optional_computed_nested_dynamic_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_string_attribute": tftypes.String, + "optional_computed_nested_dynamic_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_string_attribute": tftypes.NewValue(tftypes.String, "bap"), + "optional_computed_nested_dynamic_attribute": tftypes.NewValue(tftypes.String, nil), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_string_attribute": tftypes.String, + "optional_computed_nested_dynamic_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_string_attribute": tftypes.NewValue(tftypes.String, "blep"), + "optional_computed_nested_dynamic_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_dynamic_attributeA": tftypes.DynamicPseudoType, + "required_nested_dynamic_attributeB": tftypes.DynamicPseudoType, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_dynamic_attributeA": tftypes.DynamicPseudoType, + "required_nested_dynamic_attributeB": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_dynamic_attributeA": tftypes.NewValue(tftypes.String, "bar"), + "required_nested_dynamic_attributeB": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + "list_nested_block": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_string_attribute": tftypes.String, + "optional_computed_nested_dynamic_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_string_attribute": tftypes.String, + "optional_computed_nested_dynamic_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_string_attribute": tftypes.NewValue(tftypes.String, "bap"), + "optional_computed_nested_dynamic_attribute": tftypes.NewValue(tftypes.String, "boop"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_string_attribute": tftypes.String, + "optional_computed_nested_dynamic_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_string_attribute": tftypes.NewValue(tftypes.String, "blep"), + "optional_computed_nested_dynamic_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + }, + "prior nested map": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "map_nested_attribute": schema.MapNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + }), + "b": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "blub"), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + }), + "c": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "blub"), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + }), + "c": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "blub"), + }), + }, + ), + }, + }, + "prior optional computed nested map elem to null": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "map_nested_attribute": schema.MapNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + "optional_computed_nested_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "computed"), + }), + "b": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "blub"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "computed"), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, nil), + "c": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "blub"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, nil), + "c": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "blub"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + }, + "prior optional computed nested map to null": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "map_nested_attribute": schema.MapNestedAttribute{ + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + "optional_computed_nested_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "computed"), + }), + "b": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "blub"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "computed"), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + nil, + ), + }, + expectedVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + nil, + ), + }, + }, + "prior nested map with dynamic": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "map_nested_attribute": schema.MapNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.DynamicAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + }), + "b": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.Number, 13), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "blep"), + }), + "c": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.Number, 13), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + map[string]tftypes.Value{ + "a": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "blep"), + }), + "c": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.Number, 13), + }), + }, + ), + }, + }, + "prior nested set": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + // This non-computed attribute will serve + // as our matching key for propagating + // "optional_computed_nested_attribute" from elements in the prior value. + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + "optional_computed_nested_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glubglub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glubglub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "beep"), + }), + }, + ), + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "beep"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "boop"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "blep"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "boot"), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glubglub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "beep"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "bosh"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glubglub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "glub"), + "optional_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "beep"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "boop"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "bosh"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + }, + "set with partial optional computed change": { + schema: schema.Schema{ + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + "optional_computed_nested_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "OK"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "OK"), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "replaced"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "OK"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "replaced"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + }, + "set without partial optional computed change": { + schema: schema.Schema{ + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + "optional_computed_nested_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "one"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "two"), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "one"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "two"), + }), + }, + ), + }, + }, + "sets differing only by unknown": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "optional_computed_nested_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: nil, + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + }, + ), + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + // These remain distinct because unknown values never + // compare equal. They may be consolidated together once + // the values become known, though. + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + }, + ), + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }), + }, + ), + }, + }, + "nested list in set": { + schema: schema.Schema{ + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "nested_list_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nested_attribute": schema.StringAttribute{}, + "optional_computed_nested_attribute": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_list_block": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }}, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.String, "beep"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "boop"), + }), + }), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "nested_list_block": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }}, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.String, "beep"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, nil), + }), + }), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_list_block": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }}, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + "optional_computed_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.String, "beep"), + "optional_computed_nested_attribute": tftypes.NewValue(tftypes.String, "boop"), + }), + }), + }), + }, + ), + }, + }, + // TODO: figure out if we need this test + // The set value is panicking because there are multiple types defined in the set. + // tftypes seems to treat each definition of the `nested_list_block` type as separate types, + // possibly because it has no attributes defined in the nested block object for equality comparison. + //"empty nested list in set": { + // schema: schema.Schema{ + // Blocks: map[string]schema.Block{ + // "set_nested_block": schema.SetNestedBlock{ + // NestedObject: schema.NestedBlockObject{ + // Blocks: map[string]schema.Block{ + // "nested_list_block": schema.ListNestedBlock{ + // NestedObject: schema.NestedBlockObject{}, + // }, + // }, + // }, + // }, + // }, + // }, + // priorVal: map[string]tftypes.Value{ + // "set_nested_block": tftypes.NewValue( + // tftypes.Set{ + // ElementType: tftypes.Object{ + // AttributeTypes: map[string]tftypes.Type{ + // "nested_list_block": tftypes.List{ + // ElementType: tftypes.Object{}, + // }, + // }, + // }, + // }, + // []tftypes.Value{ + // tftypes.NewValue(tftypes.Object{ + // AttributeTypes: map[string]tftypes.Type{ + // "nested_list_block": tftypes.List{ + // ElementType: tftypes.Object{}, + // }, + // }, + // }, map[string]tftypes.Value{ + // "nested_list_block": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{}}, []tftypes.Value{}), + // }), + // }, + // ), + // }, + // configVal: map[string]tftypes.Value{ + // "set_nested_block": tftypes.NewValue( + // tftypes.Set{ + // ElementType: tftypes.Object{ + // AttributeTypes: map[string]tftypes.Type{ + // "nested_list_block": tftypes.List{ + // ElementType: tftypes.Object{}, + // }, + // }, + // }, + // }, + // []tftypes.Value{ + // tftypes.NewValue(tftypes.Object{ + // AttributeTypes: map[string]tftypes.Type{ + // "nested_list_block": tftypes.List{ + // ElementType: tftypes.Object{}, + // }, + // }, + // }, map[string]tftypes.Value{ + // "nested_list_block": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{}}, []tftypes.Value{}), + // }), + // }, + // ), + // }, + // expectedVal: map[string]tftypes.Value{ + // "set_nested_block": tftypes.NewValue( + // tftypes.Set{ + // ElementType: tftypes.Object{ + // AttributeTypes: map[string]tftypes.Type{ + // "nested_list_block": tftypes.List{ + // ElementType: tftypes.Object{}, + // }, + // }, + // }, + // }, + // []tftypes.Value{ + // tftypes.NewValue(tftypes.Object{ + // AttributeTypes: map[string]tftypes.Type{ + // "nested_list_block": tftypes.List{ + // ElementType: tftypes.Object{}, + // }, + // }, + // }, map[string]tftypes.Value{ + // "nested_list_block": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{}}, []tftypes.Value{}), + // }), + // }, + // ), + // }, + //}, + "nested list with dynamic in set": { + schema: schema.Schema{ + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "nested_list_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nested_attribute": schema.DynamicAttribute{}, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_list_block": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }}, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.String, "true"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, + []tftypes.Value{ + tftypes.NewValue(tftypes.String, "true"), + }), + }), + }), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_list_block": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }}, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.String, "true"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, + []tftypes.Value{ + tftypes.NewValue(tftypes.String, "true"), + }), + }), + }), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_block": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_list_block": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }}, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.String, "true"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, + []tftypes.Value{ + tftypes.NewValue(tftypes.String, "true"), + }, + ), + }), + }), + }), + }, + ), + }, + }, + "nested map with dynamic in set": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_map_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_attribute": schema.DynamicAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_map_attribute": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, map[string]tftypes.Value{ + "bing": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.String, "true"), + }), + "bang": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, + []tftypes.Value{ + tftypes.NewValue(tftypes.String, "true"), + }, + ), + }), + }), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_map_attribute": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, map[string]tftypes.Value{ + "bing": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, + []tftypes.Value{ + tftypes.NewValue(tftypes.String, "true"), + }, + ), + }), + }), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_map_attribute": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, + }, map[string]tftypes.Value{ + "bing": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.DynamicPseudoType, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, + []tftypes.Value{ + tftypes.NewValue(tftypes.String, "true"), + }, + ), + }), + }), + }), + }, + ), + }, + }, + "empty nested map in set": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_map_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_map_attribute": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, + }, map[string]tftypes.Value{}), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_map_attribute": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, + }, map[string]tftypes.Value{ + "bing": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.String, "true"), + }), + }), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_map_attribute": tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_map_attribute": tftypes.NewValue(tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, + }, map[string]tftypes.Value{ + "bing": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "nested_attribute": tftypes.NewValue(tftypes.String, "true"), + }), + }), + }), + }, + ), + }, + }, + // This example has a mixture of optional, computed and required in a deeply-nested NestedType attribute + "deeply NestedType": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "required_single_nested_nested_attribute": schema.SingleNestedAttribute{ + Attributes: testAttributes, + Required: true, + }, + "optional_single_nested_nested_attribute": schema.SingleNestedAttribute{ + Attributes: testAttributes, + Optional: true, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_single_nested_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + "optional_single_nested_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "required_single_nested_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, nil), + "optional_single_nested_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, nil), + "computed": tftypes.NewValue(tftypes.String, "hello"), + "optional_computed": tftypes.NewValue(tftypes.String, "prior"), + "required": tftypes.NewValue(tftypes.String, "present"), + }), + }), + }, + configVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_single_nested_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + "optional_single_nested_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "required_single_nested_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, tftypes.UnknownValue), // explicit unknown from config + "optional_single_nested_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, nil), + "computed": tftypes.NewValue(tftypes.String, nil), + "optional_computed": tftypes.NewValue(tftypes.String, "hello"), + "required": tftypes.NewValue(tftypes.String, "present"), + }), + }), + }, + expectedVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_single_nested_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + "optional_single_nested_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "required_single_nested_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, tftypes.UnknownValue), // explicit unknown preserved from the config + "optional_single_nested_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, nil), // config is null + "computed": tftypes.NewValue(tftypes.String, "hello"), // computed values come from prior + "optional_computed": tftypes.NewValue(tftypes.String, "hello"), // config takes precedent over prior in opt+computed + "required": tftypes.NewValue(tftypes.String, "present"), // value from config + }), + }), + }, + }, + "deeply nested set": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_set_nested_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: testAttributes, + }, + Required: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_set_nested_attribute": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "prior"), + "computed": tftypes.NewValue(tftypes.String, "prior"), + "optional_computed": tftypes.NewValue(tftypes.String, "prior"), + "required": tftypes.NewValue(tftypes.String, "prior"), + }), + }), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_set_nested_attribute": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "other_prior"), + "computed": tftypes.NewValue(tftypes.String, "other_prior"), + "optional_computed": tftypes.NewValue(tftypes.String, "other_prior"), + "required": tftypes.NewValue(tftypes.String, "other_prior"), + }), + }), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_set_nested_attribute": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "configured"), + "computed": tftypes.NewValue(tftypes.String, nil), // computed attrs are null in config + "optional_computed": tftypes.NewValue(tftypes.String, "configured"), + "required": tftypes.NewValue(tftypes.String, "configured"), + }), + }), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_set_nested_attribute": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, nil), // explicit null in config + "computed": tftypes.NewValue(tftypes.String, nil), // computed attrs are null in config + "optional_computed": tftypes.NewValue(tftypes.String, "other_configured"), + "required": tftypes.NewValue(tftypes.String, "other_configured"), + }), + }), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_set_nested_attribute": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "configured"), + "computed": tftypes.NewValue(tftypes.String, nil), + "optional_computed": tftypes.NewValue(tftypes.String, "configured"), + "required": tftypes.NewValue(tftypes.String, "configured"), + }), + }), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, map[string]tftypes.Value{ + "nested_set_nested_attribute": tftypes.NewValue(tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, nil), // explicit null in config is preserved + "computed": tftypes.NewValue(tftypes.String, nil), + "optional_computed": tftypes.NewValue(tftypes.String, "other_configured"), + "required": tftypes.NewValue(tftypes.String, "other_configured"), + }), + }), + }), + }, + ), + }, + }, + "expected null NestedTypes": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + "list_nested_attribute": schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + "map_nested_attribute": schema.MapNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + "nested_map_nested_attribute": schema.MapNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "inner_nested_single_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: testAttributes, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "baz"), + }), + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "baz"), + }), + }, + ), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "baz"), + }), + }, + ), + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + map[string]tftypes.Value{ + "map_entry": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "baz"), + }), + }, + ), + "nested_map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "inner_nested_single_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "map_entry": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "inner_nested_single_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, map[string]tftypes.Value{ + "inner_nested_single_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "foo"), // explicit null in config is preserved + "computed": tftypes.NewValue(tftypes.String, "foo"), + "optional_computed": tftypes.NewValue(tftypes.String, "foo"), + "required": tftypes.NewValue(tftypes.String, "foo"), + }), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, nil), + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, nil), + }, + ), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, nil), + }, + ), + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + nil, + ), + "nested_map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "inner_nested_single_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, + nil, + ), + }, + expectedVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, nil), + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, nil), + }, + ), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, nil), + }, + ), + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + nil, + ), + "nested_map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "inner_nested_single_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + "optional_computed": tftypes.String, + "required": tftypes.String, + }, + }, + }, + }, + }, + nil, + ), + }, + }, + "expected empty NestedTypes": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "list_nested_attribute": schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{}, + ), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{}, + ), + }, + configVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{}, + ), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{}, + ), + }, + expectedVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{}, + ), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{}, + ), + }, + }, + "optional types set replacement": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "required_nested_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "old"), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "new"), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "required_nested_attribute": tftypes.String, + }, + }, map[string]tftypes.Value{ + "required_nested_attribute": tftypes.NewValue(tftypes.String, "new"), + }), + }, + ), + }, + }, + "prior null nested objects": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "nested_list_nested_attribute": schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + "map_nested_attribute": schema.MapNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_list_nested_attribute": schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_nested_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + }, + }, + nil, + ), + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + }, + }, + }, + map[string]tftypes.Value{ + "one": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + }}, + map[string]tftypes.Value{ + "nested_list_nested_attribute": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + []tftypes.Value{}), + }), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + }, + }, + map[string]tftypes.Value{ + "nested_list_nested_attribute": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }}, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "a"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }}, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "b"), + }), + }), + }, + ), + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + }, + }, + }, + map[string]tftypes.Value{ + "one": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + }}, + map[string]tftypes.Value{ + "nested_list_nested_attribute": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }}, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "a"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }}, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "b"), + }), + }), + }), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + }, + }, + map[string]tftypes.Value{ + "nested_list_nested_attribute": tftypes.NewValue(tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }}, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "a"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }}, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "b"), + }), + }), + }, + ), + "map_nested_attribute": tftypes.NewValue( + tftypes.Map{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + }, + }, + }, + map[string]tftypes.Value{ + "one": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }, + }, + }}, + map[string]tftypes.Value{ + "nested_list_nested_attribute": tftypes.NewValue(tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }, + }}, + []tftypes.Value{ + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }}, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "a"), + }), + tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_nested_attribute": tftypes.String, + }}, map[string]tftypes.Value{ + "optional_nested_attribute": tftypes.NewValue(tftypes.String, "b"), + }), + }), + }), + }, + ), + }, + }, + + // Data sources are planned with an unknown value. + "unknown prior nested objects": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "list_nested_attribute": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_list_nested_attribute": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "string_attribute": schema.StringAttribute{}, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + tftypes.UnknownValue, + ), + }, + configVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + nil, + ), + }, + expectedVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_list_nested_attribute": tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "string_attribute": tftypes.String, + }, + }, + }, + }, + }, + }, + tftypes.UnknownValue, + ), + }, + }, + + // A nested object with computed attributes, which is contained in an + // optional+computed container. The nested computed values should be + // represented in the proposed new object. + "config within optional+computed": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "list_nested_attribute": schema.ListNestedAttribute{ + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_object": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "optional": schema.StringAttribute{ + Optional: true, + }, + "computed": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_object": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_object": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "nested_object": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "prior"), + "computed": tftypes.NewValue(tftypes.String, "prior computed"), + }, + ), + }, + ), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_object": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_object": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "nested_object": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "prior"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "list_nested_attribute": tftypes.NewValue( + tftypes.List{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_object": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "nested_object": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "nested_object": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "prior"), + "computed": tftypes.NewValue(tftypes.String, "prior computed"), + }, + ), + }, + ), + }, + ), + }, + }, + + // A nested object with computed attributes, which is contained in an + // optional+computed set. The nested computed values should be + // represented in the proposed new object, and correlated with state + // via the non-computed attributes. + "config add within optional+computed set": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "optional": schema.StringAttribute{ + Optional: true, + }, + "computed": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "first"), + "computed": tftypes.NewValue(tftypes.String, "first computed"), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "second"), + "computed": tftypes.NewValue(tftypes.String, "second computed"), + }, + ), + }, + ), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "first"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "second"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "third"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "first"), + "computed": tftypes.NewValue(tftypes.String, "first computed"), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "second"), + "computed": tftypes.NewValue(tftypes.String, "second computed"), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "third"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + ), + }, + }, + + // A nested object with computed attributes, which is contained in a + // set. The nested computed values should be represented in the + // proposed new object, and correlated with state via the non-computed + // attributes. + "config add within set block": { + schema: schema.Schema{ + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "optional": schema.StringAttribute{ + Optional: true, + }, + "computed": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "first"), + "computed": tftypes.NewValue(tftypes.String, "first computed"), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "second"), + "computed": tftypes.NewValue(tftypes.String, "second from config"), + }, + ), + }, + ), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "first"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "second"), + "computed": tftypes.NewValue(tftypes.String, "second from config"), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "third"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "first"), + "computed": tftypes.NewValue(tftypes.String, "first computed"), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "second"), + "computed": tftypes.NewValue(tftypes.String, "second from config"), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "third"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + ), + }, + }, + + // A nested object with computed attributes, which is contained in a + // set. The nested computed values should be represented in the + // proposed new object, and correlated with state via the non-computed + // attributes. + "config change within set block": { + schema: schema.Schema{ + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "optional": schema.StringAttribute{ + Optional: true, + }, + "computed": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "first"), + "computed": tftypes.NewValue(tftypes.String, "first computed"), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "second"), + "computed": tftypes.NewValue(tftypes.String, "second computed"), + }, + ), + }, + ), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "first"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "changed"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "first"), + "computed": tftypes.NewValue(tftypes.String, "first computed"), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "single_nested_attribute": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + }, + map[string]tftypes.Value{ + "single_nested_attribute": tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "changed"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + ), + }, + }, + + "set attr with partial optional computed change": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_block": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional": schema.StringAttribute{ + Optional: true, + }, + "computed": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "one"), + "computed": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "two"), + "computed": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "one"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "replaced"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "one"), + "computed": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "replaced"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + }, + + "set attr without optional computed change": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional": schema.StringAttribute{ + Optional: true, + }, + "computed": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "one"), + "computed": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "two"), + "computed": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "one"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "two"), + "computed": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "one"), + "computed": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional": tftypes.String, + "computed": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional": tftypes.NewValue(tftypes.String, "two"), + "computed": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + }, + ), + }, + }, + + "set attr with all optional computed": { + schema: schema.Schema{ + Attributes: map[string]schema.Attribute{ + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_computed_attribute_a": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "optional_computed_attribute_b": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + }, + ), + }, + // Each of these values can be correlated by the existence of the + // optional config attribute. Because "one" and "two" are set in + // the config, they must exist in the state regardless of + // optional&computed. + configVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, nil), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + }, + ), + }, + }, + + "set block with all optional computed and nested object types": { + schema: schema.Schema{ + Blocks: map[string]schema.Block{ + "set_nested_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "optional_computed_attribute_a": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "optional_computed_attribute_b": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "set_nested_attribute": schema.SetNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "optional_computed_attribute_a": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "optional_computed_attribute_b": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + priorVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + }, + ), + }, + ), + }, + ), + }, + configVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, nil), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + ), + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "three"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, nil), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + nil, + ), + }, + ), + }, + ), + }, + expectedVal: map[string]tftypes.Value{ + "set_nested_block": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + }, + []tftypes.Value{ + // We can correlate this with prior from the outer object + // attributes, and the equal nested set. + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "one"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + }, + ), + }, + ), + }, + ), + // This value is overridden by config, because we can't + // correlate optional+computed config values within nested + // sets. + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, "OK"), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + []tftypes.Value{ + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "two"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, nil), + }, + ), + }, + ), + }, + ), + // This value was taken only from config + tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + "set_nested_attribute": tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + }, + }, + map[string]tftypes.Value{ + "optional_computed_attribute_a": tftypes.NewValue(tftypes.String, "three"), + "optional_computed_attribute_b": tftypes.NewValue(tftypes.String, nil), + "set_nested_attribute": tftypes.NewValue( + tftypes.Set{ + ElementType: tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "optional_computed_attribute_a": tftypes.String, + "optional_computed_attribute_b": tftypes.String, + }, + }, + }, + nil, + ), + }, + ), + }, + ), + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + priorStateVal := tftypes.NewValue(tftypes.DynamicPseudoType, nil) + if test.priorVal != nil { + schemaType := test.schema.Type().TerraformType(context.Background()) + priorStateVal = tftypes.NewValue(schemaType, test.priorVal) + } + + request := ProposeNewStateRequest{ + PriorState: tfsdk.State{ + Raw: priorStateVal, + Schema: test.schema, + }, + Config: tfsdk.Config{ + Raw: tftypes.NewValue(test.schema.Type().TerraformType(context.Background()), test.configVal), + Schema: test.schema, + }, + } + expectedResponse := &ProposeNewStateResponse{ + ProposedNewState: tfsdk.Plan{ + Raw: tftypes.NewValue(test.schema.Type().TerraformType(context.Background()), test.expectedVal), + Schema: test.schema, + }, + } + response := &ProposeNewStateResponse{} + SchemaProposeNewState(context.TODO(), test.schema, request, response) + if diff := cmp.Diff(response, expectedResponse); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +var testAttributes = map[string]schema.Attribute{ + "optional": schema.StringAttribute{ + Optional: true, + }, + "computed": schema.StringAttribute{ + Computed: true, + }, + "optional_computed": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "required": schema.StringAttribute{ + Required: true, + }, +} diff --git a/internal/fwserver/server_planresourcechange.go b/internal/fwserver/server_planresourcechange.go index 1668c0819..06f2497cf 100644 --- a/internal/fwserver/server_planresourcechange.go +++ b/internal/fwserver/server_planresourcechange.go @@ -150,7 +150,30 @@ func (s *Server) PlanResourceChange(ctx context.Context, req *PlanResourceChange } } - resp.PlannedState = planToState(*req.ProposedNewState) + // Propose new plan from prior state and config + proposeNewPlanReq := ProposeNewStateRequest{ + PriorState: *req.PriorState, + Config: *req.Config, + } + + proposeNewPlanResp := ProposeNewStateResponse{} + + SchemaProposeNewState(ctx, req.ResourceSchema, proposeNewPlanReq, &proposeNewPlanResp) + + resp.Diagnostics.Append(proposeNewPlanResp.Diagnostics...) + if resp.Diagnostics.HasError() { + return + } + + // New proposed plan + resp.PlannedState = planToState(proposeNewPlanResp.ProposedNewState) + + if !resp.PlannedState.Raw.Equal(req.ProposedNewState.Raw) { + panic("WHOA! There is a diff between Terraform core's proposed new state and the framework proposed new state :D") + } + + // Old proposed plan + // resp.PlannedState = planToState(*req.ProposedNewState) // Set Defaults. // diff --git a/internal/testing/testschema/schema.go b/internal/testing/testschema/schema.go index db91aa9cd..7d6e57fb0 100644 --- a/internal/testing/testschema/schema.go +++ b/internal/testing/testschema/schema.go @@ -24,6 +24,10 @@ type Schema struct { Version int64 } +func (s Schema) EmptyValue(ctx context.Context) tftypes.Value { + return fwschema.EmptySchemaValue(ctx, s) +} + // ApplyTerraform5AttributePathStep satisfies the fwschema.Schema interface. func (s Schema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) { return fwschema.SchemaApplyTerraform5AttributePathStep(s, step) diff --git a/provider/metaschema/schema.go b/provider/metaschema/schema.go index b43826456..a7b1ed729 100644 --- a/provider/metaschema/schema.go +++ b/provider/metaschema/schema.go @@ -30,6 +30,10 @@ type Schema struct { Attributes map[string]Attribute } +func (s Schema) EmptyValue(ctx context.Context) tftypes.Value { + return fwschema.EmptySchemaValue(ctx, s) +} + // ApplyTerraform5AttributePathStep applies the given AttributePathStep to the // schema. func (s Schema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) { diff --git a/provider/schema/schema.go b/provider/schema/schema.go index 91cb1781f..318c467c7 100644 --- a/provider/schema/schema.go +++ b/provider/schema/schema.go @@ -59,6 +59,10 @@ type Schema struct { DeprecationMessage string } +func (s Schema) EmptyValue(ctx context.Context) tftypes.Value { + return fwschema.EmptySchemaValue(ctx, s) +} + // ApplyTerraform5AttributePathStep applies the given AttributePathStep to the // schema. func (s Schema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) { diff --git a/resource/identityschema/schema.go b/resource/identityschema/schema.go index 6d68aff9a..8d77269c4 100644 --- a/resource/identityschema/schema.go +++ b/resource/identityschema/schema.go @@ -37,6 +37,10 @@ type Schema struct { Version int64 } +func (s Schema) EmptyValue(ctx context.Context) tftypes.Value { + return fwschema.EmptySchemaValue(ctx, s) +} + // ApplyTerraform5AttributePathStep applies the given AttributePathStep to the // schema. func (s Schema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) { diff --git a/resource/schema/schema.go b/resource/schema/schema.go index 0f280662f..c369a826d 100644 --- a/resource/schema/schema.go +++ b/resource/schema/schema.go @@ -72,6 +72,10 @@ type Schema struct { Version int64 } +func (s Schema) EmptyValue(ctx context.Context) tftypes.Value { + return fwschema.EmptySchemaValue(ctx, s) +} + // ApplyTerraform5AttributePathStep applies the given AttributePathStep to the // schema. func (s Schema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) {