Skip to content

Commit 0d43432

Browse files
authored
Merge pull request #44972 from hashicorp/b-autoflex-noxmlwrapper
AutoFlEx: Only attempt XML (un)wrapping if `xmlwrapper` struct field tag is set
2 parents 8c94046 + 7fe3d6e commit 0d43432

File tree

12 files changed

+613
-222
lines changed

12 files changed

+613
-222
lines changed

.changelog/44972.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
```release-note:bug
2+
resource/aws_cloudfront_continuous_deployment_policy: Fix `Source type "...cloudfront.stagingDistributionDNSNamesModel" does not implement attr.Value` error. This fixes a regression introduced in [v6.17.0](https://github.com/hashicorp/terraform-provider-aws/blob/main/CHANGELOG.md#6170-october-16-2025)

internal/framework/flex/autoflex.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,9 @@ func autoflexTags(field reflect.StructField) (string, tagOptions) {
166166
}
167167

168168
type fieldOpts struct {
169-
legacy bool
170-
omitempty bool
169+
legacy bool
170+
omitempty bool
171+
xmlWrapper bool
171172
}
172173

173174
// valueWithElementsAs extends the Value interface for values that have an ElementsAs method.

internal/framework/flex/autoflex_expand.go

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,19 @@ func (expander autoExpander) convert(ctx context.Context, sourcePath path.Path,
182182

183183
// Aggregate types.
184184
case basetypes.ObjectValuable:
185-
diags.Append(expander.object(ctx, sourcePath, vFrom, targetPath, vTo)...)
185+
diags.Append(expander.object(ctx, sourcePath, vFrom, targetPath, vTo, fieldOpts)...)
186186
return diags
187187

188188
case basetypes.ListValuable:
189-
diags.Append(expander.list(ctx, sourcePath, vFrom, targetPath, vTo)...)
189+
diags.Append(expander.list(ctx, sourcePath, vFrom, targetPath, vTo, fieldOpts)...)
190190
return diags
191191

192192
case basetypes.MapValuable:
193-
diags.Append(expander.map_(ctx, vFrom, vTo)...)
193+
diags.Append(expander.map_(ctx, vFrom, vTo, fieldOpts)...)
194194
return diags
195195

196196
case basetypes.SetValuable:
197-
diags.Append(expander.set(ctx, sourcePath, vFrom, targetPath, vTo)...)
197+
diags.Append(expander.set(ctx, sourcePath, vFrom, targetPath, vTo, fieldOpts)...)
198198
return diags
199199
}
200200

@@ -538,7 +538,7 @@ func (expander autoExpander) string(ctx context.Context, vFrom basetypes.StringV
538538
}
539539

540540
// string copies a Plugin Framework Object(ish) value to a compatible AWS API value.
541-
func (expander autoExpander) object(ctx context.Context, sourcePath path.Path, vFrom basetypes.ObjectValuable, targetPath path.Path, vTo reflect.Value) diag.Diagnostics {
541+
func (expander autoExpander) object(ctx context.Context, sourcePath path.Path, vFrom basetypes.ObjectValuable, targetPath path.Path, vTo reflect.Value, _ fieldOpts) diag.Diagnostics {
542542
var diags diag.Diagnostics
543543

544544
_, d := vFrom.ToObjectValue(ctx)
@@ -588,7 +588,7 @@ func (expander autoExpander) object(ctx context.Context, sourcePath path.Path, v
588588
}
589589

590590
// list copies a Plugin Framework List(ish) value to a compatible AWS API value.
591-
func (expander autoExpander) list(ctx context.Context, sourcePath path.Path, vFrom basetypes.ListValuable, targetPath path.Path, vTo reflect.Value) diag.Diagnostics {
591+
func (expander autoExpander) list(ctx context.Context, sourcePath path.Path, vFrom basetypes.ListValuable, targetPath path.Path, vTo reflect.Value, fieldOpts fieldOpts) diag.Diagnostics {
592592
var diags diag.Diagnostics
593593

594594
v, d := vFrom.ToListValue(ctx)
@@ -599,16 +599,16 @@ func (expander autoExpander) list(ctx context.Context, sourcePath path.Path, vFr
599599

600600
switch v.ElementType(ctx).(type) {
601601
case basetypes.Int64Typable:
602-
diags.Append(expander.listOrSetOfInt64(ctx, v, vTo)...)
602+
diags.Append(expander.listOrSetOfInt64(ctx, v, vTo, fieldOpts)...)
603603
return diags
604604

605605
case basetypes.StringTypable:
606-
diags.Append(expander.listOrSetOfString(ctx, v, vTo)...)
606+
diags.Append(expander.listOrSetOfString(ctx, v, vTo, fieldOpts)...)
607607
return diags
608608

609609
case basetypes.ObjectTypable:
610610
if vFrom, ok := vFrom.(fwtypes.NestedObjectCollectionValue); ok {
611-
diags.Append(expander.nestedObjectCollection(ctx, sourcePath, vFrom, targetPath, vTo)...)
611+
diags.Append(expander.nestedObjectCollection(ctx, sourcePath, vFrom, targetPath, vTo, fieldOpts)...)
612612
return diags
613613
}
614614
}
@@ -622,13 +622,13 @@ func (expander autoExpander) list(ctx context.Context, sourcePath path.Path, vFr
622622
}
623623

624624
// listOrSetOfInt64 copies a Plugin Framework ListOfInt64(ish) or SetOfInt64(ish) value to a compatible AWS API value.
625-
func (expander autoExpander) listOrSetOfInt64(ctx context.Context, vFrom valueWithElementsAs, vTo reflect.Value) diag.Diagnostics {
625+
func (expander autoExpander) listOrSetOfInt64(ctx context.Context, vFrom valueWithElementsAs, vTo reflect.Value, fieldOpts fieldOpts) diag.Diagnostics {
626626
var diags diag.Diagnostics
627627

628628
switch vTo.Kind() {
629629
case reflect.Struct:
630630
// Check if target is an XML wrapper struct
631-
if isXMLWrapperStruct(vTo.Type()) {
631+
if fieldOpts.xmlWrapper && isXMLWrapperStruct(vTo.Type()) {
632632
diags.Append(expander.xmlWrapper(ctx, vFrom, vTo, "Items")...)
633633
return diags
634634
}
@@ -701,13 +701,13 @@ func (expander autoExpander) listOrSetOfInt64(ctx context.Context, vFrom valueWi
701701
}
702702

703703
// listOrSetOfString copies a Plugin Framework ListOfString(ish) or SetOfString(ish) value to a compatible AWS API value.
704-
func (expander autoExpander) listOrSetOfString(ctx context.Context, vFrom valueWithElementsAs, vTo reflect.Value) diag.Diagnostics {
704+
func (expander autoExpander) listOrSetOfString(ctx context.Context, vFrom valueWithElementsAs, vTo reflect.Value, fieldOpts fieldOpts) diag.Diagnostics {
705705
var diags diag.Diagnostics
706706

707707
switch vTo.Kind() {
708708
case reflect.Struct:
709709
// Check if target is an XML wrapper struct
710-
if isXMLWrapperStruct(vTo.Type()) {
710+
if fieldOpts.xmlWrapper && isXMLWrapperStruct(vTo.Type()) {
711711
diags.Append(expander.xmlWrapper(ctx, vFrom, vTo, "Items")...)
712712
return diags
713713
}
@@ -766,7 +766,7 @@ func (expander autoExpander) listOrSetOfString(ctx context.Context, vFrom valueW
766766
}
767767

768768
// map_ copies a Plugin Framework Map(ish) value to a compatible AWS API value.
769-
func (expander autoExpander) map_(ctx context.Context, vFrom basetypes.MapValuable, vTo reflect.Value) diag.Diagnostics {
769+
func (expander autoExpander) map_(ctx context.Context, vFrom basetypes.MapValuable, vTo reflect.Value, _ fieldOpts) diag.Diagnostics {
770770
var diags diag.Diagnostics
771771

772772
v, d := vFrom.ToMapValue(ctx)
@@ -893,7 +893,7 @@ func (expander autoExpander) mapOfString(ctx context.Context, vFrom basetypes.Ma
893893
}
894894

895895
// set copies a Plugin Framework Set(ish) value to a compatible AWS API value.
896-
func (expander autoExpander) set(ctx context.Context, sourcePath path.Path, vFrom basetypes.SetValuable, targetPath path.Path, vTo reflect.Value) diag.Diagnostics {
896+
func (expander autoExpander) set(ctx context.Context, sourcePath path.Path, vFrom basetypes.SetValuable, targetPath path.Path, vTo reflect.Value, fieldOpts fieldOpts) diag.Diagnostics {
897897
var diags diag.Diagnostics
898898

899899
v, d := vFrom.ToSetValue(ctx)
@@ -904,16 +904,16 @@ func (expander autoExpander) set(ctx context.Context, sourcePath path.Path, vFro
904904

905905
switch v.ElementType(ctx).(type) {
906906
case basetypes.Int64Typable:
907-
diags.Append(expander.listOrSetOfInt64(ctx, v, vTo)...)
907+
diags.Append(expander.listOrSetOfInt64(ctx, v, vTo, fieldOpts)...)
908908
return diags
909909

910910
case basetypes.StringTypable:
911-
diags.Append(expander.listOrSetOfString(ctx, v, vTo)...)
911+
diags.Append(expander.listOrSetOfString(ctx, v, vTo, fieldOpts)...)
912912
return diags
913913

914914
case basetypes.ObjectTypable:
915915
if vFrom, ok := vFrom.(fwtypes.NestedObjectCollectionValue); ok {
916-
diags.Append(expander.nestedObjectCollection(ctx, sourcePath, vFrom, targetPath, vTo)...)
916+
diags.Append(expander.nestedObjectCollection(ctx, sourcePath, vFrom, targetPath, vTo, fieldOpts)...)
917917
return diags
918918
}
919919
}
@@ -927,13 +927,13 @@ func (expander autoExpander) set(ctx context.Context, sourcePath path.Path, vFro
927927
}
928928

929929
// nestedObjectCollection copies a Plugin Framework NestedObjectCollectionValue value to a compatible AWS API value.
930-
func (expander autoExpander) nestedObjectCollection(ctx context.Context, sourcePath path.Path, vFrom fwtypes.NestedObjectCollectionValue, targetPath path.Path, vTo reflect.Value) diag.Diagnostics {
930+
func (expander autoExpander) nestedObjectCollection(ctx context.Context, sourcePath path.Path, vFrom fwtypes.NestedObjectCollectionValue, targetPath path.Path, vTo reflect.Value, fieldOpts fieldOpts) diag.Diagnostics {
931931
var diags diag.Diagnostics
932932

933933
switch tTo := vTo.Type(); vTo.Kind() {
934934
case reflect.Struct:
935935
// Check if target is an XML wrapper struct before handling as generic struct
936-
if isXMLWrapperStruct(tTo) {
936+
if fieldOpts.xmlWrapper && isXMLWrapperStruct(tTo) {
937937
diags.Append(expander.nestedObjectCollectionToXMLWrapper(ctx, sourcePath, vFrom, targetPath, vTo)...)
938938
return diags
939939
}
@@ -947,7 +947,7 @@ func (expander autoExpander) nestedObjectCollection(ctx context.Context, sourceP
947947
switch tElem := tTo.Elem(); tElem.Kind() {
948948
case reflect.Struct:
949949
// Check if target is a pointer to XML wrapper struct
950-
if isXMLWrapperStruct(tElem) {
950+
if fieldOpts.xmlWrapper && isXMLWrapperStruct(tElem) {
951951
// Create new instance of the XML wrapper struct
952952
newWrapper := reflect.New(tElem)
953953
diags.Append(expander.nestedObjectCollectionToXMLWrapper(ctx, sourcePath, vFrom, targetPath, newWrapper.Elem())...)
@@ -1221,7 +1221,8 @@ func expandStruct(ctx context.Context, sourcePath path.Path, from any, targetPat
12211221
})
12221222

12231223
opts := fieldOpts{
1224-
legacy: fromFieldOpts.Legacy(),
1224+
legacy: fromFieldOpts.Legacy(),
1225+
xmlWrapper: fromFieldOpts.XMLWrapperField() != "",
12251226
}
12261227

12271228
diags.Append(flexer.convert(ctx, sourcePath.AtName(fromFieldName), valFrom.FieldByIndex(fromField.Index), targetPath.AtName(toFieldName), toFieldVal, opts)...)
@@ -1587,7 +1588,15 @@ func isXMLWrapperStruct(t reflect.Type) bool {
15871588
return false
15881589
}
15891590

1590-
return true
1591+
// Items and Quantity should be the only non-anonymous fields
1592+
nNonAnonymousFields := 0
1593+
for i := 0; i < t.NumField(); i++ {
1594+
if !t.Field(i).Anonymous {
1595+
nNonAnonymousFields++
1596+
}
1597+
}
1598+
1599+
return nNonAnonymousFields == 2
15911600
}
15921601

15931602
// nestedObjectCollectionToXMLWrapper converts a NestedObjectCollectionValue to an XML wrapper struct

internal/framework/flex/autoflex_flatten.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,15 @@ func (flattener autoFlattener) convert(ctx context.Context, sourcePath path.Path
185185
return diags
186186

187187
case reflect.Map:
188-
diags.Append(flattener.map_(ctx, sourcePath, vFrom, targetPath, tTo, vTo)...)
188+
diags.Append(flattener.map_(ctx, sourcePath, vFrom, targetPath, tTo, vTo, fieldOpts)...)
189189
return diags
190190

191191
case reflect.Struct:
192192
diags.Append(flattener.struct_(ctx, sourcePath, vFrom, false, targetPath, tTo, vTo, fieldOpts)...)
193193
return diags
194194

195195
case reflect.Interface:
196-
diags.Append(flattener.interface_(ctx, vFrom, tTo, vTo)...)
196+
diags.Append(flattener.interface_(ctx, vFrom, tTo, vTo, fieldOpts)...)
197197
return diags
198198
}
199199

@@ -612,7 +612,7 @@ func (flattener autoFlattener) pointer(ctx context.Context, sourcePath path.Path
612612
return diags
613613
}
614614

615-
func (flattener autoFlattener) interface_(ctx context.Context, vFrom reflect.Value, tTo attr.Type, vTo reflect.Value) diag.Diagnostics {
615+
func (flattener autoFlattener) interface_(ctx context.Context, vFrom reflect.Value, tTo attr.Type, vTo reflect.Value, _ fieldOpts) diag.Diagnostics {
616616
var diags diag.Diagnostics
617617

618618
switch tTo := tTo.(type) {
@@ -846,7 +846,7 @@ func (flattener autoFlattener) slice(ctx context.Context, sourcePath path.Path,
846846
}
847847

848848
// map_ copies an AWS API map value to a compatible Plugin Framework value.
849-
func (flattener autoFlattener) map_(ctx context.Context, sourcePath path.Path, vFrom reflect.Value, targetPath path.Path, tTo attr.Type, vTo reflect.Value) diag.Diagnostics {
849+
func (flattener autoFlattener) map_(ctx context.Context, sourcePath path.Path, vFrom reflect.Value, targetPath path.Path, tTo attr.Type, vTo reflect.Value, _ fieldOpts) diag.Diagnostics {
850850
var diags diag.Diagnostics
851851

852852
switch tMapKey := vFrom.Type().Key(); tMapKey.Kind() {
@@ -1731,7 +1731,7 @@ func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPa
17311731
for toField := range tfreflect.ExportedStructFields(typeTo) {
17321732
toFieldName := toField.Name
17331733
_, toOpts := autoflexTags(toField)
1734-
if wrapperField := toOpts.WrapperField(); wrapperField != "" {
1734+
if wrapperField := toOpts.XMLWrapperField(); wrapperField != "" {
17351735
toFieldVal := valTo.FieldByIndex(toField.Index)
17361736
if !toFieldVal.CanSet() {
17371737
continue
@@ -1776,7 +1776,7 @@ func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPa
17761776
continue
17771777
}
17781778
toFieldName := toField.Name
1779-
toNameOverride, toOpts := autoflexTags(toField)
1779+
toNameOverride, toFieldOpts := autoflexTags(toField)
17801780
toFieldVal := valTo.FieldByIndex(toField.Index)
17811781
if toNameOverride == "-" {
17821782
tflog.SubsystemTrace(ctx, subsystemName, "Skipping ignored target field", map[string]any{
@@ -1785,7 +1785,7 @@ func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPa
17851785
})
17861786
continue
17871787
}
1788-
if toOpts.NoFlatten() {
1788+
if toFieldOpts.NoFlatten() {
17891789
tflog.SubsystemTrace(ctx, subsystemName, "Skipping noflatten target field", map[string]any{
17901790
logAttrKeySourceFieldname: fromFieldName,
17911791
logAttrKeyTargetFieldname: toFieldName,
@@ -1807,7 +1807,7 @@ func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPa
18071807
})
18081808

18091809
// Check if target has wrapper tag and source is an XML wrapper struct
1810-
if wrapperField := toOpts.WrapperField(); wrapperField != "" {
1810+
if wrapperField := toFieldOpts.XMLWrapperField(); wrapperField != "" {
18111811
fromFieldVal := valFrom.FieldByIndex(fromField.Index)
18121812
if isXMLWrapperStruct(fromFieldVal.Type()) {
18131813
tflog.SubsystemTrace(ctx, subsystemName, "Converting XML wrapper struct to collection", map[string]any{
@@ -1836,8 +1836,9 @@ func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPa
18361836
}
18371837

18381838
opts := fieldOpts{
1839-
legacy: toOpts.Legacy(),
1840-
omitempty: toOpts.OmitEmpty(),
1839+
legacy: toFieldOpts.Legacy(),
1840+
omitempty: toFieldOpts.OmitEmpty(),
1841+
xmlWrapper: toFieldOpts.XMLWrapperField() != "",
18411842
}
18421843

18431844
diags.Append(flexer.convert(ctx, sourcePath.AtName(fromFieldName), valFrom.FieldByIndex(fromField.Index), targetPath.AtName(toFieldName), toFieldVal, opts)...)

internal/framework/flex/autoflex_maps_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ func TestExpandMapBlock(t *testing.T) {
213213
},
214214
"map block key list": {
215215
Source: &tfMapBlockList{
216-
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[tfMapBlockElement](ctx, []tfMapBlockElement{
216+
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []tfMapBlockElement{
217217
{
218218
MapBlockKey: types.StringValue("x"),
219219
Attr1: types.StringValue("a"),
@@ -242,7 +242,7 @@ func TestExpandMapBlock(t *testing.T) {
242242
},
243243
"map block key set": {
244244
Source: &tfMapBlockSet{
245-
MapBlock: fwtypes.NewSetNestedObjectValueOfValueSliceMust[tfMapBlockElement](ctx, []tfMapBlockElement{
245+
MapBlock: fwtypes.NewSetNestedObjectValueOfValueSliceMust(ctx, []tfMapBlockElement{
246246
{
247247
MapBlockKey: types.StringValue("x"),
248248
Attr1: types.StringValue("a"),
@@ -329,7 +329,7 @@ func TestExpandMapBlock(t *testing.T) {
329329
},
330330
"map block enum key": {
331331
Source: &tfMapBlockListEnumKey{
332-
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[tfMapBlockElementEnumKey](ctx, []tfMapBlockElementEnumKey{
332+
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []tfMapBlockElementEnumKey{
333333
{
334334
MapBlockKey: fwtypes.StringEnumValue(testEnumList),
335335
Attr1: types.StringValue("a"),
@@ -359,7 +359,7 @@ func TestExpandMapBlock(t *testing.T) {
359359

360360
"map block list no key": {
361361
Source: &tfMapBlockListNoKey{
362-
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[tfMapBlockElementNoKey](ctx, []tfMapBlockElementNoKey{
362+
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []tfMapBlockElementNoKey{
363363
{
364364
Attr1: types.StringValue("a"),
365365
Attr2: types.StringValue("b"),
@@ -491,7 +491,7 @@ func TestFlattenMapBlock(t *testing.T) {
491491
},
492492
Target: &tfMapBlockList{},
493493
WantTarget: &tfMapBlockList{
494-
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[tfMapBlockElement](ctx, []tfMapBlockElement{
494+
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []tfMapBlockElement{
495495
{
496496
MapBlockKey: types.StringValue("x"),
497497
Attr1: types.StringValue("a"),
@@ -511,7 +511,7 @@ func TestFlattenMapBlock(t *testing.T) {
511511
},
512512
Target: &tfMapBlockSet{},
513513
WantTarget: &tfMapBlockSet{
514-
MapBlock: fwtypes.NewSetNestedObjectValueOfValueSliceMust[tfMapBlockElement](ctx, []tfMapBlockElement{
514+
MapBlock: fwtypes.NewSetNestedObjectValueOfValueSliceMust(ctx, []tfMapBlockElement{
515515
{
516516
MapBlockKey: types.StringValue("x"),
517517
Attr1: types.StringValue("a"),
@@ -540,7 +540,7 @@ func TestFlattenMapBlock(t *testing.T) {
540540
},
541541
Target: &tfMapBlockList{},
542542
WantTarget: &tfMapBlockList{
543-
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[tfMapBlockElement](ctx, []tfMapBlockElement{
543+
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []tfMapBlockElement{
544544
{
545545
MapBlockKey: types.StringValue("x"),
546546
Attr1: types.StringValue("a"),
@@ -580,7 +580,7 @@ func TestFlattenMapBlock(t *testing.T) {
580580
},
581581
Target: &tfMapBlockListEnumKey{},
582582
WantTarget: &tfMapBlockListEnumKey{
583-
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[tfMapBlockElementEnumKey](ctx, []tfMapBlockElementEnumKey{
583+
MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []tfMapBlockElementEnumKey{
584584
{
585585
MapBlockKey: fwtypes.StringEnumValue(testEnumList),
586586
Attr1: types.StringValue("a"),

0 commit comments

Comments
 (0)