diff --git a/internal/framework/flex/autoflex.go b/internal/framework/flex/autoflex.go index 4a3ccd5fe458..b45cbedd4a0b 100644 --- a/internal/framework/flex/autoflex.go +++ b/internal/framework/flex/autoflex.go @@ -28,6 +28,7 @@ const ( type autoFlexer interface { convert(context.Context, path.Path, reflect.Value, path.Path, reflect.Value, fieldOpts) diag.Diagnostics getOptions() AutoFlexOptions + handleXMLWrapperCollapse(context.Context, path.Path, reflect.Value, path.Path, reflect.Value, reflect.Type, reflect.Type, map[string]bool) diag.Diagnostics } // autoFlexValues returns the underlying `reflect.Value`s of `from` and `to`. @@ -166,9 +167,10 @@ func autoflexTags(field reflect.StructField) (string, tagOptions) { } type fieldOpts struct { - legacy bool - omitempty bool - xmlWrapper bool + legacy bool + omitempty bool + xmlWrapper bool + xmlWrapperField string } // valueWithElementsAs extends the Value interface for values that have an ElementsAs method. diff --git a/internal/framework/flex/autoflex_expand.go b/internal/framework/flex/autoflex_expand.go index 28ec358c3100..a485ca38b526 100644 --- a/internal/framework/flex/autoflex_expand.go +++ b/internal/framework/flex/autoflex_expand.go @@ -8,6 +8,8 @@ import ( "fmt" "iter" "reflect" + "strings" + "sync" "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -19,6 +21,17 @@ import ( tfreflect "github.com/hashicorp/terraform-provider-aws/internal/reflect" ) +const ( + xmlWrapperFieldItems = "Items" + xmlWrapperFieldQuantity = "Quantity" +) + +var ( + // xmlWrapperCache caches XML wrapper struct detection results + xmlWrapperCache = make(map[reflect.Type]bool) + xmlWrapperCacheMutex sync.RWMutex +) + // Expand = TF --> AWS // Expander is implemented by types that customize their expansion @@ -52,7 +65,8 @@ func Expand(ctx context.Context, tfObject, apiObject any, optFns ...AutoFlexOpti } type autoExpander struct { - Options AutoFlexOptions + Options AutoFlexOptions + fieldCache map[reflect.Type]map[string]reflect.StructField } // newAutoExpander initializes an auto-expander with defaults that can be overridden @@ -67,7 +81,8 @@ func newAutoExpander(optFns []AutoFlexOptionsFunc) *autoExpander { } return &autoExpander{ - Options: o, + Options: o, + fieldCache: make(map[reflect.Type]map[string]reflect.StructField), } } @@ -75,6 +90,37 @@ func (expander autoExpander) getOptions() AutoFlexOptions { return expander.Options } +// getCachedField returns a cached field lookup or performs and caches the lookup +func (expander *autoExpander) getCachedField(t reflect.Type, fieldName string) (reflect.StructField, bool) { + if expander.fieldCache == nil { + expander.fieldCache = make(map[reflect.Type]map[string]reflect.StructField) + } + + if typeCache, exists := expander.fieldCache[t]; exists { + if field, found := typeCache[fieldName]; found { + return field, true + } + } else { + expander.fieldCache[t] = make(map[string]reflect.StructField) + } + + // Perform lookup and cache result + if field, found := t.FieldByName(fieldName); found { + expander.fieldCache[t][fieldName] = field + return field, true + } + + return reflect.StructField{}, false +} + +// getCachedFieldValue returns a cached field value lookup +func (expander *autoExpander) getCachedFieldValue(v reflect.Value, fieldName string) reflect.Value { + if field, found := expander.getCachedField(v.Type(), fieldName); found { + return v.FieldByIndex(field.Index) + } + return reflect.Value{} +} + // autoFlexConvert converts `from` to `to` using the specified auto-flexer. func autoExpandConvert(ctx context.Context, from, to any, flexer autoFlexer) diag.Diagnostics { var diags diag.Diagnostics @@ -146,6 +192,45 @@ func (expander autoExpander) convert(ctx context.Context, sourcePath path.Path, // No need to set the target value if there's no source value. if vFrom.IsNull() { + // Special case: if target is XML wrapper struct and no omitempty, create zero-value + if vTo.Kind() == reflect.Ptr && !fieldOpts.omitempty { + targetType := vTo.Type().Elem() + if targetType.Kind() == reflect.Struct && isXMLWrapperStruct(targetType) { + tflog.SubsystemDebug(ctx, subsystemName, "Source is null but target is XML wrapper without omitempty - creating zero-value struct") + zeroStruct := reflect.New(targetType) + // Initialize Items slice and Quantity + wrapperFieldName := getXMLWrapperSliceFieldName(targetType) + itemsField := zeroStruct.Elem().FieldByName(wrapperFieldName) + if itemsField.IsValid() && itemsField.Kind() == reflect.Slice { + itemsField.Set(reflect.MakeSlice(itemsField.Type(), 0, 0)) + } + quantityField := zeroStruct.Elem().FieldByName(xmlWrapperFieldQuantity) + if quantityField.IsValid() && quantityField.Kind() == reflect.Ptr { + zero := int32(0) + quantityField.Set(reflect.ValueOf(&zero)) + } + // Set zero values for other pointer fields + for i := 0; i < zeroStruct.Elem().NumField(); i++ { + field := zeroStruct.Elem().Field(i) + fieldType := targetType.Field(i) + if fieldType.Name == wrapperFieldName || fieldType.Name == xmlWrapperFieldQuantity { + continue + } + if field.Kind() == reflect.Ptr && field.CanSet() && field.IsNil() { + switch fieldType.Type.Elem().Kind() { + case reflect.Bool: + falseVal := false + field.Set(reflect.ValueOf(&falseVal)) + case reflect.Int32: + zeroVal := int32(0) + field.Set(reflect.ValueOf(&zeroVal)) + } + } + } + vTo.Set(zeroStruct) + return diags + } + } tflog.SubsystemTrace(ctx, subsystemName, "Expanding null value") return diags } @@ -628,8 +713,8 @@ func (expander autoExpander) listOrSetOfInt64(ctx context.Context, vFrom valueWi switch vTo.Kind() { case reflect.Struct: // Check if target is an XML wrapper struct - if fieldOpts.xmlWrapper && isXMLWrapperStruct(vTo.Type()) { - diags.Append(expander.xmlWrapper(ctx, vFrom, vTo, "Items")...) + if fieldOpts.xmlWrapper { + diags.Append(expander.xmlWrapper(ctx, vFrom, vTo, fieldOpts.xmlWrapperField)...) return diags } @@ -690,6 +775,21 @@ func (expander autoExpander) listOrSetOfInt64(ctx context.Context, vFrom valueWi return diags } } + + case reflect.Pointer: + switch tElem := vTo.Type().Elem(); tElem.Kind() { + case reflect.Struct: + // Check if target is a pointer to an XML wrapper struct + if fieldOpts.xmlWrapper { + // Create new instance of the XML wrapper struct + newStruct := reflect.New(tElem).Elem() + diags.Append(expander.xmlWrapper(ctx, vFrom, newStruct, fieldOpts.xmlWrapperField)...) + if !diags.HasError() { + vTo.Set(newStruct.Addr()) + } + return diags + } + } } tflog.SubsystemError(ctx, subsystemName, "AutoFlex Expand; incompatible types", map[string]any{ @@ -707,8 +807,8 @@ func (expander autoExpander) listOrSetOfString(ctx context.Context, vFrom valueW switch vTo.Kind() { case reflect.Struct: // Check if target is an XML wrapper struct - if fieldOpts.xmlWrapper && isXMLWrapperStruct(vTo.Type()) { - diags.Append(expander.xmlWrapper(ctx, vFrom, vTo, "Items")...) + if fieldOpts.xmlWrapper { + diags.Append(expander.xmlWrapper(ctx, vFrom, vTo, fieldOpts.xmlWrapperField)...) return diags } @@ -755,6 +855,21 @@ func (expander autoExpander) listOrSetOfString(ctx context.Context, vFrom valueW return diags } } + + case reflect.Pointer: + switch tElem := vTo.Type().Elem(); tElem.Kind() { + case reflect.Struct: + // Check if target is a pointer to an XML wrapper struct + if fieldOpts.xmlWrapper { + // Create new instance of the XML wrapper struct + newStruct := reflect.New(tElem).Elem() + diags.Append(expander.xmlWrapper(ctx, vFrom, newStruct, fieldOpts.xmlWrapperField)...) + if !diags.HasError() { + vTo.Set(newStruct.Addr()) + } + return diags + } + } } tflog.SubsystemError(ctx, subsystemName, "AutoFlex Expand; incompatible types", map[string]any{ @@ -765,6 +880,80 @@ func (expander autoExpander) listOrSetOfString(ctx context.Context, vFrom valueW return diags } +// listOrSetOfInt32 copies a Plugin Framework ListOfInt32(ish) or SetOfInt32(ish) value to a compatible AWS API value. +func (expander autoExpander) listOrSetOfInt32(ctx context.Context, vFrom valueWithElementsAs, vTo reflect.Value, fieldOpts fieldOpts) diag.Diagnostics { + var diags diag.Diagnostics + + switch vTo.Kind() { + case reflect.Struct: + // Check if target is an XML wrapper struct + if fieldOpts.xmlWrapper { + diags.Append(expander.xmlWrapper(ctx, vFrom, vTo, fieldOpts.xmlWrapperField)...) + return diags + } + + case reflect.Slice: + switch tSliceElem := vTo.Type().Elem(); tSliceElem.Kind() { + case reflect.Int32: + // + // types.Set(OfInt32) -> []int32 + // + tflog.SubsystemTrace(ctx, subsystemName, "Expanding with ElementsAs", map[string]any{ + logAttrKeySourceSize: len(vFrom.Elements()), + }) + var to []int32 + diags.Append(vFrom.ElementsAs(ctx, &to, false)...) + if diags.HasError() { + return diags + } + + vTo.Set(reflect.ValueOf(to)) + return diags + + case reflect.Pointer: + switch tSliceElem.Elem().Kind() { + case reflect.Int32: + // + // types.Set(OfInt32) -> []*int32 + // + tflog.SubsystemTrace(ctx, subsystemName, "Expanding with ElementsAs", map[string]any{ + logAttrKeySourceSize: len(vFrom.Elements()), + }) + var to []*int32 + diags.Append(vFrom.ElementsAs(ctx, &to, false)...) + if diags.HasError() { + return diags + } + + vTo.Set(reflect.ValueOf(to)) + return diags + } + } + + case reflect.Pointer: + switch tElem := vTo.Type().Elem(); tElem.Kind() { + case reflect.Struct: + // Check if target is a pointer to an XML wrapper struct + if fieldOpts.xmlWrapper { + // Create new instance of the XML wrapper struct + newStruct := reflect.New(tElem).Elem() + diags.Append(expander.xmlWrapper(ctx, vFrom, newStruct, fieldOpts.xmlWrapperField)...) + if !diags.HasError() { + vTo.Set(newStruct.Addr()) + } + return diags + } + } + } + + tflog.SubsystemError(ctx, subsystemName, "AutoFlex Expand; incompatible types", map[string]any{ + "from": "Set[Int32]", + "to": vTo.Kind(), + }) + + return diags +} + // map_ copies a Plugin Framework Map(ish) value to a compatible AWS API value. func (expander autoExpander) map_(ctx context.Context, vFrom basetypes.MapValuable, vTo reflect.Value, _ fieldOpts) diag.Diagnostics { var diags diag.Diagnostics @@ -907,6 +1096,10 @@ func (expander autoExpander) set(ctx context.Context, sourcePath path.Path, vFro diags.Append(expander.listOrSetOfInt64(ctx, v, vTo, fieldOpts)...) return diags + case basetypes.Int32Typable: + diags.Append(expander.listOrSetOfInt32(ctx, v, vTo, fieldOpts)...) + return diags + case basetypes.StringTypable: diags.Append(expander.listOrSetOfString(ctx, v, vTo, fieldOpts)...) return diags @@ -930,11 +1123,19 @@ func (expander autoExpander) set(ctx context.Context, sourcePath path.Path, vFro func (expander autoExpander) nestedObjectCollection(ctx context.Context, sourcePath path.Path, vFrom fwtypes.NestedObjectCollectionValue, targetPath path.Path, vTo reflect.Value, fieldOpts fieldOpts) diag.Diagnostics { var diags diag.Diagnostics + // TRACE: Log entry with field options + tflog.SubsystemTrace(ctx, subsystemName, "TRACE: nestedObjectCollection entry", map[string]any{ + "target_type": vTo.Type().String(), + "target_kind": vTo.Kind().String(), + "xmlWrapper": fieldOpts.xmlWrapper, + "xmlWrapperField": fieldOpts.xmlWrapperField, + }) + switch tTo := vTo.Type(); vTo.Kind() { case reflect.Struct: - // Check if target is an XML wrapper struct before handling as generic struct - if fieldOpts.xmlWrapper && isXMLWrapperStruct(tTo) { - diags.Append(expander.nestedObjectCollectionToXMLWrapper(ctx, sourcePath, vFrom, targetPath, vTo)...) + // Check if xmlwrapper tag is present + if fieldOpts.xmlWrapper { + diags.Append(expander.nestedObjectCollectionToXMLWrapper(ctx, sourcePath, vFrom, targetPath, vTo, fieldOpts.xmlWrapperField)...) return diags } @@ -946,11 +1147,11 @@ func (expander autoExpander) nestedObjectCollection(ctx context.Context, sourceP case reflect.Pointer: switch tElem := tTo.Elem(); tElem.Kind() { case reflect.Struct: - // Check if target is a pointer to XML wrapper struct - if fieldOpts.xmlWrapper && isXMLWrapperStruct(tElem) { + // Check if xmlwrapper tag is present + if fieldOpts.xmlWrapper { // Create new instance of the XML wrapper struct newWrapper := reflect.New(tElem) - diags.Append(expander.nestedObjectCollectionToXMLWrapper(ctx, sourcePath, vFrom, targetPath, newWrapper.Elem())...) + diags.Append(expander.nestedObjectCollectionToXMLWrapper(ctx, sourcePath, vFrom, targetPath, newWrapper.Elem(), fieldOpts.xmlWrapperField)...) if !diags.HasError() { vTo.Set(newWrapper) } @@ -1042,6 +1243,19 @@ func (expander autoExpander) nestedObjectToStruct(ctx context.Context, sourcePat if diags.HasError() { return diags } + + // Rule 2 XML wrapper: After expanding nested object, check if any field had xmlwrapper tag + // and set Quantity field if target is XML wrapper struct + fromVal := reflect.ValueOf(from).Elem() + for i := 0; i < fromVal.NumField(); i++ { + field := fromVal.Type().Field(i) + _, fieldOpts := autoflexTags(field) + if wrapperField := fieldOpts.XMLWrapperField(); wrapperField != "" { + // Found xmlwrapper tag - set Quantity in target + setXMLWrapperQuantity(to, wrapperField) + break + } + } } // Set value. @@ -1192,10 +1406,34 @@ func expandStruct(ctx context.Context, sourcePath path.Path, from any, targetPat typeFrom := valFrom.Type() typeTo := valTo.Type() + // Handle XML wrapper collapse patterns where multiple source fields + // need to be combined into a single complex target field + processedFields := make(map[string]bool) + diags.Append(flexer.handleXMLWrapperCollapse(ctx, sourcePath, valFrom, targetPath, valTo, typeFrom, typeTo, processedFields)...) + if diags.HasError() { + return diags + } + for fromField := range expandSourceFields(ctx, typeFrom, flexer.getOptions()) { fromFieldName := fromField.Name _, fromFieldOpts := autoflexTags(fromField) + // TRACE: Log XML wrapper tag detection + if xmlWrapperField := fromFieldOpts.XMLWrapperField(); xmlWrapperField != "" { + tflog.SubsystemTrace(ctx, subsystemName, "TRACE: Found xmlwrapper tag", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + "xmlWrapperField": xmlWrapperField, + }) + } + + // Skip fields that were already processed by XML wrapper collapse + if processedFields[fromFieldName] { + tflog.SubsystemTrace(ctx, subsystemName, "Skipping field already processed by XML wrapper collapse", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + }) + continue + } + toField, ok := (&fuzzyFieldFinder{}).findField(ctx, fromFieldName, typeFrom, typeTo, flexer) if !ok { // Corresponding field not found in to. @@ -1221,8 +1459,10 @@ func expandStruct(ctx context.Context, sourcePath path.Path, from any, targetPat }) opts := fieldOpts{ - legacy: fromFieldOpts.Legacy(), - xmlWrapper: fromFieldOpts.XMLWrapperField() != "", + legacy: fromFieldOpts.Legacy(), + omitempty: fromFieldOpts.OmitEmpty(), + xmlWrapper: fromFieldOpts.XMLWrapperField() != "", + xmlWrapperField: fromFieldOpts.XMLWrapperField(), } diags.Append(flexer.convert(ctx, sourcePath.AtName(fromFieldName), valFrom.FieldByIndex(fromField.Index), targetPath.AtName(toFieldName), toFieldVal, opts)...) @@ -1465,143 +1705,259 @@ func diagExpandingIncompatibleTypes(sourceType, targetType reflect.Type) diag.Er // xmlWrapper handles expansion from TF collection types to AWS XML wrapper structs // that follow the pattern: {Items: []T, Quantity: *int32} -func (expander autoExpander) xmlWrapper(ctx context.Context, vFrom valueWithElementsAs, vTo reflect.Value, wrapperField string) diag.Diagnostics { +func (expander *autoExpander) xmlWrapper(ctx context.Context, vFrom valueWithElementsAs, vTo reflect.Value, wrapperField string) diag.Diagnostics { var diags diag.Diagnostics - // Verify target is a struct with Items and Quantity fields - if !isXMLWrapperStruct(vTo.Type()) { - tflog.SubsystemError(ctx, subsystemName, "Target is not a valid XML wrapper struct", map[string]any{ - "target_type": vTo.Type().String(), - }) - diags.Append(diagExpandingIncompatibleTypes(reflect.TypeOf(vFrom), vTo.Type())) + // Validate target structure + itemsField, quantityField, d := expander.validateXMLWrapperTarget(vTo, wrapperField) + diags.Append(d...) + if diags.HasError() { return diags } - // Get the Items and Quantity fields - itemsField := vTo.FieldByName("Items") - quantityField := vTo.FieldByName("Quantity") + // Convert elements to slice + itemsSlice, d := expander.convertElementsToXMLWrapperSlice(ctx, vFrom.Elements(), itemsField.Type()) + diags.Append(d...) + if diags.HasError() { + return diags + } - if !itemsField.IsValid() || !quantityField.IsValid() { - tflog.SubsystemError(ctx, subsystemName, "XML wrapper struct missing required fields") + // Set fields + itemsField.Set(itemsSlice) + if err := setXMLWrapperQuantityField(quantityField, int32(len(vFrom.Elements()))); err != nil { diags.Append(diagExpandingIncompatibleTypes(reflect.TypeOf(vFrom), vTo.Type())) return diags } - // Convert the collection elements to a slice - elements := vFrom.Elements() - itemsSliceType := itemsField.Type() - itemsSlice := reflect.MakeSlice(itemsSliceType, len(elements), len(elements)) + return diags +} + +// validateXMLWrapperTarget validates target structure and returns field references +func (expander *autoExpander) validateXMLWrapperTarget(vTo reflect.Value, wrapperField string) (itemsField, quantityField reflect.Value, diags diag.Diagnostics) { + itemsField = expander.getCachedFieldValue(vTo, wrapperField) + quantityField = expander.getCachedFieldValue(vTo, xmlWrapperFieldQuantity) + + if !itemsField.IsValid() || !quantityField.IsValid() { + diags.Append(diagExpandingIncompatibleTypes(reflect.TypeOf(vTo.Interface()), vTo.Type())) + } + return +} + +// convertElementsToXMLWrapperSlice converts collection elements to target slice +func (expander *autoExpander) convertElementsToXMLWrapperSlice(ctx context.Context, elements []attr.Value, sliceType reflect.Type) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + slice := reflect.MakeSlice(sliceType, len(elements), len(elements)) + targetElemType := sliceType.Elem() - // Convert each element for i, elem := range elements { - itemValue := itemsSlice.Index(i) + itemValue := slice.Index(i) if !itemValue.CanSet() { continue } - // Handle different element types - switch elemTyped := elem.(type) { - case basetypes.StringValuable: - if itemsSliceType.Elem().Kind() == reflect.String { - strVal, d := elemTyped.ToStringValue(ctx) - diags.Append(d...) - if !diags.HasError() { - itemValue.SetString(strVal.ValueString()) - } - } - case basetypes.Int64Valuable: - if elemKind := itemsSliceType.Elem().Kind(); elemKind == reflect.Int32 { - int64Val, d := elemTyped.ToInt64Value(ctx) - diags.Append(d...) - if !diags.HasError() { - itemValue.SetInt(int64(int32(int64Val.ValueInt64()))) - } - } else if elemKind == reflect.Int64 { - int64Val, d := elemTyped.ToInt64Value(ctx) - diags.Append(d...) - if !diags.HasError() { - itemValue.SetInt(int64Val.ValueInt64()) - } - } - case basetypes.Int32Valuable: - if itemsSliceType.Elem().Kind() == reflect.Int32 { - int32Val, d := elemTyped.ToInt32Value(ctx) - diags.Append(d...) - if !diags.HasError() { - itemValue.SetInt(int64(int32Val.ValueInt32())) - } - } - default: - // For complex types, try direct assignment if types are compatible - if elem != nil && !elem.IsNull() && !elem.IsUnknown() { - if itemValue.Type().AssignableTo(reflect.TypeOf(elem)) { - itemValue.Set(reflect.ValueOf(elem)) - } - } + diags.Append(expander.convertElementToXMLWrapperItem(ctx, elem, itemValue, targetElemType)...) + if diags.HasError() { + return reflect.Value{}, diags } } - // Set the Items field - if itemsField.CanSet() { - itemsField.Set(itemsSlice) + return slice, diags +} + +// convertElementToXMLWrapperItem converts single element to target item +func (expander *autoExpander) convertElementToXMLWrapperItem(ctx context.Context, elem attr.Value, itemValue reflect.Value, targetElemType reflect.Type) diag.Diagnostics { + switch elemTyped := elem.(type) { + case basetypes.StringValuable: + return convertStringValueableToXMLItem(ctx, elemTyped, itemValue, targetElemType) + case basetypes.Int64Valuable: + return convertInt64ValueableToXMLItem(ctx, elemTyped, itemValue, targetElemType) + case basetypes.Int32Valuable: + return convertInt32ValueableToXMLItem(ctx, elemTyped, itemValue, targetElemType) + default: + return convertComplexValueToXMLItem(elem, itemValue, targetElemType) } +} - // Set the Quantity field - if quantityField.CanSet() && quantityField.Type().Kind() == reflect.Pointer { - quantity := int32(len(elements)) - quantityPtr := reflect.New(quantityField.Type().Elem()) - quantityPtr.Elem().Set(reflect.ValueOf(quantity)) - quantityField.Set(quantityPtr) +// convertStringValueableToXMLItem converts string values to XML wrapper items +func convertStringValueableToXMLItem(ctx context.Context, elem basetypes.StringValuable, itemValue reflect.Value, targetElemType reflect.Type) diag.Diagnostics { + var diags diag.Diagnostics + + strVal, d := elem.ToStringValue(ctx) + diags.Append(d...) + if diags.HasError() || strVal.IsNull() { + return diags } - tflog.SubsystemTrace(ctx, subsystemName, "Successfully expanded to XML wrapper", map[string]any{ - "source_type": reflect.TypeOf(vFrom).String(), - "target_type": vTo.Type().String(), - "items_count": len(elements), - "wrapper_field": wrapperField, - }) + switch targetElemType.Kind() { + case reflect.Pointer: + enumPtr := reflect.New(targetElemType.Elem()) + enumPtr.Elem().SetString(strVal.ValueString()) + itemValue.Set(enumPtr) + case reflect.String: + itemValue.SetString(strVal.ValueString()) + } + return diags +} + +// convertInt64ValueableToXMLItem converts int64 values to XML wrapper items +func convertInt64ValueableToXMLItem(ctx context.Context, elem basetypes.Int64Valuable, itemValue reflect.Value, targetElemType reflect.Type) diag.Diagnostics { + var diags diag.Diagnostics + int64Val, d := elem.ToInt64Value(ctx) + diags.Append(d...) + if diags.HasError() { + return diags + } + + switch targetElemType.Kind() { + case reflect.Int32: + itemValue.SetInt(int64(int32(int64Val.ValueInt64()))) + case reflect.Int64: + itemValue.SetInt(int64Val.ValueInt64()) + } + return diags +} + +// convertInt32ValueableToXMLItem converts int32 values to XML wrapper items +func convertInt32ValueableToXMLItem(ctx context.Context, elem basetypes.Int32Valuable, itemValue reflect.Value, targetElemType reflect.Type) diag.Diagnostics { + var diags diag.Diagnostics + + int32Val, d := elem.ToInt32Value(ctx) + diags.Append(d...) + if diags.HasError() { + return diags + } + + if targetElemType.Kind() == reflect.Int32 { + itemValue.SetInt(int64(int32Val.ValueInt32())) + } return diags } +// convertComplexValueToXMLItem handles complex type conversions +func convertComplexValueToXMLItem(elem attr.Value, itemValue reflect.Value, targetElemType reflect.Type) diag.Diagnostics { + if elem != nil && !elem.IsNull() && !elem.IsUnknown() { + if itemValue.Type().AssignableTo(reflect.TypeOf(elem)) { + itemValue.Set(reflect.ValueOf(elem)) + } + } + return nil +} + +// setXMLWrapperQuantityField sets the Quantity field in XML wrapper struct +func setXMLWrapperQuantityField(quantityField reflect.Value, count int32) error { + if !quantityField.CanSet() || quantityField.Type().Kind() != reflect.Pointer { + return fmt.Errorf("quantity field cannot be set") + } + + quantityPtr := reflect.New(quantityField.Type().Elem()) + quantityPtr.Elem().Set(reflect.ValueOf(count)) + quantityField.Set(quantityPtr) + return nil +} + +// setXMLWrapperQuantity sets the Quantity field based on Items field length +func setXMLWrapperQuantity(vStruct reflect.Value, itemsFieldName string) { + if vStruct.Kind() == reflect.Pointer { + if vStruct.IsNil() { + return + } + vStruct = vStruct.Elem() + } + + itemsField := vStruct.FieldByName(itemsFieldName) + quantityField := vStruct.FieldByName(xmlWrapperFieldQuantity) + + if itemsField.IsValid() && itemsField.Kind() == reflect.Slice && quantityField.IsValid() { + count := int32(itemsField.Len()) + _ = setXMLWrapperQuantityField(quantityField, count) + } +} + // isXMLWrapperStruct detects AWS SDK types that follow the XML wrapper pattern // with Items slice and Quantity pointer fields func isXMLWrapperStruct(t reflect.Type) bool { - if t.Kind() != reflect.Struct { - return false + // Check cache first with read lock + xmlWrapperCacheMutex.RLock() + result, cached := xmlWrapperCache[t] + xmlWrapperCacheMutex.RUnlock() + + if cached { + return result } - // Check for Items field (slice) - itemsField, hasItems := t.FieldByName("Items") - if !hasItems || itemsField.Type.Kind() != reflect.Slice { + result = isXMLWrapperStructUncached(t) + + // Cache result with write lock + xmlWrapperCacheMutex.Lock() + xmlWrapperCache[t] = result + xmlWrapperCacheMutex.Unlock() + + return result +} + +// isXMLWrapperStructUncached performs the actual XML wrapper detection +func isXMLWrapperStructUncached(t reflect.Type) bool { + if t.Kind() != reflect.Struct { return false } - // Check for Quantity field (pointer to int32) - quantityField, hasQuantity := t.FieldByName("Quantity") - if !hasQuantity || quantityField.Type.Kind() != reflect.Pointer { - return false + hasSliceField := false + hasQuantityField := false + + // Check if struct has the XML wrapper pattern: + // - One slice field (Items, Elements, Members, etc.) + // - One Quantity field (*int32) + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + fieldType := field.Type + + // Check for slice field + if fieldType.Kind() == reflect.Slice { + hasSliceField = true + } + + // Check for Quantity field (pointer to int32) + if field.Name == xmlWrapperFieldQuantity && fieldType.Kind() == reflect.Pointer && fieldType.Elem().Kind() == reflect.Int32 { + hasQuantityField = true + } } - // Quantity should be *int32 - if quantityField.Type.Elem().Kind() != reflect.Int32 { - return false + return hasSliceField && hasQuantityField +} + +// getXMLWrapperSliceFieldName returns the name of the slice field in an XML wrapper struct +func getXMLWrapperSliceFieldName(t reflect.Type) string { + if t.Kind() != reflect.Struct { + return "" } - // Items and Quantity should be the only non-anonymous fields - nNonAnonymousFields := 0 for i := 0; i < t.NumField(); i++ { - if !t.Field(i).Anonymous { - nNonAnonymousFields++ + field := t.Field(i) + if field.Type.Kind() == reflect.Slice { + return field.Name } } - return nNonAnonymousFields == 2 + return "" } // nestedObjectCollectionToXMLWrapper converts a NestedObjectCollectionValue to an XML wrapper struct -// that follows the pattern: {Items: []T, Quantity: *int32} -func (expander autoExpander) nestedObjectCollectionToXMLWrapper(ctx context.Context, _ path.Path, vFrom fwtypes.NestedObjectCollectionValue, _ path.Path, vTo reflect.Value) diag.Diagnostics { +// +// XML Wrapper Compatibility Rules: +// Rule 1: Items/Quantity only - Direct collection mapping +// +// AWS: {Items: []T, Quantity: *int32} +// TF: Repeatable singular blocks (e.g., lambda_function_association { ... }) +// +// Rule 2: Items/Quantity + additional fields - Single plural block +// +// AWS: {Items: []T, Quantity: *int32, Enabled: *bool, ...} +// TF: Single plural block (e.g., trusted_signers { items = [...], enabled = true }) +// +// Supports both Rule 1 (Items/Quantity only) and Rule 2 (Items/Quantity + additional fields) +func (expander *autoExpander) nestedObjectCollectionToXMLWrapper(ctx context.Context, _ path.Path, vFrom fwtypes.NestedObjectCollectionValue, _ path.Path, vTo reflect.Value, wrapperField string) diag.Diagnostics { var diags diag.Diagnostics tflog.SubsystemTrace(ctx, subsystemName, "Expanding NestedObjectCollection to XML wrapper", map[string]any{ @@ -1609,6 +1965,11 @@ func (expander autoExpander) nestedObjectCollectionToXMLWrapper(ctx context.Cont "target_type": vTo.Type().String(), }) + tflog.SubsystemTrace(ctx, subsystemName, "Expanding NestedObjectCollection to XML wrapper", map[string]any{ + "source_type": vFrom.Type(ctx).String(), + "target_type": vTo.Type().String(), + }) + // Get the nested Objects as a slice from, d := vFrom.ToObjectSlice(ctx) diags.Append(d...) @@ -1623,13 +1984,121 @@ func (expander autoExpander) nestedObjectCollectionToXMLWrapper(ctx context.Cont return diags } - // Get the Items and Quantity fields from target struct - itemsField := vTo.FieldByName("Items") - quantityField := vTo.FieldByName("Quantity") + // Check if this is Rule 2 pattern (single nested object with items + additional fields) + if fromSlice.Len() == 1 { + nestedObj := fromSlice.Index(0) + + // TRACE: Log Rule 2 detection attempt + tflog.SubsystemTrace(ctx, subsystemName, "TRACE: Checking for Rule 2 pattern", map[string]any{ + "fromSlice_len": fromSlice.Len(), + "nested_kind": nestedObj.Kind().String(), + }) + + // Handle pointer to struct (which is what NestedObjectCollection contains) + if nestedObj.Kind() == reflect.Ptr && !nestedObj.IsNil() { + structObj := nestedObj.Elem() + if structObj.Kind() == reflect.Struct { + // Check if the struct has a wrapper field - indicates Rule 2 + itemsField := structObj.FieldByName(wrapperField) + if itemsField.IsValid() { + tflog.SubsystemTrace(ctx, subsystemName, "TRACE: Detected Rule 2 - delegating to expandRule2XMLWrapper", map[string]any{ + "wrapper_field": wrapperField, + }) + return expander.expandRule2XMLWrapper(ctx, nestedObj, vTo, wrapperField) + } + } + } + } + + tflog.SubsystemTrace(ctx, subsystemName, "TRACE: Using Rule 1 - direct collection to XML wrapper") + + // Rule 1: Direct collection to XML wrapper (existing logic) + return expander.expandRule1XMLWrapper(ctx, fromSlice, vTo, wrapperField) +} + +// expandRule2XMLWrapper handles Rule 2: single plural block with items + additional fields +func (expander *autoExpander) expandRule2XMLWrapper(ctx context.Context, nestedObjPtr reflect.Value, vTo reflect.Value, wrapperField string) diag.Diagnostics { + var diags diag.Diagnostics + + tflog.SubsystemTrace(ctx, subsystemName, "Expanding Rule 2 XML wrapper (items + additional fields)") + + // Get target fields + itemsField := expander.getCachedFieldValue(vTo, wrapperField) + quantityField := expander.getCachedFieldValue(vTo, xmlWrapperFieldQuantity) if !itemsField.IsValid() || !quantityField.IsValid() { - tflog.SubsystemError(ctx, subsystemName, "XML wrapper struct missing required fields") - diags.Append(diagExpandingIncompatibleTypes(reflect.TypeOf(vFrom), vTo.Type())) + diags.Append(diagExpandingIncompatibleTypes(nestedObjPtr.Type(), vTo.Type())) + return diags + } + + // Get the struct from the pointer + nestedObj := nestedObjPtr.Elem() + + // Extract wrapper field from nested object + itemsSourceField := expander.getCachedFieldValue(nestedObj, wrapperField) + if !itemsSourceField.IsValid() { + diags.AddError("Missing items field", fmt.Sprintf("Rule 2 XML wrapper requires '%s' field", wrapperField)) + return diags + } + + // Convert items collection to Items slice using existing logic + if itemsAttr, ok := itemsSourceField.Interface().(attr.Value); ok { + if collectionValue, ok := itemsAttr.(valueWithElementsAs); ok { + diags.Append(expander.convertCollectionToXMLWrapperFields(ctx, collectionValue, itemsField, quantityField)...) + if diags.HasError() { + return diags + } + } + } + + // Handle additional fields (e.g., Enabled, CachedMethods) + for i := 0; i < vTo.NumField(); i++ { + targetField := vTo.Field(i) + targetFieldType := vTo.Type().Field(i) + fieldName := targetFieldType.Name + + // Skip wrapper field and Quantity (already handled) + if fieldName == wrapperField || fieldName == xmlWrapperFieldQuantity { + continue + } + + // Look for matching field in nested object + sourceField := nestedObj.FieldByName(fieldName) + if sourceField.IsValid() && targetField.CanAddr() { + // Check if source field has xmlwrapper tag + sourceFieldType, ok := nestedObj.Type().FieldByName(fieldName) + if !ok { + continue + } + _, sourceFieldOpts := autoflexTags(sourceFieldType) + + // Convert TF field to AWS field with fieldOpts + opts := fieldOpts{ + xmlWrapper: sourceFieldOpts.XMLWrapperField() != "", + xmlWrapperField: sourceFieldOpts.XMLWrapperField(), + } + diags.Append(expander.convert(ctx, path.Empty(), sourceField, path.Empty(), targetField, opts)...) + if diags.HasError() { + return diags + } + } + } + + return diags +} + +// expandRule1XMLWrapper handles Rule 1: direct collection to XML wrapper (existing logic) +func (expander *autoExpander) expandRule1XMLWrapper(ctx context.Context, fromSlice reflect.Value, vTo reflect.Value, wrapperField string) diag.Diagnostics { + var diags diag.Diagnostics + + tflog.SubsystemTrace(ctx, subsystemName, "Expanding Rule 1 XML wrapper (direct collection)") + + // Get the wrapper fields from target struct + itemsField := expander.getCachedFieldValue(vTo, wrapperField) + quantityField := expander.getCachedFieldValue(vTo, xmlWrapperFieldQuantity) + + if !itemsField.IsValid() || !quantityField.IsValid() { + diags.Append(diagExpandingIncompatibleTypes(fromSlice.Type(), vTo.Type())) return diags } @@ -1658,7 +2127,7 @@ func (expander autoExpander) nestedObjectCollectionToXMLWrapper(ctx context.Cont return diags } targetItem.Set(newItem) - } else { + } else if targetItemType.Kind() == reflect.Struct { // For []struct - need to set the value directly newItem := reflect.New(targetItemType) diags.Append(autoExpandConvert(ctx, sourceItem.Interface(), newItem.Interface(), expander)...) @@ -1666,6 +2135,12 @@ func (expander autoExpander) nestedObjectCollectionToXMLWrapper(ctx context.Cont return diags } targetItem.Set(newItem.Elem()) + } else { + // For primitive types ([]int32, []string, etc.) - direct conversion + diags.Append(autoExpandConvert(ctx, sourceItem.Interface(), targetItem.Addr().Interface(), expander)...) + if diags.HasError() { + return diags + } } } @@ -1682,9 +2157,558 @@ func (expander autoExpander) nestedObjectCollectionToXMLWrapper(ctx context.Cont quantityField.Set(quantityPtr) } - tflog.SubsystemTrace(ctx, subsystemName, "Successfully expanded NestedObjectCollection to XML wrapper", map[string]any{ - "items_count": itemsCount, + return diags +} + +// handleXMLWrapperCollapse handles the special case where multiple source fields +// need to be combined into a single complex target field containing XML wrapper structures. +// This handles patterns like: +// - Source: separate XMLWrappedEnumSlice and Other fields +// - Target: single XMLWrappedEnumSlice field containing XMLWrappedEnumSliceOther struct +// with Items/Quantity from main field and Other nested XML wrapper from other field +func (expander autoExpander) handleXMLWrapperCollapse(ctx context.Context, sourcePath path.Path, valFrom reflect.Value, targetPath path.Path, valTo reflect.Value, typeFrom, typeTo reflect.Type, processedFields map[string]bool) diag.Diagnostics { + var diags diag.Diagnostics + + // Look for target fields that are complex XML wrapper structures + for i := 0; i < typeTo.NumField(); i++ { + toField := typeTo.Field(i) + toFieldName := toField.Name + toFieldType := toField.Type + toFieldVal := valTo.Field(i) + + // Check if this is a pointer to a struct or direct struct + var targetStructType reflect.Type + if toFieldType.Kind() == reflect.Pointer && toFieldType.Elem().Kind() == reflect.Struct { + targetStructType = toFieldType.Elem() + } else if toFieldType.Kind() == reflect.Struct { + targetStructType = toFieldType + } else { + continue + } + + // Check if this target struct has the XML wrapper collapse pattern: + // - Contains Items/Quantity fields (making it an XML wrapper) + // - Contains additional fields that should come from other source fields + if !expander.isXMLWrapperCollapseTarget(targetStructType) { + continue + } + + tflog.SubsystemTrace(ctx, subsystemName, "Found XML wrapper collapse target", map[string]any{ + logAttrKeyTargetFieldname: toFieldName, + logAttrKeyTargetType: targetStructType.String(), + }) + + // Handle XML wrapper collapse patterns generically + diags.Append(expander.buildGenericXMLWrapperCollapse(ctx, sourcePath, valFrom, targetPath.AtName(toFieldName), toFieldVal, typeFrom, targetStructType, toFieldType.Kind() == reflect.Pointer, processedFields)...) + if diags.HasError() { + return diags + } + } + + return diags +} + +// isXMLWrapperCollapseTarget checks if a struct type represents a target that should be +// populated via XML wrapper collapse (multiple source fields -> single complex target) +func (expander autoExpander) isXMLWrapperCollapseTarget(structType reflect.Type) bool { + hasSliceField := false + hasQuantity := false + hasOtherFields := false + + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + fieldName := field.Name + + if field.Type.Kind() == reflect.Slice { + hasSliceField = true + } else if fieldName == xmlWrapperFieldQuantity { + hasQuantity = true + } else { + // Any other field suggests this is a complex collapse target + hasOtherFields = true + } + } + + // Must have slice field + Quantity (XML wrapper pattern) plus other fields + return hasSliceField && hasQuantity && hasOtherFields +} + +// convertCollectionToItemsQuantity converts a source collection to Items slice and Quantity fields +func (expander autoExpander) convertCollectionToItemsQuantity(ctx context.Context, sourceFieldVal reflect.Value, targetPath path.Path, itemsField, quantityField reflect.Value) diag.Diagnostics { + var diags diag.Diagnostics + + sourceValue, ok := sourceFieldVal.Interface().(attr.Value) + if !ok { + tflog.SubsystemError(ctx, subsystemName, "Source field is not an attr.Value") + return diags + } + + // Convert based on source type + switch vFrom := sourceValue.(type) { + case basetypes.SetValuable, basetypes.ListValuable: + if setValue, ok := vFrom.(valueWithElementsAs); ok { + // Use existing logic to convert to Items/Quantity, but target specific fields + diags.Append(expander.convertCollectionToXMLWrapperFields(ctx, setValue, itemsField, quantityField)...) + if diags.HasError() { + return diags + } + } + default: + tflog.SubsystemError(ctx, subsystemName, "Unsupported source type for Items/Quantity conversion", map[string]any{ + "source_type": fmt.Sprintf("%T", vFrom), + }) + } + + return diags +} + +// convertCollectionToXMLWrapperFields converts a collection directly to Items and Quantity fields +func (expander autoExpander) convertCollectionToXMLWrapperFields(ctx context.Context, vFrom valueWithElementsAs, itemsField, quantityField reflect.Value) diag.Diagnostics { + var diags diag.Diagnostics + + // Get the source elements + elements := vFrom.Elements() + itemsCount := len(elements) + + // Create the Items slice + if itemsField.CanSet() { + itemsType := itemsField.Type() + itemsSlice := reflect.MakeSlice(itemsType, itemsCount, itemsCount) + + // Convert each element - use the same logic as xmlWrapper + for i, elem := range elements { + itemValue := itemsSlice.Index(i) + if !itemValue.CanSet() { + continue + } + + // Handle different element types + switch elemTyped := elem.(type) { + case basetypes.StringValuable: + // Check if target is a pointer to the enum type and source is StringEnum + if itemsType.Elem().Kind() == reflect.Pointer { + // Try to extract StringEnum value for pointer slice conversion + strVal, d := elemTyped.ToStringValue(ctx) + diags.Append(d...) + if !diags.HasError() && !strVal.IsNull() { + enumValue := strVal.ValueString() + + // Create a pointer to the enum type + enumPtr := reflect.New(itemsType.Elem().Elem()) + enumPtr.Elem().SetString(enumValue) + itemValue.Set(enumPtr) + } + } else if itemsType.Elem().Kind() == reflect.String { + strVal, d := elemTyped.ToStringValue(ctx) + diags.Append(d...) + if !diags.HasError() { + itemValue.SetString(strVal.ValueString()) + } + } + case basetypes.Int64Valuable: + if elemKind := itemsType.Elem().Kind(); elemKind == reflect.Int32 { + int64Val, d := elemTyped.ToInt64Value(ctx) + diags.Append(d...) + if !diags.HasError() { + itemValue.SetInt(int64(int32(int64Val.ValueInt64()))) + } + } else if elemKind == reflect.Int64 { + int64Val, d := elemTyped.ToInt64Value(ctx) + diags.Append(d...) + if !diags.HasError() { + itemValue.SetInt(int64Val.ValueInt64()) + } + } + case basetypes.Int32Valuable: + if itemsType.Elem().Kind() == reflect.Int32 { + int32Val, d := elemTyped.ToInt32Value(ctx) + diags.Append(d...) + if !diags.HasError() { + itemValue.SetInt(int64(int32Val.ValueInt32())) + } + } + default: + // For complex types, try direct assignment if types are compatible + if elem != nil && !elem.IsNull() && !elem.IsUnknown() { + if itemValue.Type().AssignableTo(reflect.TypeOf(elem)) { + itemValue.Set(reflect.ValueOf(elem)) + } + } + } + } + + itemsField.Set(itemsSlice) + } + + // Set the Quantity field + if quantityField.CanSet() && quantityField.Type().Kind() == reflect.Pointer { + quantity := int32(itemsCount) + quantityPtr := reflect.New(quantityField.Type().Elem()) + quantityPtr.Elem().Set(reflect.ValueOf(quantity)) + quantityField.Set(quantityPtr) + } + + return diags +} + +// shouldConvertToXMLWrapper determines if a source field should be converted to XML wrapper format +func (expander autoExpander) shouldConvertToXMLWrapper(sourceFieldVal, targetFieldVal reflect.Value) bool { + // Check if source is a collection (Set/List) and target is XML wrapper struct + sourceValue, ok := sourceFieldVal.Interface().(attr.Value) + if !ok { + return false + } + + // Source should be a collection + switch sourceValue.(type) { + case basetypes.SetValuable, basetypes.ListValuable: + // Target should be a pointer to struct or struct with XML wrapper pattern + targetType := targetFieldVal.Type() + if targetType.Kind() == reflect.Pointer && targetType.Elem().Kind() == reflect.Struct { + return isXMLWrapperStruct(targetType.Elem()) + } + if targetType.Kind() == reflect.Struct { + return isXMLWrapperStruct(targetType) + } + } + + return false +} + +// convertToXMLWrapper converts a source collection to an XML wrapper structure +func (expander autoExpander) convertToXMLWrapper(ctx context.Context, sourceFieldVal reflect.Value, targetPath path.Path, targetFieldVal reflect.Value) diag.Diagnostics { + var diags diag.Diagnostics + + sourceValue, ok := sourceFieldVal.Interface().(attr.Value) + if !ok { + tflog.SubsystemError(ctx, subsystemName, "Source field is not an attr.Value", map[string]any{ + "source_type": sourceFieldVal.Type().String(), + }) + return diags + } + + // Handle conversion based on source type + switch vFrom := sourceValue.(type) { + case basetypes.SetValuable: + if setValue, ok := vFrom.(valueWithElementsAs); ok { + diags.Append(expander.listOrSetOfString(ctx, setValue, targetFieldVal, fieldOpts{})...) + if diags.HasError() { + return diags + } + } + case basetypes.ListValuable: + if listValue, ok := vFrom.(valueWithElementsAs); ok { + diags.Append(expander.listOrSetOfString(ctx, listValue, targetFieldVal, fieldOpts{})...) + if diags.HasError() { + return diags + } + } + default: + tflog.SubsystemError(ctx, subsystemName, "Unsupported source type for XML wrapper conversion", map[string]any{ + "source_type": fmt.Sprintf("%T", vFrom), + }) + } + + return diags +} + +// buildGenericXMLWrapperCollapse handles any XML wrapper collapse pattern generically +func (expander autoExpander) buildGenericXMLWrapperCollapse(ctx context.Context, sourcePath path.Path, valFrom reflect.Value, targetPath path.Path, toFieldVal reflect.Value, typeFrom, targetStructType reflect.Type, isPointer bool, processedFields map[string]bool) diag.Diagnostics { + var diags diag.Diagnostics + + // Create the target struct + targetStruct := reflect.New(targetStructType) + targetStructVal := targetStruct.Elem() + + tflog.SubsystemTrace(ctx, subsystemName, "Building generic XML wrapper collapse struct", map[string]any{ + "target_type": targetStructType.String(), + }) + + // Track if we should create the struct (will be set to true if shouldCreateZeroValue) + var forceCreateStruct bool + + // Check for null handling - if all source collection fields are null and target is pointer, set to nil + if isPointer { + allFieldsNull := true + hasCollectionFields := false + + for i := 0; i < typeFrom.NumField(); i++ { + sourceField := typeFrom.Field(i) + sourceFieldVal := valFrom.FieldByName(sourceField.Name) + + if sourceValue, ok := sourceFieldVal.Interface().(attr.Value); ok { + switch sourceValue.(type) { + case basetypes.SetValuable, basetypes.ListValuable: + hasCollectionFields = true + if !sourceValue.IsNull() { + allFieldsNull = false + break + } + } + } + } + + if hasCollectionFields && allFieldsNull { + // Check if any collection field has omitempty=false (should create zero-value) + shouldCreateZeroValue := false + for i := 0; i < typeFrom.NumField(); i++ { + sourceField := typeFrom.Field(i) + sourceFieldVal := valFrom.FieldByName(sourceField.Name) + if sourceValue, ok := sourceFieldVal.Interface().(attr.Value); ok { + switch sourceValue.(type) { + case basetypes.SetValuable, basetypes.ListValuable: + _, sourceFieldOpts := autoflexTags(sourceField) + if !sourceFieldOpts.OmitEmpty() { + shouldCreateZeroValue = true + break + } + } + } + } + + if !shouldCreateZeroValue { + // All collection fields are null and have omitempty - set to nil + toFieldVal.SetZero() + + // Mark all collection fields as processed + for i := 0; i < typeFrom.NumField(); i++ { + sourceField := typeFrom.Field(i) + sourceFieldVal := valFrom.FieldByName(sourceField.Name) + if sourceValue, ok := sourceFieldVal.Interface().(attr.Value); ok { + switch sourceValue.(type) { + case basetypes.SetValuable, basetypes.ListValuable: + processedFields[sourceField.Name] = true + } + } + } + + tflog.SubsystemTrace(ctx, subsystemName, "All source collection fields null with omitempty - setting target to nil") + return diags + } + tflog.SubsystemTrace(ctx, subsystemName, "All source collection fields null but no omitempty - will create zero-value struct") + // Force creation of zero-value struct even if no source fields are found + forceCreateStruct = true + } + } + + // First, identify which source field should populate Items/Quantity + // This is typically the field that matches the target field name or the "main" collection + var mainSourceFieldName string + + // Try to find a source field that matches the target field name + targetFieldName := targetPath.String() + if lastDot := strings.LastIndex(targetFieldName, "."); lastDot >= 0 { + targetFieldName = targetFieldName[lastDot+1:] + } + + if _, found := typeFrom.FieldByName(targetFieldName); found { + mainSourceFieldName = targetFieldName + } + // Remove the fallback logic that incorrectly picks any collection field + // This was causing the wrong field data to be used for XML wrappers + + tflog.SubsystemTrace(ctx, subsystemName, "Identified main source field", map[string]any{ + "main_source_field": mainSourceFieldName, }) + // Process Items and Quantity from the main source field + hasMainSourceField := false + wrapperFieldName := getXMLWrapperSliceFieldName(targetStructType) + + if mainSourceFieldName != "" { + sourceFieldVal := valFrom.FieldByName(mainSourceFieldName) + + // Check if source is a NestedObjectCollectionValue (Rule 2 pattern) + if sourceValue, ok := sourceFieldVal.Interface().(attr.Value); ok { + if !sourceValue.IsNull() && !sourceValue.IsUnknown() { + if nestedObjCollection, ok := sourceValue.(fwtypes.NestedObjectCollectionValue); ok { + // This is Rule 2: single nested object with items + additional fields + // Delegate to nestedObjectCollectionToXMLWrapper which handles Rule 2 + tflog.SubsystemTrace(ctx, subsystemName, "Detected Rule 2 pattern - delegating to nestedObjectCollectionToXMLWrapper") + diags.Append(expander.nestedObjectCollectionToXMLWrapper(ctx, sourcePath.AtName(mainSourceFieldName), nestedObjCollection, targetPath, targetStructVal, wrapperFieldName)...) + if diags.HasError() { + return diags + } + processedFields[mainSourceFieldName] = true + + // Set the populated struct to the target field + if isPointer { + toFieldVal.Set(targetStruct) + } else { + toFieldVal.Set(targetStructVal) + } + return diags + } + } else { + // Rule 2 field is null - check omitempty + sourceFieldType, _ := typeFrom.FieldByName(mainSourceFieldName) + _, sourceFieldOpts := autoflexTags(sourceFieldType) + if !sourceFieldOpts.OmitEmpty() { + // Create zero-value struct for round-trip consistency + tflog.SubsystemDebug(ctx, subsystemName, "Rule 2 field is null but no omitempty - creating zero-value struct", map[string]any{ + "source_field": mainSourceFieldName, + }) + // Set empty Items and Quantity + itemsField := targetStructVal.FieldByName(wrapperFieldName) + quantityField := targetStructVal.FieldByName(xmlWrapperFieldQuantity) + if itemsField.IsValid() && itemsField.Kind() == reflect.Slice { + itemsField.Set(reflect.MakeSlice(itemsField.Type(), 0, 0)) + } + if quantityField.IsValid() && quantityField.Kind() == reflect.Ptr && quantityField.Type().Elem().Kind() == reflect.Int32 { + zero := int32(0) + quantityField.Set(reflect.ValueOf(&zero)) + } + // Set zero values for other fields (e.g., Enabled) + for i := 0; i < targetStructVal.NumField(); i++ { + field := targetStructVal.Field(i) + fieldType := targetStructType.Field(i) + fieldName := fieldType.Name + // Skip Items and Quantity (already handled) + if fieldName == wrapperFieldName || fieldName == xmlWrapperFieldQuantity { + continue + } + // Set zero value for pointer fields + if field.Kind() == reflect.Ptr && field.CanSet() && field.IsNil() { + switch fieldType.Type.Elem().Kind() { + case reflect.Bool: + falseVal := false + field.Set(reflect.ValueOf(&falseVal)) + case reflect.Int32: + zeroVal := int32(0) + field.Set(reflect.ValueOf(&zeroVal)) + } + } + } + processedFields[mainSourceFieldName] = true + + // Set the struct to the target field + if isPointer { + toFieldVal.Set(targetStruct) + } else { + toFieldVal.Set(targetStructVal) + } + return diags + } + } + } + + // Get the wrapper fields in the target + itemsField := targetStructVal.FieldByName(wrapperFieldName) + quantityField := targetStructVal.FieldByName(xmlWrapperFieldQuantity) + + if itemsField.IsValid() && quantityField.IsValid() { + // Check if the source field is actually usable (not null/unknown) + if sourceValue, ok := sourceFieldVal.Interface().(attr.Value); ok { + if !sourceValue.IsNull() && !sourceValue.IsUnknown() { + // Convert the collection to wrapper slice and Quantity + diags.Append(expander.convertCollectionToItemsQuantity(ctx, sourceFieldVal, targetPath.AtName(wrapperFieldName), itemsField, quantityField)...) + if diags.HasError() { + return diags + } + hasMainSourceField = true + } else { + // Check if source field has omitempty tag + sourceFieldType, _ := typeFrom.FieldByName(mainSourceFieldName) + _, sourceFieldOpts := autoflexTags(sourceFieldType) + if sourceFieldOpts.OmitEmpty() { + tflog.SubsystemDebug(ctx, subsystemName, "Main source field is null and has omitempty - skipping XML wrapper creation", map[string]any{ + "source_field": mainSourceFieldName, + }) + } else { + // Create zero-value struct for round-trip consistency + tflog.SubsystemDebug(ctx, subsystemName, "Main source field is null but no omitempty - creating zero-value XML wrapper", map[string]any{ + "source_field": mainSourceFieldName, + }) + // Set Items to empty slice and Quantity to 0 + if itemsField.Kind() == reflect.Slice { + itemsField.Set(reflect.MakeSlice(itemsField.Type(), 0, 0)) + } + if quantityField.Kind() == reflect.Ptr && quantityField.Type().Elem().Kind() == reflect.Int32 { + zero := int32(0) + quantityField.Set(reflect.ValueOf(&zero)) + } + hasMainSourceField = true + } + } + } + } + + // Mark main source field as processed + processedFields[mainSourceFieldName] = true + } + + // Track if we found any fields to populate + hasAnySourceFields := hasMainSourceField + + // Now process each remaining field in the target struct + for i := 0; i < targetStructType.NumField(); i++ { + targetField := targetStructType.Field(i) + targetFieldName := targetField.Name + targetFieldVal := targetStructVal.Field(i) + + // Skip wrapper field and Quantity as they were handled above + if targetFieldName == wrapperFieldName || targetFieldName == xmlWrapperFieldQuantity { + continue + } + + if !targetFieldVal.CanSet() { + continue + } + + tflog.SubsystemTrace(ctx, subsystemName, "Processing additional target field", map[string]any{ + "target_field": targetFieldName, + "target_type": targetField.Type.String(), + }) + + // Look for a source field with the same name + if _, found := typeFrom.FieldByName(targetFieldName); found { + sourceFieldVal := valFrom.FieldByName(targetFieldName) + + tflog.SubsystemTrace(ctx, subsystemName, "Found matching source field", map[string]any{ + "source_field": targetFieldName, + "target_field": targetFieldName, + }) + + // Check if we need special XML wrapper conversion + if expander.shouldConvertToXMLWrapper(sourceFieldVal, targetFieldVal) { + // Convert collection to XML wrapper structure + diags.Append(expander.convertToXMLWrapper(ctx, sourceFieldVal, targetPath.AtName(targetFieldName), targetFieldVal)...) + if diags.HasError() { + return diags + } + } else { + // Regular field conversion + opts := fieldOpts{} + diags.Append(expander.convert(ctx, sourcePath.AtName(targetFieldName), sourceFieldVal, targetPath.AtName(targetFieldName), targetFieldVal, opts)...) + if diags.HasError() { + return diags + } + } + + // Mark source field as processed and track that we found fields + processedFields[targetFieldName] = true + hasAnySourceFields = true + } else { + tflog.SubsystemDebug(ctx, subsystemName, "No source field found for target field", map[string]any{ + "target_field": targetFieldName, + }) + } + } + + // Only set the constructed struct if we found source fields to populate it OR if we're forcing creation + if hasAnySourceFields || forceCreateStruct { + // Set the constructed struct into the target field + if isPointer { + toFieldVal.Set(targetStruct) + } else { + toFieldVal.Set(targetStruct.Elem()) + } + + tflog.SubsystemTrace(ctx, subsystemName, "Successfully built generic XML wrapper collapse struct") + } else { + tflog.SubsystemDebug(ctx, subsystemName, "No source fields found for XML wrapper collapse target - leaving as nil/zero value") + // Leave the field as nil/zero value (don't set anything) + } + return diags } diff --git a/internal/framework/flex/autoflex_flatten.go b/internal/framework/flex/autoflex_flatten.go index d272c1bf1496..110672a00d7d 100644 --- a/internal/framework/flex/autoflex_flatten.go +++ b/internal/framework/flex/autoflex_flatten.go @@ -53,7 +53,8 @@ func Flatten(ctx context.Context, apiObject, tfObject any, optFns ...AutoFlexOpt } type autoFlattener struct { - Options AutoFlexOptions + Options AutoFlexOptions + fieldCache map[reflect.Type]map[string]reflect.StructField } // newAutoFlattener initializes an auto-flattener with defaults that can be overridden @@ -68,7 +69,8 @@ func newAutoFlattener(optFns []AutoFlexOptionsFunc) *autoFlattener { } return &autoFlattener{ - Options: o, + Options: o, + fieldCache: make(map[reflect.Type]map[string]reflect.StructField), } } @@ -76,6 +78,37 @@ func (flattener autoFlattener) getOptions() AutoFlexOptions { return flattener.Options } +// getCachedField returns a cached field lookup or performs and caches the lookup +func (flattener *autoFlattener) getCachedField(t reflect.Type, fieldName string) (reflect.StructField, bool) { + if flattener.fieldCache == nil { + flattener.fieldCache = make(map[reflect.Type]map[string]reflect.StructField) + } + + if typeCache, exists := flattener.fieldCache[t]; exists { + if field, found := typeCache[fieldName]; found { + return field, true + } + } else { + flattener.fieldCache[t] = make(map[string]reflect.StructField) + } + + // Perform lookup and cache result + if field, found := t.FieldByName(fieldName); found { + flattener.fieldCache[t][fieldName] = field + return field, true + } + + return reflect.StructField{}, false +} + +// getCachedFieldValue returns a cached field value lookup +func (flattener *autoFlattener) getCachedFieldValue(v reflect.Value, fieldName string) reflect.Value { + if field, found := flattener.getCachedField(v.Type(), fieldName); found { + return v.FieldByIndex(field.Index) + } + return reflect.Value{} +} + // autoFlattenConvert converts `from` to `to` using the specified auto-flexer. func autoFlattenConvert(ctx context.Context, from, to any, flexer autoFlexer) diag.Diagnostics { var diags diag.Diagnostics @@ -835,6 +868,49 @@ func (flattener autoFlattener) slice(ctx context.Context, sourcePath path.Path, diags.Append(flattener.sliceOfStructToNestedObjectCollection(ctx, sourcePath, vFrom, targetPath, tTo, vTo, fieldOpts)...) return diags } + + default: + // Check for custom types with string underlying type (e.g., enums) + // For type declarations like "type MyEnum string", Kind() will not be reflect.String, + // but we can convert values to string using String() method + if tSliceElem.Kind() != reflect.String { + // Try to see if this is a string-based custom type by checking if we can call String() on it + sampleVal := reflect.New(tSliceElem).Elem() + if sampleVal.CanInterface() { + // Check if it has an underlying string type + if tSliceElem.ConvertibleTo(reflect.TypeOf("")) { + var elementType attr.Type = types.StringType + attrValueFromReflectValue := newStringValueFromReflectValue + if tTo, ok := tTo.(attr.TypeWithElementType); ok { + if tElem, ok := tTo.ElementType().(basetypes.StringTypable); ok { + // + // []stringy -> types.List/Set(OfStringEnum). + // + elementType = tElem + attrValueFromReflectValue = func(val reflect.Value) (attr.Value, diag.Diagnostics) { + return tElem.ValueFromString(ctx, types.StringValue(val.String())) + } + } + } + + switch tTo := tTo.(type) { + case basetypes.ListTypable: + // + // []custom_string_type -> types.List(OfString). + // + diags.Append(flattener.sliceOfPrimtiveToList(ctx, vFrom, tTo, vTo, elementType, attrValueFromReflectValue, fieldOpts)...) + return diags + + case basetypes.SetTypable: + // + // []custom_string_type -> types.Set(OfString). + // + diags.Append(flattener.sliceOfPrimitiveToSet(ctx, vFrom, tTo, vTo, elementType, attrValueFromReflectValue, fieldOpts)...) + return diags + } + } + } + } } tflog.SubsystemError(ctx, subsystemName, "AutoFlex Flatten; incompatible types", map[string]any{ @@ -1191,6 +1267,73 @@ func (flattener autoFlattener) structToNestedObject(ctx context.Context, sourceP } } + // Check if source is a Rule 2 XML wrapper struct with all zero values and omitempty + if isXMLWrapperStruct(vFrom.Type()) && fieldOpts.omitempty { + wrapperFieldName := getXMLWrapperSliceFieldName(vFrom.Type()) + itemsField := vFrom.FieldByName(wrapperFieldName) + + if itemsField.IsValid() { + itemsEmpty := itemsField.IsNil() || (itemsField.Kind() == reflect.Slice && itemsField.Len() == 0) + + if itemsEmpty { + allOtherFieldsZero := true + for i := 0; i < vFrom.NumField(); i++ { + sourceField := vFrom.Field(i) + fieldName := vFrom.Type().Field(i).Name + + if fieldName == wrapperFieldName || fieldName == xmlWrapperFieldQuantity { + continue + } + + isFieldZero := sourceField.Kind() == reflect.Pointer && sourceField.IsNil() || + sourceField.Kind() == reflect.Pointer && sourceField.Elem().IsZero() || + sourceField.Kind() != reflect.Pointer && sourceField.IsZero() + + if !isFieldZero { + allOtherFieldsZero = false + break + } + } + + if allOtherFieldsZero { + tflog.SubsystemTrace(ctx, subsystemName, "Nested XML wrapper struct has all zero values with omitempty, returning null") + val, d := tTo.NullValue(ctx) + diags.Append(d...) + if !diags.HasError() { + vTo.Set(reflect.ValueOf(val)) + } + return diags + } + } + } + } + + // Check if source is a regular struct with all zero values and omitempty + if !isXMLWrapperStruct(vFrom.Type()) && fieldOpts.omitempty { + allFieldsZero := true + for i := 0; i < vFrom.NumField(); i++ { + sourceField := vFrom.Field(i) + isFieldZero := sourceField.Kind() == reflect.Pointer && sourceField.IsNil() || + sourceField.Kind() == reflect.Pointer && sourceField.Elem().IsZero() || + sourceField.Kind() != reflect.Pointer && sourceField.IsZero() + + if !isFieldZero { + allFieldsZero = false + break + } + } + + if allFieldsZero { + tflog.SubsystemTrace(ctx, subsystemName, "Nested struct has all zero values with omitempty, returning null") + val, d := tTo.NullValue(ctx) + diags.Append(d...) + if !diags.HasError() { + vTo.Set(reflect.ValueOf(val)) + } + return diags + } + } + // Create a new target structure and walk its fields. to, d := tTo.NewObjectPtr(ctx) diags.Append(d...) @@ -1301,6 +1444,23 @@ func (flattener autoFlattener) sliceOfPrimtiveToList(ctx context.Context, vFrom } } else { if vFrom.IsNil() { + if fieldOpts.legacy { + tflog.SubsystemTrace(ctx, subsystemName, "Flattening with ListValue (empty for nil in legacy mode)") + list, d := types.ListValue(elementType, []attr.Value{}) + diags.Append(d...) + if diags.HasError() { + return diags + } + to, d := tTo.ValueFromList(ctx, list) + diags.Append(d...) + if diags.HasError() { + return diags + } + + vTo.Set(reflect.ValueOf(to)) + return diags + } + tflog.SubsystemTrace(ctx, subsystemName, "Flattening with ListNull") to, d := tTo.ValueFromList(ctx, types.ListNull(elementType)) diags.Append(d...) @@ -1366,6 +1526,38 @@ func (flattener autoFlattener) sliceOfPrimitiveToSet(ctx context.Context, vFrom } } else { if vFrom.IsNil() { + // If omitempty is set, return null + if fieldOpts.omitempty { + tflog.SubsystemTrace(ctx, subsystemName, "Flattening with SetNull (omitempty)") + to, d := tTo.ValueFromSet(ctx, types.SetNull(elementType)) + diags.Append(d...) + if diags.HasError() { + return diags + } + + vTo.Set(reflect.ValueOf(to)) + return diags + } + + // If legacy mode, return empty set + if fieldOpts.legacy { + tflog.SubsystemTrace(ctx, subsystemName, "Flattening with SetValue (empty for nil in legacy mode)") + set, d := types.SetValue(elementType, []attr.Value{}) + diags.Append(d...) + if diags.HasError() { + return diags + } + to, d := tTo.ValueFromSet(ctx, set) + diags.Append(d...) + if diags.HasError() { + return diags + } + + vTo.Set(reflect.ValueOf(to)) + return diags + } + + // Default: return null set tflog.SubsystemTrace(ctx, subsystemName, "Flattening with SetNull") to, d := tTo.ValueFromSet(ctx, types.SetNull(elementType)) diags.Append(d...) @@ -1378,6 +1570,19 @@ func (flattener autoFlattener) sliceOfPrimitiveToSet(ctx context.Context, vFrom } } + // Check if slice is empty and omitempty is set - return null instead of empty set + if vFrom.Len() == 0 && fieldOpts.omitempty { + tflog.SubsystemTrace(ctx, subsystemName, "Flattening with SetValue (null for empty slice with omitempty)") + to, d := tTo.ValueFromSet(ctx, types.SetNull(elementType)) + diags.Append(d...) + if diags.HasError() { + return diags + } + + vTo.Set(reflect.ValueOf(to)) + return diags + } + tflog.SubsystemTrace(ctx, subsystemName, "Flattening with SetValue", map[string]any{ logAttrKeySourceSize: vFrom.Len(), }) @@ -1431,8 +1636,15 @@ func (flattener autoFlattener) sliceOfStructToNestedObjectCollection(ctx context } } else { if vFrom.IsNil() { - tflog.SubsystemTrace(ctx, subsystemName, "Flattening with NullValue") - val, d := tTo.NullValue(ctx) + tflog.SubsystemTrace(ctx, subsystemName, "Flattening with EmptyValue (for nested object collection)") + // For nested object collections, create empty instead of null to match Terraform's planned values + to, d := tTo.NewObjectSlice(ctx, 0, 0) + diags.Append(d...) + if diags.HasError() { + return diags + } + + val, d := tTo.ValueFromObjectSlice(ctx, to) diags.Append(d...) if diags.HasError() { return diags @@ -1448,8 +1660,35 @@ func (flattener autoFlattener) sliceOfStructToNestedObjectCollection(ctx context tflog.SubsystemTrace(ctx, subsystemName, "Flattening nested object collection", map[string]any{ logAttrKeySourceSize: n, + "source_type": vFrom.Type().String(), + "target_type": tTo.String(), }) + // DEBUG: Log the source struct fields for the first element + if n > 0 { + firstElem := vFrom.Index(0) + tflog.SubsystemDebug(ctx, subsystemName, "DEBUG: First element of nested object collection", map[string]any{ + "element_type": firstElem.Type().String(), + }) + + // Log exported fields only + if firstElem.Kind() == reflect.Struct { + for i := 0; i < firstElem.NumField(); i++ { + field := firstElem.Type().Field(i) + if !field.IsExported() { + continue + } + fieldVal := firstElem.Field(i) + tflog.SubsystemDebug(ctx, subsystemName, "DEBUG: Struct field", map[string]any{ + "field_name": field.Name, + "field_type": fieldVal.Type().String(), + "is_nil": fieldVal.Kind() == reflect.Pointer && fieldVal.IsNil(), + "is_zero": fieldVal.IsZero(), + }) + } + } + } + to, d := tTo.NewObjectSlice(ctx, n, n) diags.Append(d...) if diags.HasError() { @@ -1488,27 +1727,77 @@ func (flattener autoFlattener) sliceOfStructToNestedObjectCollection(ctx context } // xmlWrapperFlatten handles flattening from AWS XML wrapper structs to TF collection types -// that follow the pattern: {Items: []T, Quantity: *int32} -> []T -func (flattener autoFlattener) xmlWrapperFlatten(ctx context.Context, vFrom reflect.Value, tTo attr.Type, vTo reflect.Value, wrapperField string) diag.Diagnostics { - var diags diag.Diagnostics - +// +// XML Wrapper Compatibility Rules: +// Rule 1: Items/Quantity only - Direct collection mapping +// +// AWS: {Items: []T, Quantity: *int32} +// TF: Repeatable singular blocks (e.g., lambda_function_association { ... }) +// +// Rule 2: Items/Quantity + additional fields - Single plural block +// +// AWS: {Items: []T, Quantity: *int32, Enabled: *bool, ...} +// TF: Single plural block (e.g., trusted_signers { items = [...], enabled = true }) +// +// Supports both Rule 1 (Items/Quantity only) and Rule 2 (Items/Quantity + additional fields) +func (flattener *autoFlattener) xmlWrapperFlatten(ctx context.Context, vFrom reflect.Value, tTo attr.Type, vTo reflect.Value, opts tagOptions) diag.Diagnostics { + wrapperField := opts.XMLWrapperField() tflog.SubsystemTrace(ctx, subsystemName, "Starting XML wrapper flatten", map[string]any{ "source_type": vFrom.Type().String(), "target_type": tTo.String(), "wrapper_field": wrapperField, }) - // Verify source is a valid XML wrapper struct - if !isXMLWrapperStruct(vFrom.Type()) { - tflog.SubsystemError(ctx, subsystemName, "Source is not a valid XML wrapper struct", map[string]any{ - "source_type": vFrom.Type().String(), + // Check if target is a NestedObjectCollection (Rule 2 pattern) + if nestedObjType, ok := tTo.(fwtypes.NestedObjectCollectionType); ok { + tflog.SubsystemTrace(ctx, subsystemName, "Target is NestedObjectCollectionType - checking for Rule 2", map[string]any{ + "target_type": tTo.String(), }) - diags.Append(DiagFlatteningIncompatibleTypes(vFrom.Type(), reflect.TypeOf(vTo.Interface()))) - return diags + + // Rule 2 detection: check if source AWS struct has more than 2 fields + // (Items, Quantity, plus additional fields like Enabled) + sourceStructType := vFrom.Type() + if sourceStructType.Kind() == reflect.Ptr { + sourceStructType = sourceStructType.Elem() + } + + isRule2 := false + if sourceStructType.Kind() == reflect.Struct { + // Count fields, excluding noSmithyDocumentSerde + fieldCount := 0 + for i := 0; i < sourceStructType.NumField(); i++ { + fieldName := sourceStructType.Field(i).Name + if fieldName != "noSmithyDocumentSerde" { + fieldCount++ + } + } + isRule2 = fieldCount > 2 + } + + tflog.SubsystemTrace(ctx, subsystemName, "Rule 2 detection result", map[string]any{ + "is_rule2": isRule2, + "field_count": sourceStructType.NumField(), + "source_type": sourceStructType.String(), + }) + + if isRule2 { + tflog.SubsystemTrace(ctx, subsystemName, "Using Rule 2 flatten - calling xmlWrapperFlattenRule2") + return flattener.xmlWrapperFlattenRule2(ctx, vFrom, nestedObjType, vTo, opts) + } + tflog.SubsystemTrace(ctx, subsystemName, "NOT Rule 2 - continuing with Rule 1") } + // Rule 1: Flatten Items field directly to collection + return flattener.xmlWrapperFlattenRule1(ctx, vFrom, tTo, vTo, wrapperField, opts) +} + +// xmlWrapperFlattenRule1 handles Rule 1: flatten Items field directly to collection +func (flattener *autoFlattener) xmlWrapperFlattenRule1(ctx context.Context, vFrom reflect.Value, tTo attr.Type, vTo reflect.Value, wrapperField string, opts tagOptions) diag.Diagnostics { + var diags diag.Diagnostics + // Get the Items field from the source wrapper struct - itemsField := vFrom.FieldByName("Items") + wrapperFieldName := getXMLWrapperSliceFieldName(vFrom.Type()) + itemsField := vFrom.FieldByName(wrapperFieldName) if !itemsField.IsValid() { tflog.SubsystemError(ctx, subsystemName, "XML wrapper struct missing Items field") diags.Append(DiagFlatteningIncompatibleTypes(vFrom.Type(), reflect.TypeOf(vTo.Interface()))) @@ -1535,7 +1824,7 @@ func (flattener autoFlattener) xmlWrapperFlatten(ctx context.Context, vFrom refl switch tTo := tTo.(type) { case basetypes.ListTypable: // Items []T -> types.List - if itemsField.IsNil() { + if itemsField.IsNil() || (itemsField.Len() == 0 && opts.OmitEmpty()) { tflog.SubsystemTrace(ctx, subsystemName, "Flattening XML wrapper with ListNull") to, d := tTo.ValueFromList(ctx, types.ListNull(elementType)) diags.Append(d...) @@ -1548,15 +1837,26 @@ func (flattener autoFlattener) xmlWrapperFlatten(ctx context.Context, vFrom refl // Convert items slice to list elements itemsLen := itemsField.Len() - elements := make([]attr.Value, itemsLen) + + // Filter out nil pointers first and collect valid items + validItems := make([]reflect.Value, 0, itemsLen) + for i := range itemsLen { + item := itemsField.Index(i) + // Skip nil pointers + if item.Kind() == reflect.Pointer && item.IsNil() { + continue + } + validItems = append(validItems, item) + } + + elements := make([]attr.Value, len(validItems)) tflog.SubsystemTrace(ctx, subsystemName, "Converting items to list elements", map[string]any{ "items_count": itemsLen, + "valid_count": len(validItems), }) - for i := range itemsLen { - item := itemsField.Index(i) - + for i, item := range validItems { tflog.SubsystemTrace(ctx, subsystemName, "Processing item", map[string]any{ "index": i, "item_kind": item.Kind().String(), @@ -1565,15 +1865,82 @@ func (flattener autoFlattener) xmlWrapperFlatten(ctx context.Context, vFrom refl // Convert each item based on its type switch item.Kind() { - case reflect.Int32: - elements[i] = types.Int64Value(item.Int()) case reflect.String: - elements[i] = types.StringValue(item.String()) + // Try to create a value that matches the target element type + if val, d := flattener.createTargetValue(ctx, types.StringValue(item.String()), elementType); d.HasError() { + diags.Append(d...) + return diags + } else { + elements[i] = val + } + case reflect.Pointer: + // Handle pointer types like *testEnum (pointer to enum) + // (nil pointers are already filtered out) + // Dereference the pointer and get the underlying value + derefItem := item.Elem() + + // Handle the dereferenced value based on its type + switch derefItem.Kind() { + case reflect.String: + // Handle *string or *testEnum (where testEnum is a string type) + stringVal := derefItem.String() + if val, d := flattener.createTargetValue(ctx, types.StringValue(stringVal), elementType); d.HasError() { + diags.Append(d...) + return diags + } else { + elements[i] = val + } + default: + // Check if the dereferenced type is convertible to string (like custom enums) + if derefItem.Type().ConvertibleTo(reflect.TypeOf("")) { + stringVal := derefItem.Convert(reflect.TypeOf("")).String() + if val, d := flattener.createTargetValue(ctx, types.StringValue(stringVal), elementType); d.HasError() { + diags.Append(d...) + return diags + } else { + elements[i] = val + } + } else { + diags.Append(DiagFlatteningIncompatibleTypes(derefItem.Type(), reflect.TypeOf(elementType))) + return diags + } + } + case reflect.Struct: + // Handle struct types - convert to ObjectValue + if objType, ok := elementType.(fwtypes.NestedObjectType); ok { + objPtr, d := objType.NewObjectPtr(ctx) + diags.Append(d...) + if diags.HasError() { + return diags + } + diags.Append(autoFlattenConvert(ctx, item.Interface(), objPtr, flattener)...) + if diags.HasError() { + return diags + } + objVal, d := objType.ValueFromObjectPtr(ctx, objPtr) + diags.Append(d...) + if diags.HasError() { + return diags + } + elements[i] = objVal + } else { + diags.Append(DiagFlatteningIncompatibleTypes(item.Type(), reflect.TypeOf(elementType))) + return diags + } default: - // For complex types, handle struct conversion if needed - if item.Kind() == reflect.Struct { - // This would need to be handled by a nested object conversion - // For now, we'll return an error for unsupported types + // Check for custom string types (like enums) + if item.Type().ConvertibleTo(reflect.TypeOf("")) { + // Convert custom string type (like testEnum/Method) to string + stringVal := item.Convert(reflect.TypeOf("")).String() + + // Try to create a value that matches the target element type + if val, d := flattener.createTargetValue(ctx, types.StringValue(stringVal), elementType); d.HasError() { + diags.Append(d...) + return diags + } else { + elements[i] = val + } + } else { diags.Append(DiagFlatteningIncompatibleTypes(item.Type(), reflect.TypeOf(elementType))) return diags } @@ -1622,15 +1989,26 @@ func (flattener autoFlattener) xmlWrapperFlatten(ctx context.Context, vFrom refl // Convert items slice to set elements itemsLen := itemsField.Len() - elements := make([]attr.Value, itemsLen) + + // Filter out nil pointers first and collect valid items + validItems := make([]reflect.Value, 0, itemsLen) + for i := range itemsLen { + item := itemsField.Index(i) + // Skip nil pointers + if item.Kind() == reflect.Pointer && item.IsNil() { + continue + } + validItems = append(validItems, item) + } + + elements := make([]attr.Value, len(validItems)) tflog.SubsystemTrace(ctx, subsystemName, "Converting items to set elements", map[string]any{ "items_count": itemsLen, + "valid_count": len(validItems), }) - for i := range itemsLen { - item := itemsField.Index(i) - + for i, item := range validItems { tflog.SubsystemTrace(ctx, subsystemName, "Processing item", map[string]any{ "index": i, "item_kind": item.Kind().String(), @@ -1639,10 +2017,46 @@ func (flattener autoFlattener) xmlWrapperFlatten(ctx context.Context, vFrom refl // Convert each item based on its type switch item.Kind() { - case reflect.Int32: - elements[i] = types.Int64Value(item.Int()) case reflect.String: - elements[i] = types.StringValue(item.String()) + // Try to create a value that matches the target element type + if val, d := flattener.createTargetValue(ctx, types.StringValue(item.String()), elementType); d.HasError() { + diags.Append(d...) + return diags + } else { + elements[i] = val + } + case reflect.Pointer: + // Handle pointer types like *testEnum (pointer to enum) + // (nil pointers are already filtered out) + // Dereference the pointer and get the underlying value + derefItem := item.Elem() + + // Handle the dereferenced value based on its type + switch derefItem.Kind() { + case reflect.String: + // Handle *string or *testEnum (where testEnum is a string type) + stringVal := derefItem.String() + if val, d := flattener.createTargetValue(ctx, types.StringValue(stringVal), elementType); d.HasError() { + diags.Append(d...) + return diags + } else { + elements[i] = val + } + default: + // Check if the dereferenced type is convertible to string (like custom enums) + if derefItem.Type().ConvertibleTo(reflect.TypeOf("")) { + stringVal := derefItem.Convert(reflect.TypeOf("")).String() + if val, d := flattener.createTargetValue(ctx, types.StringValue(stringVal), elementType); d.HasError() { + diags.Append(d...) + return diags + } else { + elements[i] = val + } + } else { + diags.Append(DiagFlatteningIncompatibleTypes(derefItem.Type(), reflect.TypeOf(elementType))) + return diags + } + } case reflect.Struct: // Handle complex struct types by converting to the target element type if elemTyper, ok := tTo.(attr.TypeWithElementType); ok && elemTyper.ElementType() != nil { @@ -1669,9 +2083,23 @@ func (flattener autoFlattener) xmlWrapperFlatten(ctx context.Context, vFrom refl return diags } default: - // For other complex types, handle conversion if needed - diags.Append(DiagFlatteningIncompatibleTypes(item.Type(), reflect.TypeOf(elementType))) - return diags + // Check for custom string types (like enums) + if item.Type().ConvertibleTo(reflect.TypeOf("")) { + // Convert custom string type (like testEnum/Method) to string + stringVal := item.Convert(reflect.TypeOf("")).String() + + // Try to create a value that matches the target element type + if val, d := flattener.createTargetValue(ctx, types.StringValue(stringVal), elementType); d.HasError() { + diags.Append(d...) + return diags + } else { + elements[i] = val + } + } else { + // For other complex types, handle conversion if needed + diags.Append(DiagFlatteningIncompatibleTypes(item.Type(), reflect.TypeOf(elementType))) + return diags + } } } @@ -1704,69 +2132,237 @@ func (flattener autoFlattener) xmlWrapperFlatten(ctx context.Context, vFrom refl } // flattenStruct traverses struct `from`, calling `flexer` for each exported field. -func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPath path.Path, to any, flexer autoFlexer) diag.Diagnostics { +// handleXMLWrapperRule1 handles Rule 1: flatten entire source struct to target collection field with xmlwrapper tag +func handleXMLWrapperRule1(ctx context.Context, valFrom, valTo reflect.Value, typeFrom, typeTo reflect.Type, flexer autoFlexer) (bool, diag.Diagnostics) { var diags diag.Diagnostics - ctx = tflog.SubsystemSetField(ctx, subsystemName, logAttrKeySourcePath, sourcePath.String()) - ctx = tflog.SubsystemSetField(ctx, subsystemName, logAttrKeyTargetPath, targetPath.String()) - - ctx, valFrom, valTo, d := autoFlexValues(ctx, from, to) - diags.Append(d...) - if diags.HasError() { - return diags + // Skip if target is a struct - that's Rule 2 where we map fields individually + if typeTo.Kind() == reflect.Struct { + return false, diags } - if toFlattener, ok := to.(Flattener); ok { - tflog.SubsystemInfo(ctx, subsystemName, "Target implements flex.Flattener") - diags.Append(flattenFlattener(ctx, valFrom, toFlattener)...) - return diags + for toField := range tfreflect.ExportedStructFields(typeTo) { + toFieldName := toField.Name + _, toOpts := autoflexTags(toField) + if wrapperField := toOpts.XMLWrapperField(); wrapperField != "" { + toFieldVal := valTo.FieldByIndex(toField.Index) + if !toFieldVal.CanSet() { + continue + } + + tflog.SubsystemTrace(ctx, subsystemName, "Converting entire XML wrapper struct to collection field (Rule 1)", map[string]any{ + logAttrKeySourceType: typeFrom.String(), + logAttrKeyTargetFieldname: toFieldName, + "wrapper_field": wrapperField, + }) + + attrVal, ok := toFieldVal.Interface().(attr.Value) + if !ok { + tflog.SubsystemError(ctx, subsystemName, "Target field does not implement attr.Value") + diags.Append(diagFlatteningTargetDoesNotImplementAttrValue(reflect.TypeOf(toFieldVal.Interface()))) + return true, diags + } + + if f, ok := flexer.(*autoFlattener); ok { + diags.Append(f.xmlWrapperFlatten(ctx, valFrom, attrVal.Type(ctx), toFieldVal, toOpts)...) + } else { + diags.Append(DiagFlatteningIncompatibleTypes(valFrom.Type(), reflect.TypeOf(toFieldVal.Interface()))) + } + return true, diags + } } + return false, diags +} + +// handleXMLWrapperToNestedObject handles flattening XML wrapper structs to NestedObjectCollectionType (Rule 2) +func handleXMLWrapperToNestedObject(ctx context.Context, valFrom, valTo reflect.Value, to any, flexer autoFlexer) (bool, diag.Diagnostics) { + var diags diag.Diagnostics typeFrom := valFrom.Type() typeTo := valTo.Type() - // Special handling: Check if the entire source struct is an XML wrapper - // and should be flattened to a target field with wrapper tag - if isXMLWrapperStruct(typeFrom) { - for toField := range tfreflect.ExportedStructFields(typeTo) { - toFieldName := toField.Name - _, toOpts := autoflexTags(toField) - if wrapperField := toOpts.XMLWrapperField(); wrapperField != "" { - toFieldVal := valTo.FieldByIndex(toField.Index) - if !toFieldVal.CanSet() { - continue - } + tflog.SubsystemTrace(ctx, subsystemName, "Source is XML wrapper struct", map[string]any{ + logAttrKeySourceType: typeFrom.String(), + logAttrKeyTargetType: typeTo.String(), + }) - tflog.SubsystemTrace(ctx, subsystemName, "Converting entire XML wrapper struct to collection field", map[string]any{ - logAttrKeySourceType: typeFrom.String(), - logAttrKeyTargetFieldname: toFieldName, - "wrapper_field": wrapperField, - }) + attrVal, ok := to.(attr.Value) + if !ok { + return false, diags + } - valTo, ok := toFieldVal.Interface().(attr.Value) - if !ok { - tflog.SubsystemError(ctx, subsystemName, "Target field does not implement attr.Value") - diags.Append(diagFlatteningTargetDoesNotImplementAttrValue(reflect.TypeOf(toFieldVal.Interface()))) - return diags - } + tflog.SubsystemTrace(ctx, subsystemName, "Target implements attr.Value", map[string]any{ + "target_type": attrVal.Type(ctx).String(), + }) - if f, ok := flexer.(*autoFlattener); ok { - diags.Append(f.xmlWrapperFlatten(ctx, valFrom, valTo.Type(ctx), toFieldVal, wrapperField)...) - } else { - diags.Append(DiagFlatteningIncompatibleTypes(valFrom.Type(), reflect.TypeOf(toFieldVal.Interface()))) + nestedObjType, ok := attrVal.Type(ctx).(fwtypes.NestedObjectCollectionType) + if !ok { + // Try other collection types if we have autoFlattener + if f, ok := flexer.(*autoFlattener); ok { + switch attrVal.Type(ctx).(type) { + case basetypes.SetTypable, basetypes.ListTypable: + tflog.SubsystemTrace(ctx, subsystemName, "Flattening XML wrapper directly to collection", map[string]any{ + logAttrKeySourceType: typeFrom.String(), + logAttrKeyTargetType: typeTo.String(), + }) + _ = getXMLWrapperSliceFieldName(typeFrom) // wrapperFieldName unused + // Use empty tagOptions since we don't have field context here + diags.Append(f.xmlWrapperFlatten(ctx, valFrom, attrVal.Type(ctx), valTo, tagOptions(""))...) + return true, diags + } + } + return false, diags + } + + tflog.SubsystemTrace(ctx, subsystemName, "Target is NestedObjectCollectionType", map[string]any{ + "target_type": nestedObjType.String(), + }) + + // Check if wrapper Items field is empty/nil + wrapperFieldName := getXMLWrapperSliceFieldName(valFrom.Type()) + itemsField := valFrom.FieldByName(wrapperFieldName) + + if itemsField.IsValid() { + itemsEmpty := itemsField.IsNil() || (itemsField.Kind() == reflect.Slice && itemsField.Len() == 0) + + if itemsEmpty { + // Check if all other fields (except Quantity) are at zero values + allOtherFieldsZero := true + for i := 0; i < valFrom.NumField(); i++ { + sourceField := valFrom.Field(i) + fieldName := valFrom.Type().Field(i).Name + + if fieldName == wrapperFieldName || fieldName == xmlWrapperFieldQuantity { + continue } - if diags.HasError() { - return diags + + isFieldZero := false + if sourceField.Kind() == reflect.Pointer { + if sourceField.IsNil() { + isFieldZero = true + } else if sourceField.Elem().IsZero() { + isFieldZero = true + } + } else { + isFieldZero = sourceField.IsZero() } - // Successfully handled as XML wrapper, don't process individual fields - return diags + + if !isFieldZero { + allOtherFieldsZero = false + break + } + } + + if allOtherFieldsZero { + tflog.SubsystemTrace(ctx, subsystemName, "XML wrapper Items is empty and all other fields are zero, returning null for Rule 2") + nullVal, d := nestedObjType.NullValue(ctx) + diags.Append(d...) + if !diags.HasError() { + valTo.Set(reflect.ValueOf(nullVal)) + } + return true, diags } } } + // Check if target model has Items field (Rule 2) + samplePtr, d := nestedObjType.NewObjectPtr(ctx) + diags.Append(d...) + if diags.HasError() { + return true, diags + } + + sampleValue := reflect.ValueOf(samplePtr).Elem() + wrapperFieldName = getXMLWrapperSliceFieldName(sampleValue.Type()) + if !sampleValue.FieldByName(wrapperFieldName).IsValid() { + return false, diags + } + + // Rule 2: Create single-element collection with nested object containing Items + other fields + tflog.SubsystemTrace(ctx, subsystemName, "Flattening XML wrapper to NestedObjectCollection (Rule 2)", map[string]any{ + logAttrKeySourceType: typeFrom.String(), + logAttrKeyTargetType: typeTo.String(), + }) + + // Map source fields to target nested object fields + for i := 0; i < typeFrom.NumField(); i++ { + sourceField := typeFrom.Field(i) + sourceFieldName := sourceField.Name + sourceFieldVal := valFrom.Field(i) + + if sourceFieldName == xmlWrapperFieldQuantity { + continue + } + + targetField := sampleValue.FieldByName(sourceFieldName) + if targetField.IsValid() && targetField.CanAddr() { + if targetAttr, ok := targetField.Addr().Interface().(attr.Value); ok { + diags.Append(autoFlattenConvert(ctx, sourceFieldVal.Interface(), targetAttr, flexer)...) + } + } + } + + if !diags.HasError() { + ptrType := reflect.TypeOf(samplePtr) + objectSlice := reflect.MakeSlice(reflect.SliceOf(ptrType), 1, 1) + objectSlice.Index(0).Set(reflect.ValueOf(samplePtr)) + + targetValue, d := nestedObjType.ValueFromObjectSlice(ctx, objectSlice.Interface()) + diags.Append(d...) + if !diags.HasError() { + valTo.Set(reflect.ValueOf(targetValue)) + } + } + + return true, diags +} + +func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPath path.Path, to any, flexer autoFlexer) diag.Diagnostics { + var diags diag.Diagnostics + + ctx = tflog.SubsystemSetField(ctx, subsystemName, logAttrKeySourcePath, sourcePath.String()) + ctx = tflog.SubsystemSetField(ctx, subsystemName, logAttrKeyTargetPath, targetPath.String()) + + ctx, valFrom, valTo, d := autoFlexValues(ctx, from, to) + diags.Append(d...) + if diags.HasError() { + return diags + } + + if toFlattener, ok := to.(Flattener); ok { + tflog.SubsystemInfo(ctx, subsystemName, "Target implements flex.Flattener") + diags.Append(flattenFlattener(ctx, valFrom, toFlattener)...) + return diags + } + + typeFrom := valFrom.Type() + typeTo := valTo.Type() + + // Special handling: Check if target has xmlwrapper tag (Rule 1) + if handled, d := handleXMLWrapperRule1(ctx, valFrom, valTo, typeFrom, typeTo, flexer); handled { + diags.Append(d...) + return diags + } + + // Handle XML wrapper split patterns where complex source fields + // need to be split into multiple target collection fields + processedFields := make(map[string]bool) + diags.Append(flexer.handleXMLWrapperCollapse(ctx, sourcePath, valFrom, targetPath, valTo, typeFrom, typeTo, processedFields)...) + if diags.HasError() { + return diags + } + for fromField := range flattenSourceFields(ctx, typeFrom, flexer.getOptions()) { fromFieldName := fromField.Name + // Skip fields that were already processed by XML wrapper split + if processedFields[fromFieldName] { + tflog.SubsystemTrace(ctx, subsystemName, "Skipping field already processed by XML wrapper split", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + }) + continue + } + toField, ok := (&fuzzyFieldFinder{}).findField(ctx, fromFieldName, typeFrom, typeTo, flexer) if !ok { // Corresponding field not found in to. @@ -1809,6 +2405,8 @@ func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPa // Check if target has wrapper tag and source is an XML wrapper struct if wrapperField := toFieldOpts.XMLWrapperField(); wrapperField != "" { fromFieldVal := valFrom.FieldByIndex(fromField.Index) + + // Handle direct XML wrapper struct if isXMLWrapperStruct(fromFieldVal.Type()) { tflog.SubsystemTrace(ctx, subsystemName, "Converting XML wrapper struct to collection", map[string]any{ logAttrKeySourceFieldname: fromFieldName, @@ -1824,8 +2422,87 @@ func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPa } if f, ok := flexer.(*autoFlattener); ok { - diags.Append(f.xmlWrapperFlatten(ctx, fromFieldVal, valTo.Type(ctx), toFieldVal, wrapperField)...) + diags.Append(f.xmlWrapperFlatten(ctx, fromFieldVal, valTo.Type(ctx), toFieldVal, toFieldOpts)...) + } else { + diags.Append(DiagFlatteningIncompatibleTypes(fromFieldVal.Type(), reflect.TypeOf(toFieldVal.Interface()))) + } + if diags.HasError() { + break + } + continue + } + + // Handle pointer to XML wrapper struct + if fromFieldVal.Kind() == reflect.Pointer && !fromFieldVal.IsNil() && isXMLWrapperStruct(fromFieldVal.Type().Elem()) { + tflog.SubsystemTrace(ctx, subsystemName, "Converting pointer to XML wrapper struct to collection via wrapper tag", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: toFieldName, + "wrapper_field": wrapperField, + "flexer_type": fmt.Sprintf("%T", flexer), + }) + + valTo, ok := toFieldVal.Interface().(attr.Value) + if !ok { + tflog.SubsystemError(ctx, subsystemName, "Target field does not implement attr.Value") + diags.Append(diagFlatteningTargetDoesNotImplementAttrValue(reflect.TypeOf(toFieldVal.Interface()))) + break + } + + // Try both value and pointer type assertions + if f, ok := flexer.(autoFlattener); ok { + diags.Append(f.xmlWrapperFlatten(ctx, fromFieldVal.Elem(), valTo.Type(ctx), toFieldVal, toFieldOpts)...) + } else if f, ok := flexer.(*autoFlattener); ok { + diags.Append(f.xmlWrapperFlatten(ctx, fromFieldVal.Elem(), valTo.Type(ctx), toFieldVal, toFieldOpts)...) } else { + tflog.SubsystemError(ctx, subsystemName, "Type assertion to autoFlattener failed for wrapper tag", map[string]any{ + "flexer_type": fmt.Sprintf("%T", flexer), + }) + diags.Append(DiagFlatteningIncompatibleTypes(fromFieldVal.Type(), reflect.TypeOf(toFieldVal.Interface()))) + } + if diags.HasError() { + break + } + continue + } + + // Handle nil pointer to XML wrapper struct + if fromFieldVal.Kind() == reflect.Pointer && fromFieldVal.IsNil() && isXMLWrapperStruct(fromFieldVal.Type().Elem()) { + tflog.SubsystemTrace(ctx, subsystemName, "Converting nil pointer to XML wrapper struct to null collection", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: toFieldName, + "wrapper_field": wrapperField, + }) + + valTo, ok := toFieldVal.Interface().(attr.Value) + if !ok { + tflog.SubsystemError(ctx, subsystemName, "Target field does not implement attr.Value") + diags.Append(diagFlatteningTargetDoesNotImplementAttrValue(reflect.TypeOf(toFieldVal.Interface()))) + break + } + + // For nil XML wrapper pointers, set target to null collection + switch tTo := valTo.Type(ctx).(type) { + case basetypes.ListTypable: + var elementType attr.Type = types.StringType // default + if tToWithElem, ok := tTo.(attr.TypeWithElementType); ok { + elementType = tToWithElem.ElementType() + } + nullList, d := tTo.ValueFromList(ctx, types.ListNull(elementType)) + diags.Append(d...) + if !diags.HasError() { + toFieldVal.Set(reflect.ValueOf(nullList)) + } + case basetypes.SetTypable: + var elementType attr.Type = types.StringType // default + if tToWithElem, ok := tTo.(attr.TypeWithElementType); ok { + elementType = tToWithElem.ElementType() + } + nullSet, d := tTo.ValueFromSet(ctx, types.SetNull(elementType)) + diags.Append(d...) + if !diags.HasError() { + toFieldVal.Set(reflect.ValueOf(nullSet)) + } + default: diags.Append(DiagFlatteningIncompatibleTypes(fromFieldVal.Type(), reflect.TypeOf(toFieldVal.Interface()))) } if diags.HasError() { @@ -1835,10 +2512,208 @@ func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPa } } + // Automatic XML wrapper detection (without explicit wrapper tags) + fromFieldVal := valFrom.FieldByIndex(fromField.Index) + _, toOpts := autoflexTags(toField) + + // Handle pointer to XML wrapper struct + // Only auto-detect if target has explicit wrapper tag + if fromFieldVal.Kind() == reflect.Pointer { + if toOpts.XMLWrapperField() != "" && !fromFieldVal.IsNil() && isXMLWrapperStruct(fromFieldVal.Type().Elem()) { + // Check if target is a collection type (Set, List, or NestedObjectCollection) + if valTo, ok := toFieldVal.Interface().(attr.Value); ok { + targetType := valTo.Type(ctx) + switch tt := targetType.(type) { + case basetypes.SetTypable, basetypes.ListTypable, fwtypes.NestedObjectCollectionType: + // Check if wrapper Items field is empty/nil + // If so, set null and skip processing + wrapperVal := fromFieldVal.Elem() + wrapperFieldName := getXMLWrapperSliceFieldName(wrapperVal.Type()) + itemsField := wrapperVal.FieldByName(wrapperFieldName) + + if itemsField.IsValid() { + itemsEmpty := itemsField.IsNil() || (itemsField.Kind() == reflect.Slice && itemsField.Len() == 0) + + if itemsEmpty { + tflog.SubsystemTrace(ctx, subsystemName, "XML wrapper Items is empty, setting null", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: toFieldName, + }) + + // Call NullValue on NestedObjectCollectionType + if nestedObjType, ok := tt.(fwtypes.NestedObjectCollectionType); ok { + nullVal, d := nestedObjType.NullValue(ctx) + diags.Append(d...) + if !diags.HasError() { + toFieldVal.Set(reflect.ValueOf(nullVal)) + } + continue + } + } + } + + // Determine wrapper field based on target type + // For NestedObjectCollection, the wrapper field doesn't matter as Rule 2 will be used + wrapperField := getXMLWrapperSliceFieldName(fromFieldVal.Type().Elem()) + + tflog.SubsystemTrace(ctx, subsystemName, "Auto-converting pointer to XML wrapper struct to collection", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: toFieldName, + "wrapper_field": wrapperField, + }) + + // Try both value and pointer type assertions + if f, ok := flexer.(autoFlattener); ok { + diags.Append(f.xmlWrapperFlatten(ctx, fromFieldVal.Elem(), targetType, toFieldVal, toFieldOpts)...) + if diags.HasError() { + break + } + continue // Successfully handled, skip normal processing + } else if f, ok := flexer.(*autoFlattener); ok { + diags.Append(f.xmlWrapperFlatten(ctx, fromFieldVal.Elem(), targetType, toFieldVal, toFieldOpts)...) + if diags.HasError() { + break + } + continue // Successfully handled, skip normal processing + } + // If flexer is not autoFlattener, fall through to normal field matching + } + } + } else if toOpts.XMLWrapperField() != "" && fromFieldVal.IsNil() && isXMLWrapperStruct(fromFieldVal.Type().Elem()) { + // Handle nil pointer to XML wrapper struct - should result in null collection + if valTo, ok := toFieldVal.Interface().(attr.Value); ok { + switch tTo := valTo.Type(ctx).(type) { + case basetypes.SetTypable: + tflog.SubsystemTrace(ctx, subsystemName, "Auto-converting nil pointer to XML wrapper struct to null set", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: toFieldName, + }) + var elemType attr.Type = types.StringType + if tToWithElem, ok := tTo.(attr.TypeWithElementType); ok { + elemType = tToWithElem.ElementType() + } + nullSet, d := tTo.ValueFromSet(ctx, types.SetNull(elemType)) + diags.Append(d...) + if !diags.HasError() { + toFieldVal.Set(reflect.ValueOf(nullSet)) + } + if diags.HasError() { + break + } + continue + case basetypes.ListTypable: + tflog.SubsystemTrace(ctx, subsystemName, "Auto-converting nil pointer to XML wrapper struct to null list", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: toFieldName, + }) + var elemType attr.Type = types.StringType + if tToWithElem, ok := tTo.(attr.TypeWithElementType); ok { + elemType = tToWithElem.ElementType() + } + nullList, d := tTo.ValueFromList(ctx, types.ListNull(elemType)) + diags.Append(d...) + if !diags.HasError() { + toFieldVal.Set(reflect.ValueOf(nullList)) + } + if diags.HasError() { + break + } + continue + } + } + } + } else if toOpts.XMLWrapperField() != "" && isXMLWrapperStruct(fromFieldVal.Type()) { + // Handle direct XML wrapper struct (non-pointer) + if valTo, ok := toFieldVal.Interface().(attr.Value); ok { + switch valTo.Type(ctx).(type) { + case basetypes.SetTypable, basetypes.ListTypable: + wrapperField := toOpts.XMLWrapperField() + tflog.SubsystemTrace(ctx, subsystemName, "Auto-converting XML wrapper struct to collection", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: toFieldName, + "wrapper_field": wrapperField, + }) + + if f, ok := flexer.(*autoFlattener); ok { + diags.Append(f.xmlWrapperFlatten(ctx, fromFieldVal, valTo.Type(ctx), toFieldVal, toFieldOpts)...) + } else { + diags.Append(DiagFlatteningIncompatibleTypes(fromFieldVal.Type(), reflect.TypeOf(toFieldVal.Interface()))) + } + if diags.HasError() { + break + } + continue + } + } + } + + // Check for Rule 2: Source is XML wrapper, target is NestedObjectCollection + // Only apply if target field has xmlwrapper tag + fromFieldVal2 := valFrom.FieldByIndex(fromField.Index) + if toFieldOpts.XMLWrapperField() != "" && fromFieldVal2.Kind() == reflect.Pointer && !fromFieldVal2.IsNil() && isXMLWrapperStruct(fromFieldVal2.Type().Elem()) { + if valTo, ok := toFieldVal.Interface().(attr.Value); ok { + if nestedObjType, ok := valTo.Type(ctx).(fwtypes.NestedObjectCollectionType); ok { + sourceStructType := fromFieldVal2.Type().Elem() + + // Check if source is Rule 2 (more than 2 fields: Items, Quantity, + additional fields) + isRule2 := sourceStructType.NumField() > 2 + + if isRule2 { + tflog.SubsystemTrace(ctx, subsystemName, "Detected nested Rule 2 XML wrapper to NestedObjectCollection", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: toFieldName, + "field_count": sourceStructType.NumField(), + }) + + if f, ok := flexer.(*autoFlattener); ok { + diags.Append(f.xmlWrapperFlatten(ctx, fromFieldVal2.Elem(), valTo.Type(ctx), toFieldVal, toFieldOpts)...) + if diags.HasError() { + break + } + continue + } + } + + // Also check if nested model has xmlwrapper field (original logic) + samplePtr, d := nestedObjType.NewObjectPtr(ctx) + diags.Append(d...) + if !diags.HasError() { + sampleValue := reflect.ValueOf(samplePtr).Elem() + hasXMLWrapperTag := false + for i := 0; i < sampleValue.NumField(); i++ { + field := sampleValue.Type().Field(i) + _, fieldOpts := autoflexTags(field) + if fieldOpts.XMLWrapperField() != "" { + hasXMLWrapperTag = true + break + } + } + + if hasXMLWrapperTag { + tflog.SubsystemTrace(ctx, subsystemName, "Detected Rule 2: XML wrapper to NestedObjectCollection with nested xmlwrapper field", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: toFieldName, + }) + + _ = getXMLWrapperSliceFieldName(fromFieldVal2.Type().Elem()) + if f, ok := flexer.(*autoFlattener); ok { + diags.Append(f.xmlWrapperFlatten(ctx, fromFieldVal2.Elem(), valTo.Type(ctx), toFieldVal, toFieldOpts)...) + if diags.HasError() { + break + } + continue + } + } + } + } + } + } + opts := fieldOpts{ - legacy: toFieldOpts.Legacy(), - omitempty: toFieldOpts.OmitEmpty(), - xmlWrapper: toFieldOpts.XMLWrapperField() != "", + legacy: toFieldOpts.Legacy(), + omitempty: toFieldOpts.OmitEmpty(), + xmlWrapper: toFieldOpts.XMLWrapperField() != "", + xmlWrapperField: toFieldOpts.XMLWrapperField(), } diags.Append(flexer.convert(ctx, sourcePath.AtName(fromFieldName), valFrom.FieldByIndex(fromField.Index), targetPath.AtName(toFieldName), toFieldVal, opts)...) @@ -2054,3 +2929,486 @@ func DiagFlatteningIncompatibleTypes(sourceType, targetType reflect.Type) diag.E fmt.Sprintf("Source type %q cannot be flattened to target type %q.", fullTypeName(sourceType), fullTypeName(targetType)), ) } + +// handleXMLWrapperCollapse handles the reverse of XML wrapper collapse - i.e., XML wrapper split. +// This takes complex AWS structures with XML wrapper patterns and splits them into multiple TF fields. +func (flattener autoFlattener) handleXMLWrapperCollapse(ctx context.Context, sourcePath path.Path, valFrom reflect.Value, targetPath path.Path, valTo reflect.Value, typeFrom, typeTo reflect.Type, processedFields map[string]bool) diag.Diagnostics { + var diags diag.Diagnostics + + // Look for source fields that are complex XML wrapper structures that should be split + for i := 0; i < typeFrom.NumField(); i++ { + fromField := typeFrom.Field(i) + fromFieldName := fromField.Name + fromFieldType := fromField.Type + fromFieldVal := valFrom.Field(i) + + // Skip already processed fields + if processedFields[fromFieldName] { + continue + } + + // Check if this is a pointer to a struct or direct struct that could be split + var sourceStructType reflect.Type + var sourceStructVal reflect.Value + isNil := false + + if fromFieldType.Kind() == reflect.Pointer && fromFieldType.Elem().Kind() == reflect.Struct { + if fromFieldVal.IsNil() { + isNil = true + sourceStructType = fromFieldType.Elem() + sourceStructVal = reflect.Zero(sourceStructType) + } else { + sourceStructType = fromFieldType.Elem() + sourceStructVal = fromFieldVal.Elem() + } + } else if fromFieldType.Kind() == reflect.Struct { + sourceStructType = fromFieldType + sourceStructVal = fromFieldVal + } else { + continue + } + + // Check if this source struct should be split into multiple target fields + if !flattener.isXMLWrapperSplitSource(sourceStructType) { + continue + } + + // Before splitting, check if there's a direct field match in the target + // If the target has a field with the same name that can accept this XML wrapper, + // skip the split and let normal field matching handle it + if targetField, ok := (&fuzzyFieldFinder{}).findField(ctx, fromFieldName, typeFrom, typeTo, flattener); ok { + if targetFieldVal := valTo.FieldByIndex(targetField.Index); targetFieldVal.CanSet() { + // Check if target field is a NestedObjectCollection that can accept this wrapper + if targetAttr, ok := targetFieldVal.Interface().(attr.Value); ok { + if _, isNestedObjCollection := targetAttr.Type(ctx).(fwtypes.NestedObjectCollectionType); isNestedObjCollection { + // Skip split - let normal field matching handle this as a direct wrapper-to-wrapper mapping + tflog.SubsystemTrace(ctx, subsystemName, "Skipping XML wrapper split - direct field match found", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeyTargetFieldname: targetField.Name, + }) + continue + } + } + } + } + + tflog.SubsystemTrace(ctx, subsystemName, "Found XML wrapper split source", map[string]any{ + logAttrKeySourceFieldname: fromFieldName, + logAttrKeySourceType: sourceStructType.String(), + "is_nil": isNil, + }) + + // Handle the XML wrapper split + diags.Append(flattener.handleXMLWrapperSplit(ctx, sourcePath.AtName(fromFieldName), sourceStructVal, targetPath, valTo, sourceStructType, typeTo, isNil)...) + if diags.HasError() { + return diags + } + + // Mark the source field as processed + processedFields[fromFieldName] = true + } + + return diags +} + +// isXMLWrapperSplitSource checks if a struct type represents a source that should be +// split into multiple target fields (complex XML wrapper with additional fields beyond Items/Quantity) +func (flattener autoFlattener) isXMLWrapperSplitSource(structType reflect.Type) bool { + hasValidItems := false + hasValidQuantity := false + hasOtherFields := false + + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + fieldName := field.Name + fieldType := field.Type + + switch fieldName { + case getXMLWrapperSliceFieldName(structType): + // Items must be a slice to be a valid XML wrapper + if fieldType.Kind() == reflect.Slice { + hasValidItems = true + } + case xmlWrapperFieldQuantity: + // Quantity must be *int32 to be a valid XML wrapper + if fieldType == reflect.TypeOf((*int32)(nil)) { + hasValidQuantity = true + } + default: + // Any other field suggests this is a complex structure that should be split + hasOtherFields = true + } + } + + // Must have properly typed Items/Quantity (XML wrapper pattern) plus other fields to be a split candidate + return hasValidItems && hasValidQuantity && hasOtherFields +} + +// handleXMLWrapperSplit splits a complex AWS XML wrapper structure into multiple Terraform fields +func (flattener autoFlattener) handleXMLWrapperSplit(ctx context.Context, sourcePath path.Path, sourceStructVal reflect.Value, targetPath path.Path, valTo reflect.Value, sourceStructType, typeTo reflect.Type, isNil bool) diag.Diagnostics { + var diags diag.Diagnostics + + tflog.SubsystemTrace(ctx, subsystemName, "Handling XML wrapper split", map[string]any{ + logAttrKeySourceType: sourceStructType.String(), + logAttrKeyTargetType: typeTo.String(), + }) + + // If source is nil, find and set the matching target field to null + if isNil { + // Extract source field name from path + sourceFieldName := "" + sourcePathStr := sourcePath.String() + if lastDot := strings.LastIndex(sourcePathStr, "."); lastDot >= 0 { + sourceFieldName = sourcePathStr[lastDot+1:] + } + + // Find matching target field + if sourceFieldName != "" { + if targetField, ok := (&fuzzyFieldFinder{}).findField(ctx, sourceFieldName, reflect.StructOf([]reflect.StructField{{Name: sourceFieldName, Type: reflect.TypeOf(""), PkgPath: ""}}), typeTo, flattener); ok { + toFieldVal := valTo.FieldByIndex(targetField.Index) + if toFieldVal.CanSet() { + if valTo, ok := toFieldVal.Interface().(attr.Value); ok { + switch tTo := valTo.Type(ctx).(type) { + case basetypes.SetTypable, basetypes.ListTypable: + tflog.SubsystemTrace(ctx, subsystemName, "Setting target collection field to null", map[string]any{ + logAttrKeyTargetFieldname: targetField.Name, + }) + + var elemType attr.Type = types.StringType + if tToWithElem, ok := tTo.(attr.TypeWithElementType); ok { + elemType = tToWithElem.ElementType() + } + + var nullVal attr.Value + var d diag.Diagnostics + if setType, ok := tTo.(basetypes.SetTypable); ok { + nullVal, d = setType.ValueFromSet(ctx, types.SetNull(elemType)) + } else if listType, ok := tTo.(basetypes.ListTypable); ok { + nullVal, d = listType.ValueFromList(ctx, types.ListNull(elemType)) + } + diags.Append(d...) + if !diags.HasError() { + toFieldVal.Set(reflect.ValueOf(nullVal)) + } + } + } + } + } + } + return diags + } + + // Map each field in the source struct to corresponding target fields + for i := 0; i < sourceStructType.NumField(); i++ { + sourceField := sourceStructType.Field(i) + sourceFieldName := sourceField.Name + sourceFieldVal := sourceStructVal.Field(i) + + tflog.SubsystemTrace(ctx, subsystemName, "Processing source field for split", map[string]any{ + "source_field": sourceFieldName, + "source_type": sourceField.Type.String(), + }) + + // Map the source field to target field(s) + wrapperFieldName := getXMLWrapperSliceFieldName(sourceStructType) + if sourceFieldName == wrapperFieldName || sourceFieldName == xmlWrapperFieldQuantity { + // Items and Quantity should map to the main collection field + // Find a target field that matches the parent source field name + mainTargetFieldName := flattener.findMainTargetFieldForSplit(ctx, sourcePath, typeTo) + if mainTargetFieldName != "" { + if _, found := typeTo.FieldByName(mainTargetFieldName); found { + toFieldVal := valTo.FieldByName(mainTargetFieldName) + if toFieldVal.CanSet() { + tflog.SubsystemTrace(ctx, subsystemName, "Mapping Items/Quantity to main target field", map[string]any{ + "source_field": sourceFieldName, + "target_field": mainTargetFieldName, + }) + + // Only process this for the Items field, skip Quantity + if sourceFieldName == wrapperFieldName { + // Extract tagOptions from target field + opts := tagOptions("") + if targetField, ok := typeTo.FieldByName(mainTargetFieldName); ok { + if tag := targetField.Tag.Get("autoflex"); tag != "" { + _, opts = parseTag(tag) + } + } + diags.Append(flattener.convertXMLWrapperFieldToCollection(ctx, sourcePath, sourceStructVal, targetPath.AtName(mainTargetFieldName), toFieldVal, opts)...) + if diags.HasError() { + return diags + } + } + } + } + } + } else { + // Other fields should map directly by name + if targetField, found := typeTo.FieldByName(sourceFieldName); found { + toFieldVal := valTo.FieldByName(sourceFieldName) + if toFieldVal.CanSet() { + tflog.SubsystemTrace(ctx, subsystemName, "Mapping additional field by name", map[string]any{ + "source_field": sourceFieldName, + "target_field": sourceFieldName, + }) + + // Extract tagOptions from target field + opts := tagOptions("") + if tag := targetField.Tag.Get("autoflex"); tag != "" { + _, opts = parseTag(tag) + } + + // Convert the source field to target field + diags.Append(flattener.convertXMLWrapperFieldToCollection(ctx, sourcePath.AtName(sourceFieldName), sourceFieldVal, targetPath.AtName(sourceFieldName), toFieldVal, opts)...) + if diags.HasError() { + return diags + } + } + } + } + } + + return diags +} + +// findMainTargetFieldForSplit determines which target field should receive the Items/Quantity from the source +func (flattener autoFlattener) findMainTargetFieldForSplit(ctx context.Context, sourcePath path.Path, typeTo reflect.Type) string { + sourcePathStr := sourcePath.String() + if lastDot := strings.LastIndex(sourcePathStr, "."); lastDot >= 0 { + sourceFieldName := sourcePathStr[lastDot+1:] + + // Use fuzzy field finder for proper singular/plural and case matching + dummySourceType := reflect.StructOf([]reflect.StructField{{Name: sourceFieldName, Type: reflect.TypeOf(""), PkgPath: ""}}) + if targetField, ok := (&fuzzyFieldFinder{}).findField(context.Background(), sourceFieldName, dummySourceType, typeTo, flattener); ok { + return targetField.Name + } + } + + return "" +} + +// convertXMLWrapperFieldToCollection converts a source field (either XML wrapper or simple field) to a target collection +func (flattener autoFlattener) convertXMLWrapperFieldToCollection(ctx context.Context, sourcePath path.Path, sourceFieldVal reflect.Value, targetPath path.Path, toFieldVal reflect.Value, opts tagOptions) diag.Diagnostics { + var diags diag.Diagnostics + + // Check if source is an XML wrapper struct (has slice field + Quantity) + if sourceFieldVal.Kind() == reflect.Struct && isXMLWrapperStruct(sourceFieldVal.Type()) { + wrapperFieldName := getXMLWrapperSliceFieldName(sourceFieldVal.Type()) + tflog.SubsystemTrace(ctx, subsystemName, "Converting XML wrapper struct to collection", map[string]any{ + "source_type": sourceFieldVal.Type().String(), + "wrapper_field": wrapperFieldName, + }) + + // Use existing XML wrapper flatten logic + if valTo, ok := toFieldVal.Interface().(attr.Value); ok { + diags.Append(flattener.xmlWrapperFlatten(ctx, sourceFieldVal, valTo.Type(ctx), toFieldVal, opts)...) + if diags.HasError() { + return diags + } + } + } else if sourceFieldVal.Kind() == reflect.Pointer && !sourceFieldVal.IsNil() && isXMLWrapperStruct(sourceFieldVal.Type().Elem()) { + wrapperFieldName := getXMLWrapperSliceFieldName(sourceFieldVal.Type().Elem()) + tflog.SubsystemTrace(ctx, subsystemName, "Converting pointer to XML wrapper struct to collection", map[string]any{ + "source_type": sourceFieldVal.Type().String(), + "wrapper_field": wrapperFieldName, + }) + + // Use existing XML wrapper flatten logic + if valTo, ok := toFieldVal.Interface().(attr.Value); ok { + diags.Append(flattener.xmlWrapperFlatten(ctx, sourceFieldVal.Elem(), valTo.Type(ctx), toFieldVal, opts)...) + if diags.HasError() { + return diags + } + } + } else { + tflog.SubsystemTrace(ctx, subsystemName, "Converting non-XML wrapper field", map[string]any{ + "source_type": sourceFieldVal.Type().String(), + }) + + // For non-XML wrapper fields, use regular conversion + fieldOpts := fieldOpts{} + diags.Append(flattener.convert(ctx, sourcePath, sourceFieldVal, targetPath, toFieldVal, fieldOpts)...) + if diags.HasError() { + return diags + } + } + + return diags +} + +// createTargetValue creates a value of the target type from a source string value +func (flattener autoFlattener) createTargetValue(ctx context.Context, sourceValue types.String, targetType attr.Type) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + // Check what type of target we have + switch targetType := targetType.(type) { + case basetypes.StringTypable: + // Regular string type + val, d := targetType.ValueFromString(ctx, sourceValue) + diags.Append(d...) + return val, diags + default: + // For StringEnum types or other complex types, try to use regular conversion + // This is a bit of a hack, but StringEnum types should accept string values + targetTypeStr := targetType.String() + if strings.Contains(targetTypeStr, "StringEnum") { + // Create a zero value of the target type and try to set it + targetVal := reflect.New(reflect.TypeOf(targetType.ValueType(ctx))).Elem() + + // Try to convert using the AutoFlex conversion logic + diags.Append(flattener.convert(ctx, path.Empty(), reflect.ValueOf(sourceValue), path.Empty(), targetVal, fieldOpts{})...) + if diags.HasError() { + return nil, diags + } + + if attrVal, ok := targetVal.Interface().(attr.Value); ok { + return attrVal, diags + } + } + + // Fallback to string value + return sourceValue, diags + } +} + +// xmlWrapperFlattenRule2 handles Rule 2: XML wrapper to single plural block with items + additional fields +func (flattener *autoFlattener) xmlWrapperFlattenRule2(ctx context.Context, vFrom reflect.Value, tTo fwtypes.NestedObjectCollectionType, vTo reflect.Value, opts tagOptions) diag.Diagnostics { + var diags diag.Diagnostics + + tflog.SubsystemTrace(ctx, subsystemName, "xmlWrapperFlattenRule2 ENTRY", map[string]any{ + "source_type": vFrom.Type().String(), + }) + + // Check if wrapper Items field is empty/nil + wrapperFieldName := getXMLWrapperSliceFieldName(vFrom.Type()) + itemsField := vFrom.FieldByName(wrapperFieldName) + + itemsLen := 0 + if itemsField.IsValid() && !itemsField.IsNil() { + itemsLen = itemsField.Len() + } + + tflog.SubsystemTrace(ctx, subsystemName, "Checking Items field", map[string]any{ + "wrapper_field_name": wrapperFieldName, + "items_valid": itemsField.IsValid(), + "items_len": itemsLen, + }) + + if itemsField.IsValid() { + itemsEmpty := itemsField.IsNil() || (itemsField.Kind() == reflect.Slice && itemsField.Len() == 0) + + tflog.SubsystemTrace(ctx, subsystemName, "Items empty check", map[string]any{ + "items_empty": itemsEmpty, + }) + + // If Items is empty AND all other fields (except Quantity) are nil or zero, + // return null instead of creating a block with all default values + if itemsEmpty { + allOtherFieldsZero := true + for i := 0; i < vFrom.NumField(); i++ { + sourceField := vFrom.Field(i) + fieldName := vFrom.Type().Field(i).Name + + // Skip Items and Quantity fields + if fieldName == wrapperFieldName || fieldName == xmlWrapperFieldQuantity { + continue + } + + // For pointers: nil is zero, non-nil pointer to zero value is also considered zero + // For non-pointers: use IsZero() + isFieldZero := false + if sourceField.Kind() == reflect.Pointer { + if sourceField.IsNil() { + isFieldZero = true + } else if sourceField.Elem().IsZero() { + isFieldZero = true + } + } else { + isFieldZero = sourceField.IsZero() + } + + tflog.SubsystemTrace(ctx, subsystemName, "Checking field zero status", map[string]any{ + "field_name": fieldName, + "is_field_zero": isFieldZero, + "field_kind": sourceField.Kind().String(), + "field_is_nil": sourceField.Kind() == reflect.Pointer && sourceField.IsNil(), + }) + + if !isFieldZero { + allOtherFieldsZero = false + break + } + } + + tflog.SubsystemTrace(ctx, subsystemName, "Zero value check result", map[string]any{ + "all_other_fields_zero": allOtherFieldsZero, + }) + + if allOtherFieldsZero { + // Check if target field has omitempty - only return null if it does + if opts.OmitEmpty() { + tflog.SubsystemTrace(ctx, subsystemName, "XML wrapper Items is empty and all other fields are zero, returning null for Rule 2 (omitempty)") + nullVal, d := tTo.NullValue(ctx) + diags.Append(d...) + if !diags.HasError() { + vTo.Set(reflect.ValueOf(nullVal)) + } + return diags + } + tflog.SubsystemTrace(ctx, subsystemName, "XML wrapper Items is empty and all other fields are zero, but no omitempty - creating zero-value struct for Rule 2") + } + } + } + + nestedObjPtr, d := tTo.NewObjectPtr(ctx) + diags.Append(d...) + if diags.HasError() { + return diags + } + + nestedObjValue := reflect.ValueOf(nestedObjPtr).Elem() + + // Map Items field - find target field by xmlwrapper tag + if itemsField := vFrom.FieldByName(wrapperFieldName); itemsField.IsValid() { + for i := 0; i < nestedObjValue.NumField(); i++ { + targetField := nestedObjValue.Field(i) + targetFieldType := nestedObjValue.Type().Field(i) + _, fieldOpts := autoflexTags(targetFieldType) + if fieldOpts.XMLWrapperField() == wrapperFieldName { + if targetField.CanAddr() { + if targetAttr, ok := targetField.Addr().Interface().(attr.Value); ok { + diags.Append(autoFlattenConvert(ctx, itemsField.Interface(), targetAttr, flattener)...) + } + } + break + } + } + } + + // Map all other fields (skip Items and Quantity) + for i := 0; i < vFrom.NumField(); i++ { + sourceField := vFrom.Field(i) + fieldName := vFrom.Type().Field(i).Name + + if fieldName == wrapperFieldName || fieldName == xmlWrapperFieldQuantity { + continue + } + + if targetField := nestedObjValue.FieldByName(fieldName); targetField.IsValid() && targetField.CanAddr() { + if targetAttr, ok := targetField.Addr().Interface().(attr.Value); ok { + diags.Append(autoFlattenConvert(ctx, sourceField.Interface(), targetAttr, flattener)...) + } + } + } + + ptrType := reflect.TypeOf(nestedObjPtr) + objectSlice := reflect.MakeSlice(reflect.SliceOf(ptrType), 1, 1) + objectSlice.Index(0).Set(reflect.ValueOf(nestedObjPtr)) + + targetValue, d := tTo.ValueFromObjectSlice(ctx, objectSlice.Interface()) + diags.Append(d...) + if !diags.HasError() { + vTo.Set(reflect.ValueOf(targetValue)) + } + + return diags +} diff --git a/internal/framework/flex/autoflex_test.go b/internal/framework/flex/autoflex_test.go index 28034f110841..e1cf103409be 100644 --- a/internal/framework/flex/autoflex_test.go +++ b/internal/framework/flex/autoflex_test.go @@ -34,6 +34,7 @@ type runChecks struct { CompareDiags bool CompareTarget bool GoldenLogs bool // use golden snapshots for log comparison + PrintLogs bool // print logs to test output } // diagAF is a testing helper that creates a diag.Diagnostics containing @@ -117,6 +118,18 @@ func runAutoExpandTestCases(t *testing.T, testCases autoFlexTestCases, checks ru compareWithGolden(t, goldenPath, normalizedLines) } + if checks.PrintLogs { + lines, err := tflogtest.MultilineJSONDecode(&buf) + if err != nil { + t.Fatalf("Expand: decoding log lines: %s", err) + } + for _, line := range lines { + if msg, ok := line["@message"].(string); ok { + t.Logf("%s", msg) + } + } + } + if checks.CompareTarget && !diags.HasError() { if diff := cmp.Diff(tc.Target, tc.WantTarget); diff != "" { t.Errorf("unexpected diff (+wanted, -got): %s", diff) @@ -158,6 +171,18 @@ func runAutoFlattenTestCases(t *testing.T, testCases autoFlexTestCases, checks r compareWithGolden(t, goldenPath, normalizedLines) } + if checks.PrintLogs { + lines, err := tflogtest.MultilineJSONDecode(&buf) + if err != nil { + t.Fatalf("Flatten: decoding log lines: %s", err) + } + for _, line := range lines { + if msg, ok := line["@message"].(string); ok { + t.Logf("%s", msg) + } + } + } + if checks.CompareTarget && !diags.HasError() { less := func(a, b any) bool { return fmt.Sprintf("%+v", a) < fmt.Sprintf("%+v", b) } if diff := cmp.Diff(testCase.Target, testCase.WantTarget, append(opts, cmpopts.SortSlices(less))...); diff != "" { diff --git a/internal/framework/flex/autoflex_xml_wrapper_basic_test.go b/internal/framework/flex/autoflex_xml_wrapper_basic_test.go new file mode 100644 index 000000000000..d48a4abd7c01 --- /dev/null +++ b/internal/framework/flex/autoflex_xml_wrapper_basic_test.go @@ -0,0 +1,596 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package flex + +import ( + "context" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" +) + +// Rule 1 + Simple Type: Mammals/Whales example from context +func TestExpandXMLWrapperRule1SimpleType(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // AWS types + type Whales struct { + Quantity *int32 + Items []string + } + type Mammals struct { + Whales *Whales + } + + // TF model (collapsed) + type mammalModel struct { + Whales fwtypes.ListValueOf[types.String] `tfsdk:"whales" autoflex:",xmlwrapper=Items,omitempty"` + } + + testCases := autoFlexTestCases{ + "null": { + Source: &mammalModel{Whales: fwtypes.NewListValueOfNull[types.String](ctx)}, + Target: &Mammals{}, + WantTarget: &Mammals{Whales: nil}, + }, + "empty": { + Source: &mammalModel{Whales: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{})}, + Target: &Mammals{}, + WantTarget: &Mammals{Whales: &Whales{Items: []string{}, Quantity: aws.Int32(0)}}, + }, + "single": { + Source: &mammalModel{Whales: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{ + types.StringValue("blue"), + })}, + Target: &Mammals{}, + WantTarget: &Mammals{Whales: &Whales{Items: []string{"blue"}, Quantity: aws.Int32(1)}}, + }, + "multiple": { + Source: &mammalModel{Whales: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{ + types.StringValue("blue"), + types.StringValue("humpback"), + })}, + Target: &Mammals{}, + WantTarget: &Mammals{Whales: &Whales{Items: []string{"blue", "humpback"}, Quantity: aws.Int32(2)}}, + }, + } + + runAutoExpandTestCases(t, testCases, runChecks{CompareDiags: true, CompareTarget: true}) +} + +// Rule 1 + Complex Type: Fruits/Apples/Apple example from context +func TestExpandXMLWrapperRule1ComplexType(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // AWS types + type Apple struct { + Name *string + Color *string + } + type Apples struct { + Quantity *int32 + Items []Apple + } + type Fruits struct { + Apples *Apples + } + + // TF models (collapsed) + type appleModel struct { + Name types.String `tfsdk:"name"` + Color types.String `tfsdk:"color"` + } + type fruitModel struct { + Apple fwtypes.ListNestedObjectValueOf[appleModel] `tfsdk:"apple" autoflex:",xmlwrapper=Items,omitempty"` + } + + testCases := autoFlexTestCases{ + "null": { + Source: &fruitModel{Apple: fwtypes.NewListNestedObjectValueOfNull[appleModel](ctx)}, + Target: &Fruits{}, + WantTarget: &Fruits{Apples: nil}, + }, + "empty": { + Source: &fruitModel{Apple: fwtypes.NewListNestedObjectValueOfValueSliceMust[appleModel](ctx, []appleModel{})}, + Target: &Fruits{}, + WantTarget: &Fruits{Apples: &Apples{Items: []Apple{}, Quantity: aws.Int32(0)}}, + }, + "single": { + Source: &fruitModel{Apple: fwtypes.NewListNestedObjectValueOfValueSliceMust[appleModel](ctx, []appleModel{ + {Name: types.StringValue("Fuji"), Color: types.StringValue("red")}, + })}, + Target: &Fruits{}, + WantTarget: &Fruits{Apples: &Apples{ + Items: []Apple{{Name: aws.String("Fuji"), Color: aws.String("red")}}, + Quantity: aws.Int32(1), + }}, + }, + } + + runAutoExpandTestCases(t, testCases, runChecks{CompareDiags: true, CompareTarget: true}) +} + +// Rule 2 + Simple Type: Birds/Parrots example from context +func TestExpandXMLWrapperRule2SimpleType(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // AWS types + type Parrots struct { + Flying *bool + Quantity *int32 + Items []string + } + type Birds struct { + Parrots *Parrots + } + + // TF models (no collapsing) + type parrotModel struct { + Flying types.Bool `tfsdk:"flying"` + Items fwtypes.ListValueOf[types.String] `tfsdk:"items" autoflex:",xmlwrapper=Items"` + } + type birdModel struct { + Parrot fwtypes.ListNestedObjectValueOf[parrotModel] `tfsdk:"parrot" autoflex:",omitempty"` + } + + testCases := autoFlexTestCases{ + "null": { + Source: &birdModel{Parrot: fwtypes.NewListNestedObjectValueOfNull[parrotModel](ctx)}, + Target: &Birds{}, + WantTarget: &Birds{Parrots: nil}, + }, + "empty_items": { + Source: &birdModel{Parrot: fwtypes.NewListNestedObjectValueOfValueSliceMust[parrotModel](ctx, []parrotModel{ + { + Flying: types.BoolValue(false), + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{}), + }, + })}, + Target: &Birds{}, + WantTarget: &Birds{Parrots: &Parrots{ + Flying: aws.Bool(false), + Items: []string{}, + Quantity: aws.Int32(0), + }}, + }, + "with_flying": { + Source: &birdModel{Parrot: fwtypes.NewListNestedObjectValueOfValueSliceMust[parrotModel](ctx, []parrotModel{ + { + Flying: types.BoolValue(true), + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{ + types.StringValue("macaw"), + }), + }, + })}, + Target: &Birds{}, + WantTarget: &Birds{Parrots: &Parrots{ + Flying: aws.Bool(true), + Items: []string{"macaw"}, + Quantity: aws.Int32(1), + }}, + }, + } + + runAutoExpandTestCases(t, testCases, runChecks{CompareDiags: true, CompareTarget: true}) +} + +func TestExpandXMLWrapperRule2SimpleTypeNoOmitEmpty(t *testing.T) { + t.Parallel() + ctx := context.Background() + + type Parrots struct { + Flying *bool + Quantity *int32 + Items []string + } + type Birds struct { + Parrots *Parrots + } + + type parrotModel struct { + Flying types.Bool `tfsdk:"flying"` + Items fwtypes.ListValueOf[types.String] `tfsdk:"items" autoflex:",xmlwrapper=Items"` + } + type birdModel struct { + Parrot fwtypes.ListNestedObjectValueOf[parrotModel] `tfsdk:"parrot"` + } + + testCases := autoFlexTestCases{ + "null_no_omitempty": { + Source: &birdModel{Parrot: fwtypes.NewListNestedObjectValueOfNull[parrotModel](ctx)}, + Target: &Birds{}, + WantTarget: &Birds{Parrots: &Parrots{ + Flying: aws.Bool(false), + Items: []string{}, + Quantity: aws.Int32(0), + }}, + }, + } + + runAutoExpandTestCases(t, testCases, runChecks{CompareDiags: true, CompareTarget: true}) +} + +// Rule 2 + Complex Type: Trees/Oaks/Oak example from context +func TestExpandXMLWrapperRule2ComplexType(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // AWS types + type Oak struct { + Species *string + Age *int32 + } + type Oaks struct { + Diseased *bool + Quantity *int32 + Items []Oak + } + type Trees struct { + Oaks *Oaks + } + + // TF models (no collapsing) + type oakItemModel struct { + Species types.String `tfsdk:"species"` + Age types.Int64 `tfsdk:"age"` + } + type oakModel struct { + Diseased types.Bool `tfsdk:"diseased"` + Item fwtypes.ListNestedObjectValueOf[oakItemModel] `tfsdk:"item" autoflex:",xmlwrapper=Items"` + } + type treeModel struct { + Oak fwtypes.ListNestedObjectValueOf[oakModel] `tfsdk:"oak" autoflex:",omitempty"` + } + + testCases := autoFlexTestCases{ + "null": { + Source: &treeModel{Oak: fwtypes.NewListNestedObjectValueOfNull[oakModel](ctx)}, + Target: &Trees{}, + WantTarget: &Trees{Oaks: nil}, + }, + "empty_items": { + Source: &treeModel{Oak: fwtypes.NewListNestedObjectValueOfValueSliceMust[oakModel](ctx, []oakModel{ + { + Diseased: types.BoolValue(false), + Item: fwtypes.NewListNestedObjectValueOfValueSliceMust[oakItemModel](ctx, []oakItemModel{}), + }, + })}, + Target: &Trees{}, + WantTarget: &Trees{Oaks: &Oaks{ + Diseased: aws.Bool(false), + Items: []Oak{}, + Quantity: aws.Int32(0), + }}, + }, + "with_diseased": { + Source: &treeModel{Oak: fwtypes.NewListNestedObjectValueOfValueSliceMust[oakModel](ctx, []oakModel{ + { + Diseased: types.BoolValue(false), + Item: fwtypes.NewListNestedObjectValueOfValueSliceMust[oakItemModel](ctx, []oakItemModel{ + {Species: types.StringValue("white oak"), Age: types.Int64Value(100)}, + }), + }, + })}, + Target: &Trees{}, + WantTarget: &Trees{Oaks: &Oaks{ + Diseased: aws.Bool(false), + Items: []Oak{{Species: aws.String("white oak"), Age: aws.Int32(100)}}, + Quantity: aws.Int32(1), + }}, + }, + } + + runAutoExpandTestCases(t, testCases, runChecks{CompareDiags: true, CompareTarget: true}) +} + +// Flatten tests for Rule 1 + Simple Type +func TestFlattenXMLWrapperRule1SimpleType(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // AWS types + type Whales struct { + Quantity *int32 + Items []string + } + type Mammals struct { + Whales *Whales + } + + // TF model (collapsed) + type mammalModel struct { + Whales fwtypes.ListValueOf[types.String] `tfsdk:"whales" autoflex:",xmlwrapper=Items,omitempty"` + } + + testCases := autoFlexTestCases{ + "null": { + Source: &Mammals{Whales: nil}, + Target: &mammalModel{}, + WantTarget: &mammalModel{Whales: fwtypes.NewListValueOfNull[types.String](ctx)}, + }, + "empty": { + Source: &Mammals{Whales: &Whales{Items: []string{}, Quantity: aws.Int32(0)}}, + Target: &mammalModel{}, + WantTarget: &mammalModel{Whales: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{})}, + }, + "single": { + Source: &Mammals{Whales: &Whales{Items: []string{"blue"}, Quantity: aws.Int32(1)}}, + Target: &mammalModel{}, + WantTarget: &mammalModel{Whales: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{types.StringValue("blue")})}, + }, + "multiple": { + Source: &Mammals{Whales: &Whales{Items: []string{"blue", "humpback"}, Quantity: aws.Int32(2)}}, + Target: &mammalModel{}, + WantTarget: &mammalModel{Whales: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{ + types.StringValue("blue"), + types.StringValue("humpback"), + })}, + }, + } + + runAutoFlattenTestCases(t, testCases, runChecks{CompareDiags: true, CompareTarget: true}) +} + +// Flatten tests for Rule 1 + Complex Type +func TestFlattenXMLWrapperRule1ComplexType(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // AWS types + type Apple struct { + Name *string + Color *string + } + type Apples struct { + Quantity *int32 + Items []Apple + } + type Fruits struct { + Apples *Apples + } + + // TF models (collapsed) + type appleModel struct { + Name types.String `tfsdk:"name"` + Color types.String `tfsdk:"color"` + } + type fruitModel struct { + Apple fwtypes.ListNestedObjectValueOf[appleModel] `tfsdk:"apple" autoflex:",xmlwrapper=Items,omitempty"` + } + + testCases := autoFlexTestCases{ + "null": { + Source: &Fruits{Apples: nil}, + Target: &fruitModel{}, + WantTarget: &fruitModel{Apple: fwtypes.NewListNestedObjectValueOfNull[appleModel](ctx)}, + }, + "empty": { + Source: &Fruits{Apples: &Apples{Items: []Apple{}, Quantity: aws.Int32(0)}}, + Target: &fruitModel{}, + WantTarget: &fruitModel{Apple: fwtypes.NewListNestedObjectValueOfValueSliceMust[appleModel](ctx, []appleModel{})}, + }, + "single": { + Source: &Fruits{Apples: &Apples{ + Items: []Apple{{Name: aws.String("Fuji"), Color: aws.String("red")}}, + Quantity: aws.Int32(1), + }}, + Target: &fruitModel{}, + WantTarget: &fruitModel{Apple: fwtypes.NewListNestedObjectValueOfValueSliceMust[appleModel](ctx, []appleModel{ + {Name: types.StringValue("Fuji"), Color: types.StringValue("red")}, + })}, + }, + } + + runAutoFlattenTestCases(t, testCases, runChecks{CompareDiags: false, CompareTarget: true, PrintLogs: true}) +} + +// Flatten tests for Rule 2 + Simple Type +func TestFlattenXMLWrapperRule2SimpleType(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // AWS types + type Parrots struct { + Flying *bool + Quantity *int32 + Items []string + } + type Birds struct { + Parrots *Parrots + } + + // TF models (no collapsing) + type parrotModel struct { + Flying types.Bool `tfsdk:"flying"` + Items fwtypes.ListValueOf[types.String] `tfsdk:"items" autoflex:",xmlwrapper=Items"` + } + type birdModel struct { + Parrot fwtypes.ListNestedObjectValueOf[parrotModel] `tfsdk:"parrot" autoflex:",omitempty"` + } + + testCases := autoFlexTestCases{ + "null": { + Source: &Birds{Parrots: nil}, + Target: &birdModel{}, + WantTarget: &birdModel{Parrot: fwtypes.NewListNestedObjectValueOfNull[parrotModel](ctx)}, + }, + "empty_all_zero": { + Source: &Birds{Parrots: &Parrots{ + Flying: aws.Bool(false), // Zero value + Items: []string{}, + Quantity: aws.Int32(0), + }}, + Target: &birdModel{}, + // Empty Items + Flying at zero = null (no meaningful data) + WantTarget: &birdModel{Parrot: fwtypes.NewListNestedObjectValueOfNull[parrotModel](ctx)}, + }, + "empty_items_with_value": { + Source: &Birds{Parrots: &Parrots{ + Flying: aws.Bool(true), // Non-zero value + Items: []string{}, + Quantity: aws.Int32(0), + }}, + Target: &birdModel{}, + // Empty Items but Flying=true is meaningful, so create block + WantTarget: &birdModel{Parrot: fwtypes.NewListNestedObjectValueOfValueSliceMust[parrotModel](ctx, []parrotModel{ + { + Flying: types.BoolValue(true), + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{}), + }, + })}, + }, + "with_items": { + Source: &Birds{Parrots: &Parrots{ + Flying: aws.Bool(true), + Items: []string{"macaw"}, + Quantity: aws.Int32(1), + }}, + Target: &birdModel{}, + WantTarget: &birdModel{Parrot: fwtypes.NewListNestedObjectValueOfValueSliceMust[parrotModel](ctx, []parrotModel{ + { + Flying: types.BoolValue(true), + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{ + types.StringValue("macaw"), + }), + }, + })}, + }, + } + + runAutoFlattenTestCases(t, testCases, runChecks{CompareDiags: true, CompareTarget: true}) +} + +func TestFlattenXMLWrapperRule2SimpleTypeNoOmitEmpty(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // AWS types + type Parrots struct { + Flying *bool + Quantity *int32 + Items []string + } + type Birds struct { + Parrots *Parrots + } + + // TF models (no omitempty - should create zero-value struct) + type parrotModel struct { + Flying types.Bool `tfsdk:"flying"` + Items fwtypes.ListValueOf[types.String] `tfsdk:"items" autoflex:",xmlwrapper=Items"` + } + type birdModel struct { + Parrot fwtypes.ListNestedObjectValueOf[parrotModel] `tfsdk:"parrot"` + } + + testCases := autoFlexTestCases{ + "empty_all_zero_no_omitempty": { + Source: &Birds{Parrots: &Parrots{ + Flying: aws.Bool(false), + Items: []string{}, + Quantity: aws.Int32(0), + }}, + Target: &birdModel{}, + // Without omitempty, zero values should create a block + WantTarget: &birdModel{Parrot: fwtypes.NewListNestedObjectValueOfValueSliceMust[parrotModel](ctx, []parrotModel{ + { + Flying: types.BoolValue(false), + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{}), + }, + })}, + }, + } + + runAutoFlattenTestCases(t, testCases, runChecks{CompareDiags: true, CompareTarget: true}) +} + +// Flatten tests for Rule 2 + Complex Type +func TestFlattenXMLWrapperRule2ComplexType(t *testing.T) { + t.Parallel() + ctx := context.Background() + + // AWS types + type Oak struct { + Species *string + Age *int32 + } + type Oaks struct { + Diseased *bool + Quantity *int32 + Items []Oak + } + type Trees struct { + Oaks *Oaks + } + + // TF models (no collapsing) + type oakItemModel struct { + Species types.String `tfsdk:"species"` + Age types.Int64 `tfsdk:"age"` + } + type oakModel struct { + Diseased types.Bool `tfsdk:"diseased"` + Item fwtypes.ListNestedObjectValueOf[oakItemModel] `tfsdk:"item" autoflex:",xmlwrapper=Items"` + } + type treeModel struct { + Oak fwtypes.ListNestedObjectValueOf[oakModel] `tfsdk:"oak" autoflex:",omitempty"` + } + + testCases := autoFlexTestCases{ + "null": { + Source: &Trees{Oaks: nil}, + Target: &treeModel{}, + WantTarget: &treeModel{Oak: fwtypes.NewListNestedObjectValueOfNull[oakModel](ctx)}, + }, + "empty_all_zero": { + Source: &Trees{Oaks: &Oaks{ + Diseased: aws.Bool(false), // Zero value + Items: []Oak{}, + Quantity: aws.Int32(0), + }}, + Target: &treeModel{}, + // Empty Items + Diseased at zero = null (no meaningful data) + WantTarget: &treeModel{Oak: fwtypes.NewListNestedObjectValueOfNull[oakModel](ctx)}, + }, + "empty_items_with_value": { + Source: &Trees{Oaks: &Oaks{ + Diseased: aws.Bool(true), // Non-zero value + Items: []Oak{}, + Quantity: aws.Int32(0), + }}, + Target: &treeModel{}, + // Empty Items but Diseased=true is meaningful, so create block + WantTarget: &treeModel{Oak: fwtypes.NewListNestedObjectValueOfValueSliceMust[oakModel](ctx, []oakModel{ + { + Diseased: types.BoolValue(true), + Item: fwtypes.NewListNestedObjectValueOfValueSliceMust[oakItemModel](ctx, []oakItemModel{}), + }, + })}, + }, + "with_items": { + Source: &Trees{Oaks: &Oaks{ + Diseased: aws.Bool(false), + Items: []Oak{{Species: aws.String("white oak"), Age: aws.Int32(100)}}, + Quantity: aws.Int32(1), + }}, + Target: &treeModel{}, + WantTarget: &treeModel{Oak: fwtypes.NewListNestedObjectValueOfValueSliceMust[oakModel](ctx, []oakModel{ + { + Diseased: types.BoolValue(false), + Item: fwtypes.NewListNestedObjectValueOfValueSliceMust[oakItemModel](ctx, []oakItemModel{ + {Species: types.StringValue("white oak"), Age: types.Int64Value(100)}, + }), + }, + })}, + }, + } + + runAutoFlattenTestCases(t, testCases, runChecks{CompareDiags: true, CompareTarget: true}) +} diff --git a/internal/framework/flex/autoflex_xml_wrapper_test.go b/internal/framework/flex/autoflex_xml_wrapper_test.go new file mode 100644 index 000000000000..470855037338 --- /dev/null +++ b/internal/framework/flex/autoflex_xml_wrapper_test.go @@ -0,0 +1,1031 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +// XML Wrapper Compatibility Tests +// +// These tests validate AutoFlex handling of AWS XML wrapper patterns commonly found in CloudFront APIs. +// XML wrappers are 3-field structs that encapsulate collections for XML serialization. +// +// ## Rule 1: Wrapper contains only Items/Quantity +// AWS: {Items: []T, Quantity: *int32} +// +// Scalar elements ([]string, []enum): +// Terraform: SetAttribute or ListAttribute +// Example: origin_ssl_protocols = ["TLSv1.2", "TLSv1.3"] +// +// Struct elements ([]CustomStruct): +// Terraform: Repeatable singular blocks (one block per item) +// Example: lambda_function_association { ... } +// lambda_function_association { ... } +// +// ## Rule 2: Wrapper contains Items/Quantity + additional fields (e.g., Enabled) +// AWS: {Items: []T, Quantity: *int32, Enabled: *bool} +// +// Terraform: Single plural block containing collection + extra fields +// Example: trusted_signers { +// items = ["signer1", "signer2"] +// enabled = true # computed from len(items) > 0 +// } +// +// ## Key Behaviors: +// - null/unknown Terraform input → nil AWS pointer (no empty struct creation) +// - empty collection → {Items: [], Quantity: 0, ...} +// - Quantity is always derived from len(Items), never user-specified +// - Expand and flatten must be symmetrical for stable apply/refresh cycles + +package flex + +import ( + "context" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" +) + +// Test data types for Rule 1 XML wrappers +type testXMLWrapperScalar struct { + Items []string + Quantity *int32 +} + +// Test int32 slice items (like StatusCodes) +type testXMLWrapperInt32 struct { + Items []int32 + Quantity *int32 +} + +// Test int64 slice items +type testXMLWrapperInt64 struct { + Items []int64 + Quantity *int32 +} + +type testXMLWrapperStruct struct { + Items []testStructItem + Quantity *int32 +} + +type testStructItem struct { + Name *string + Value *int32 +} + +type testStructItemModel struct { + Name types.String `tfsdk:"name"` + Value types.Int32 `tfsdk:"value"` +} + +// Test Rule 1: XML wrappers with Items/Quantity only (scalar elements) +func TestExpandXMLWrapperRule1ScalarElements(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + testCases := map[string]autoFlexTestCases{ + "OriginSslProtocols": { + "null set": { + Source: fwtypes.NewSetValueOfNull[fwtypes.StringEnum[awstypes.SslProtocol]](ctx), + Target: &awstypes.OriginSslProtocols{}, + WantTarget: (*awstypes.OriginSslProtocols)(nil), + }, + "single protocol": { + Source: fwtypes.NewSetValueOfMust[fwtypes.StringEnum[awstypes.SslProtocol]](ctx, []attr.Value{ + fwtypes.StringEnumValue(awstypes.SslProtocolTLSv12), + }), + Target: &awstypes.OriginSslProtocols{}, + WantTarget: &awstypes.OriginSslProtocols{Items: []awstypes.SslProtocol{awstypes.SslProtocolTLSv12}, Quantity: aws.Int32(1)}, + }, + }, + "TestXMLWrapperScalar": func() autoFlexTestCases { + type tfModel struct { + Field fwtypes.SetValueOf[types.String] `tfsdk:"field" autoflex:",xmlwrapper=Items,omitempty"` + } + type awsModel struct { + Field *testXMLWrapperScalar + } + return autoFlexTestCases{ + "null set": { + Source: &tfModel{Field: fwtypes.NewSetValueOfNull[types.String](ctx)}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: nil}, + }, + "empty set": { + Source: &tfModel{Field: fwtypes.NewSetValueOfMust[types.String](ctx, []attr.Value{})}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperScalar{Items: []string{}, Quantity: aws.Int32(0)}}, + }, + "single item": { + Source: &tfModel{Field: fwtypes.NewSetValueOfMust[types.String](ctx, []attr.Value{ + types.StringValue("item1"), + })}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperScalar{Items: []string{"item1"}, Quantity: aws.Int32(1)}}, + }, + "multiple items": { + Source: &tfModel{Field: fwtypes.NewSetValueOfMust[types.String](ctx, []attr.Value{ + types.StringValue("item1"), + types.StringValue("item2"), + })}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperScalar{Items: []string{"item1", "item2"}, Quantity: aws.Int32(2)}}, + }, + } + }(), + "TestXMLWrapperInt32": func() autoFlexTestCases { + type tfModel struct { + Field fwtypes.SetValueOf[types.Int32] `tfsdk:"field" autoflex:",xmlwrapper=Items"` + } + type awsModel struct { + Field *testXMLWrapperInt32 + } + return autoFlexTestCases{ + "empty set": { + Source: &tfModel{Field: fwtypes.NewSetValueOfMust[types.Int32](ctx, []attr.Value{})}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperInt32{Items: []int32{}, Quantity: aws.Int32(0)}}, + }, + "single item": { + Source: &tfModel{Field: fwtypes.NewSetValueOfMust[types.Int32](ctx, []attr.Value{types.Int32Value(404)})}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperInt32{Items: []int32{404}, Quantity: aws.Int32(1)}}, + }, + } + }(), + "TestXMLWrapperInt64": func() autoFlexTestCases { + type tfModel struct { + Field fwtypes.SetValueOf[types.Int64] `tfsdk:"field" autoflex:",xmlwrapper=Items"` + } + type awsModel struct { + Field *testXMLWrapperInt64 + } + return autoFlexTestCases{ + "empty set": { + Source: &tfModel{Field: fwtypes.NewSetValueOfMust[types.Int64](ctx, []attr.Value{})}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperInt64{Items: []int64{}, Quantity: aws.Int32(0)}}, + }, + "single item": { + Source: &tfModel{Field: fwtypes.NewSetValueOfMust[types.Int64](ctx, []attr.Value{types.Int64Value(12345)})}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperInt64{Items: []int64{12345}, Quantity: aws.Int32(1)}}, + }, + } + }(), + } + + for testName, cases := range testCases { + t.Run(testName, func(t *testing.T) { + t.Parallel() + + if testName == "OriginSslProtocols" { + runAutoExpandTestCases(t, cases, runChecks{CompareDiags: true, CompareTarget: false}) + } else { + runAutoExpandTestCases(t, cases, runChecks{CompareDiags: true, CompareTarget: true}) + } + }) + } +} + +// Test Rule 1: XML wrappers with Items/Quantity only (struct elements) +func TestExpandXMLWrapperRule1StructElements(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + type tfModel struct { + Field fwtypes.SetNestedObjectValueOf[testStructItemModel] `tfsdk:"field" autoflex:",xmlwrapper=Items,omitempty"` + } + type awsModel struct { + Field *testXMLWrapperStruct + } + + testCases := map[string]autoFlexTestCases{ + "TestXMLWrapperStruct": { + "null set": { + Source: &tfModel{Field: fwtypes.NewSetNestedObjectValueOfNull[testStructItemModel](ctx)}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: nil}, + }, + "empty set": { + Source: &tfModel{Field: fwtypes.NewSetNestedObjectValueOfValueSliceMust[testStructItemModel](ctx, []testStructItemModel{})}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperStruct{Items: []testStructItem{}, Quantity: aws.Int32(0)}}, + }, + "single item": { + Source: &tfModel{Field: fwtypes.NewSetNestedObjectValueOfValueSliceMust[testStructItemModel](ctx, []testStructItemModel{ + { + Name: types.StringValue("test"), + Value: types.Int32Value(42), + }, + })}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperStruct{ + Items: []testStructItem{ + { + Name: aws.String("test"), + Value: aws.Int32(42), + }, + }, + Quantity: aws.Int32(1), + }}, + }, + }, + } + + for testName, cases := range testCases { + t.Run(testName, func(t *testing.T) { + t.Parallel() + + runAutoExpandTestCases(t, cases, runChecks{CompareDiags: true, CompareTarget: true}) + }) + } +} + +// Test Rule 1 Flatten: XML wrappers with Items/Quantity only (scalar elements) +func TestFlattenXMLWrapperRule1ScalarElements(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // Source struct containing XML wrapper + type sourceStruct struct { + XMLWrapper *testXMLWrapperScalar + } + + // Target struct containing collection + type targetStruct struct { + XMLWrapper fwtypes.SetValueOf[types.String] `autoflex:",xmlwrapper=Items"` + } + + testCases := map[string]autoFlexTestCases{ + "TestXMLWrapperScalar": { + "nil source": { + Source: &sourceStruct{XMLWrapper: nil}, + Target: &targetStruct{}, + WantTarget: &targetStruct{XMLWrapper: fwtypes.NewSetValueOfNull[types.String](ctx)}, + }, + "empty items": { + Source: &sourceStruct{XMLWrapper: &testXMLWrapperScalar{Items: []string{}, Quantity: aws.Int32(0)}}, + Target: &targetStruct{}, + WantTarget: &targetStruct{XMLWrapper: fwtypes.NewSetValueOfMust[types.String](ctx, []attr.Value{})}, + }, + "single item": { + Source: &sourceStruct{XMLWrapper: &testXMLWrapperScalar{Items: []string{"item1"}, Quantity: aws.Int32(1)}}, + Target: &targetStruct{}, + WantTarget: &targetStruct{XMLWrapper: fwtypes.NewSetValueOfMust[types.String](ctx, []attr.Value{types.StringValue("item1")})}, + }, + }, + } + + for testName, cases := range testCases { + t.Run(testName, func(t *testing.T) { + t.Parallel() + + runAutoFlattenTestCases(t, cases, runChecks{CompareDiags: true, CompareTarget: true}) + }) + } +} + +// Test Rule 1 Flatten: XML wrappers with Items/Quantity only (struct elements) +func TestFlattenXMLWrapperRule1StructElements(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // Source struct containing XML wrapper + type sourceStruct struct { + XMLWrapper *testXMLWrapperStruct + } + + // Target struct containing collection + type targetStruct struct { + XMLWrapper fwtypes.SetNestedObjectValueOf[testStructItemModel] `autoflex:",xmlwrapper=Items"` + } + + testCases := map[string]autoFlexTestCases{ + "TestXMLWrapperStruct": { + "nil source": { + Source: &sourceStruct{XMLWrapper: nil}, + Target: &targetStruct{}, + WantTarget: &targetStruct{XMLWrapper: fwtypes.NewSetNestedObjectValueOfNull[testStructItemModel](ctx)}, + }, + "empty items": { + Source: &sourceStruct{XMLWrapper: &testXMLWrapperStruct{Items: []testStructItem{}, Quantity: aws.Int32(0)}}, + Target: &targetStruct{}, + WantTarget: &targetStruct{XMLWrapper: fwtypes.NewSetNestedObjectValueOfValueSliceMust[testStructItemModel](ctx, []testStructItemModel{})}, + }, + "single item": { + Source: &sourceStruct{ + XMLWrapper: &testXMLWrapperStruct{ + Items: []testStructItem{ + {Name: aws.String("test"), Value: aws.Int32(42)}, + }, + Quantity: aws.Int32(1), + }, + }, + Target: &targetStruct{}, + WantTarget: &targetStruct{ + XMLWrapper: fwtypes.NewSetNestedObjectValueOfValueSliceMust[testStructItemModel](ctx, []testStructItemModel{ + {Name: types.StringValue("test"), Value: types.Int32Value(42)}, + }), + }, + }, + }, + } + + for testName, cases := range testCases { + t.Run(testName, func(t *testing.T) { + t.Parallel() + + runAutoFlattenTestCases(t, cases, runChecks{CompareDiags: true, CompareTarget: true}) + }) + } +} + +// Test Rule 1 Symmetry: Verify expand→flatten produces identical results +func TestXMLWrapperRule1Symmetry(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // Test scalar elements symmetry + t.Run("ScalarElements", func(t *testing.T) { + t.Parallel() + + type tfSource struct { + XMLWrapper fwtypes.SetValueOf[types.String] `tfsdk:"xml_wrapper" autoflex:",xmlwrapper=Items"` + } + + type awsTarget struct { + XMLWrapper *testXMLWrapperScalar + } + + // Original Terraform value + original := tfSource{ + XMLWrapper: fwtypes.NewSetValueOfMust[types.String](ctx, []attr.Value{ + types.StringValue("item1"), + types.StringValue("item2"), + }), + } + + // Expand: TF → AWS + var awsStruct awsTarget + expandDiags := Expand(ctx, original, &awsStruct) + if expandDiags.HasError() { + t.Fatalf("Expand failed: %v", expandDiags) + } + + // Verify expand result + if awsStruct.XMLWrapper == nil { + t.Fatal("Expected non-nil XMLWrapper") + } + if len(awsStruct.XMLWrapper.Items) != 2 || *awsStruct.XMLWrapper.Quantity != 2 { + t.Errorf("Expand produced incorrect result: Items=%v, Quantity=%v", awsStruct.XMLWrapper.Items, *awsStruct.XMLWrapper.Quantity) + } + + // Flatten: AWS → TF + var targetStruct tfSource + flattenDiags := Flatten(ctx, &awsStruct, &targetStruct) + if flattenDiags.HasError() { + t.Fatalf("Flatten failed: %v", flattenDiags) + } + + // Verify symmetry: flattened result should match original + if !original.XMLWrapper.Equal(targetStruct.XMLWrapper) { + t.Errorf("Symmetry broken: original=%v, flattened=%v", original.XMLWrapper, targetStruct.XMLWrapper) + } + }) + + // Test struct elements symmetry + t.Run("StructElements", func(t *testing.T) { + t.Parallel() + + type tfSource struct { + XMLWrapper fwtypes.SetNestedObjectValueOf[testStructItemModel] `tfsdk:"xml_wrapper" autoflex:",xmlwrapper=Items"` + } + + type awsTarget struct { + XMLWrapper *testXMLWrapperStruct + } + + // Original Terraform value + original := tfSource{ + XMLWrapper: fwtypes.NewSetNestedObjectValueOfValueSliceMust[testStructItemModel](ctx, []testStructItemModel{ + {Name: types.StringValue("test"), Value: types.Int32Value(42)}, + }), + } + + // Expand: TF → AWS + var awsStruct awsTarget + expandDiags := Expand(ctx, original, &awsStruct) + if expandDiags.HasError() { + t.Fatalf("Expand failed: %v", expandDiags) + } + + // Verify expand result + if awsStruct.XMLWrapper == nil { + t.Fatal("Expected non-nil XMLWrapper") + } + if len(awsStruct.XMLWrapper.Items) != 1 || *awsStruct.XMLWrapper.Quantity != 1 { + t.Errorf("Expand produced incorrect result: Items=%v, Quantity=%v", awsStruct.XMLWrapper.Items, *awsStruct.XMLWrapper.Quantity) + } + + // Flatten: AWS → TF + var targetStruct tfSource + flattenDiags := Flatten(ctx, &awsStruct, &targetStruct) + if flattenDiags.HasError() { + t.Fatalf("Flatten failed: %v", flattenDiags) + } + + // Verify symmetry: flattened result should match original + if !original.XMLWrapper.Equal(targetStruct.XMLWrapper) { + t.Errorf("Symmetry broken: original=%v, flattened=%v", original.XMLWrapper, targetStruct.XMLWrapper) + } + }) +} + +// Test data types for Rule 2 XML wrappers (Items/Quantity + additional fields) +type testXMLWrapperRule2 struct { + Items []string + Quantity *int32 + Enabled *bool +} + +// Test different field ordering (matches real TrustedSigners/TrustedKeyGroups) +type testXMLWrapperRule2DifferentOrder struct { + Enabled *bool + Quantity *int32 + Items []string +} + +type testRule2Model struct { + Items fwtypes.ListValueOf[types.String] `tfsdk:"items" autoflex:",xmlwrapper=Items"` + Enabled types.Bool `tfsdk:"enabled"` +} + +// Test Rule 2: XML wrappers with Items/Quantity + additional fields (e.g., Enabled) +func TestExpandXMLWrapperRule2(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + type tfModel struct { + Field fwtypes.ListNestedObjectValueOf[testRule2Model] `tfsdk:"field" autoflex:",omitempty"` + } + type awsModel struct { + Field *testXMLWrapperRule2 + } + type awsModelDifferentOrder struct { + Field *testXMLWrapperRule2DifferentOrder + } + + testCases := map[string]autoFlexTestCases{ + "TestXMLWrapperRule2": { + "null block": { + Source: &tfModel{Field: fwtypes.NewListNestedObjectValueOfNull[testRule2Model](ctx)}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: nil}, + }, + "empty items, enabled false": { + Source: &tfModel{Field: fwtypes.NewListNestedObjectValueOfValueSliceMust[testRule2Model](ctx, []testRule2Model{ + { + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{}), + Enabled: types.BoolValue(false), + }, + })}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperRule2{Items: []string{}, Quantity: aws.Int32(0), Enabled: aws.Bool(false)}}, + }, + "with items, enabled true": { + Source: &tfModel{Field: fwtypes.NewListNestedObjectValueOfValueSliceMust[testRule2Model](ctx, []testRule2Model{ + { + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{types.StringValue("item1")}), + Enabled: types.BoolValue(true), + }, + })}, + Target: &awsModel{}, + WantTarget: &awsModel{Field: &testXMLWrapperRule2{Items: []string{"item1"}, Quantity: aws.Int32(1), Enabled: aws.Bool(true)}}, + }, + }, + "TestXMLWrapperRule2DifferentOrder": { + "different field order": { + Source: &tfModel{Field: fwtypes.NewListNestedObjectValueOfValueSliceMust[testRule2Model](ctx, []testRule2Model{ + { + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{types.StringValue("item1")}), + Enabled: types.BoolValue(true), + }, + })}, + Target: &awsModelDifferentOrder{}, + WantTarget: &awsModelDifferentOrder{Field: &testXMLWrapperRule2DifferentOrder{Items: []string{"item1"}, Quantity: aws.Int32(1), Enabled: aws.Bool(true)}}, + }, + }, + } + + for testName, cases := range testCases { + t.Run(testName, func(t *testing.T) { + t.Parallel() + + runAutoExpandTestCases(t, cases, runChecks{CompareDiags: true, CompareTarget: true}) + }) + } +} + +// Test Rule 2 Flatten: XML wrappers with Items/Quantity + additional fields +func TestFlattenXMLWrapperRule2(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // Source struct containing Rule 2 XML wrapper + type sourceStruct struct { + XMLWrapper *testXMLWrapperRule2 + } + + // Target struct containing Rule 2 pattern + type targetStruct struct { + XMLWrapper fwtypes.ListNestedObjectValueOf[testRule2Model] `autoflex:",omitempty"` + } + + testCases := map[string]autoFlexTestCases{ + "TestXMLWrapperRule2": { + "nil source": { + Source: &sourceStruct{XMLWrapper: nil}, + Target: &targetStruct{}, + WantTarget: &targetStruct{XMLWrapper: fwtypes.NewListNestedObjectValueOfNull[testRule2Model](ctx)}, + }, + "empty items, enabled false": { + Source: &sourceStruct{ + XMLWrapper: &testXMLWrapperRule2{Items: []string{}, Quantity: aws.Int32(0), Enabled: aws.Bool(false)}, + }, + Target: &targetStruct{}, + // Empty state (Enabled=false, Items=[]) should flatten to null per Rule 2 + WantTarget: &targetStruct{XMLWrapper: fwtypes.NewListNestedObjectValueOfNull[testRule2Model](ctx)}, + }, + }, + } + + for testName, cases := range testCases { + t.Run(testName, func(t *testing.T) { + t.Parallel() + + runAutoFlattenTestCases(t, cases, runChecks{CompareDiags: true, CompareTarget: true}) + }) + } +} + +// Test Rule 2 Symmetry: Verify expand→flatten produces identical results +func TestXMLWrapperRule2Symmetry(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + t.Run("Rule2Pattern", func(t *testing.T) { + t.Parallel() + + // Wrap source and target in structs with xmlwrapper tags + type tfSource struct { + XMLWrapper fwtypes.ListNestedObjectValueOf[testRule2Model] `tfsdk:"xml_wrapper"` + } + + type awsTarget struct { + XMLWrapper *testXMLWrapperRule2 + } + + // Original Terraform value (Rule 2 pattern) + original := tfSource{ + XMLWrapper: fwtypes.NewListNestedObjectValueOfValueSliceMust[testRule2Model](ctx, []testRule2Model{ + { + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{types.StringValue("signer1"), types.StringValue("signer2")}), + Enabled: types.BoolValue(true), + }, + }), + } + + // Expand: TF → AWS + var awsStruct awsTarget + expandDiags := Expand(ctx, original, &awsStruct) + if expandDiags.HasError() { + t.Fatalf("Expand failed: %v", expandDiags) + } + + // Verify expand result + if awsStruct.XMLWrapper == nil { + t.Fatal("Expected non-nil XMLWrapper") + } + if len(awsStruct.XMLWrapper.Items) != 2 || *awsStruct.XMLWrapper.Quantity != 2 || !*awsStruct.XMLWrapper.Enabled { + t.Errorf("Expand produced incorrect result: Items=%v, Quantity=%v, Enabled=%v", + awsStruct.XMLWrapper.Items, *awsStruct.XMLWrapper.Quantity, *awsStruct.XMLWrapper.Enabled) + } + + // Flatten: AWS → TF + var targetStruct tfSource + flattenDiags := Flatten(ctx, &awsStruct, &targetStruct) + if flattenDiags.HasError() { + t.Fatalf("Flatten failed: %v", flattenDiags) + } + + // Verify symmetry: flattened result should match original + if !original.XMLWrapper.Equal(targetStruct.XMLWrapper) { + t.Errorf("Symmetry broken: original=%v, flattened=%v", original.XMLWrapper, targetStruct.XMLWrapper) + } + }) +} + +// Test Real CloudFront Types: Validate actual AWS SDK types +func TestXMLWrapperRealCloudFrontTypes(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // Test with actual CloudFront TrustedSigners type + t.Run("TrustedSigners", func(t *testing.T) { + t.Parallel() + + // Terraform model matching CloudFront schema + type trustedSignersModel struct { + Items fwtypes.ListValueOf[types.String] `tfsdk:"items"` + Enabled types.Bool `tfsdk:"enabled"` + } + + type tfSource struct { + TrustedSigners fwtypes.ListNestedObjectValueOf[trustedSignersModel] `tfsdk:"trusted_signers" autoflex:",xmlwrapper=Items"` + } + + type awsTarget struct { + TrustedSigners *awstypes.TrustedSigners + } + + source := tfSource{ + TrustedSigners: fwtypes.NewListNestedObjectValueOfValueSliceMust[trustedSignersModel](ctx, []trustedSignersModel{ + { + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{types.StringValue("AKIAIOSFODNN7EXAMPLE")}), + Enabled: types.BoolValue(true), + }, + }), + } + + var target awsTarget + diags := Expand(ctx, source, &target) + + if diags.HasError() { + t.Errorf("TrustedSigners expand failed: %v", diags) + } else { + // Verify correct AWS structure + if target.TrustedSigners == nil { + t.Fatal("Expected non-nil TrustedSigners") + } + if len(target.TrustedSigners.Items) != 1 || *target.TrustedSigners.Quantity != 1 || !*target.TrustedSigners.Enabled { + t.Errorf("TrustedSigners incorrect: Items=%v, Quantity=%v, Enabled=%v", + target.TrustedSigners.Items, *target.TrustedSigners.Quantity, *target.TrustedSigners.Enabled) + } + } + }) +} + +// Test Rule 1: XML wrapper with complex struct items (like Origins) +func TestFlattenXMLWrapperRule1ComplexStructItems(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // AWS types: Origins with Items/Quantity + type awsOrigin struct { + DomainName *string + Id *string + } + + type awsOrigins struct { + Items []awsOrigin + Quantity *int32 + } + + type awsDistributionConfig struct { + Origins *awsOrigins + Comment *string + } + + // Terraform models + type tfOriginModel struct { + DomainName types.String `tfsdk:"domain_name"` + Id types.String `tfsdk:"id"` + } + + type tfDistributionModel struct { + Origin fwtypes.SetNestedObjectValueOf[tfOriginModel] `tfsdk:"origin" autoflex:",xmlwrapper=Items"` + Comment types.String `tfsdk:"comment"` + } + + t.Run("flatten origins", func(t *testing.T) { + t.Parallel() + + source := &awsDistributionConfig{ + Origins: &awsOrigins{ + Items: []awsOrigin{ + {DomainName: aws.String("example.com"), Id: aws.String("origin1")}, + {DomainName: aws.String("cdn.example.com"), Id: aws.String("origin2")}, + }, + Quantity: aws.Int32(2), + }, + Comment: aws.String("test distribution"), + } + + var target tfDistributionModel + diags := Flatten(ctx, source, &target) + + if diags.HasError() { + t.Fatalf("Flatten failed: %v", diags) + } + + if target.Origin.IsNull() { + t.Fatal("Expected non-null origin set") + } + + elements := target.Origin.Elements() + if len(elements) != 2 { + t.Errorf("Expected 2 origins, got %d", len(elements)) + } + }) +} + +// Test that structs with Items/Quantity but NO xmlwrapper tag are NOT treated as XML wrappers +func TestExpandNoXMLWrapperTag(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // AWS types + type awsFunctionAssociation struct { + FunctionArn *string + EventType string + } + + type awsFunctionAssociations struct { + Items []awsFunctionAssociation + Quantity *int32 + } + + // Terraform types (NO xmlwrapper tag) + type tfFunctionAssociation struct { + EventType types.String `tfsdk:"event_type"` + FunctionArn types.String `tfsdk:"function_arn"` + } + + type tfFunctionAssociations struct { + Items fwtypes.ListNestedObjectValueOf[tfFunctionAssociation] `tfsdk:"items"` + Quantity types.Int64 `tfsdk:"quantity"` + } + + // Source with nested struct containing Items/Quantity + source := tfFunctionAssociations{ + Items: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []tfFunctionAssociation{ + { + EventType: types.StringValue("viewer-request"), + FunctionArn: types.StringValue("arn:aws:cloudfront::123456789012:function/test-1"), + }, + }), + Quantity: types.Int64Value(1), + } + + var target awsFunctionAssociations + diags := Expand(ctx, source, &target) + + if diags.HasError() { + t.Fatalf("Expand failed: %v", diags) + } + + // Without xmlwrapper tag, should map fields directly (not treat as wrapper) + if len(target.Items) != 1 { + t.Errorf("Expected 1 item, got %d", len(target.Items)) + } + if target.Quantity == nil || *target.Quantity != 1 { + t.Errorf("Expected Quantity=1, got %v", target.Quantity) + } +} + +func TestFlattenNoXMLWrapperTag(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // AWS types + type awsFunctionAssociation struct { + FunctionArn *string + EventType string + } + + type awsFunctionAssociations struct { + Items []awsFunctionAssociation + Quantity *int32 + } + + // Terraform types (NO xmlwrapper tag) + type tfFunctionAssociation struct { + EventType types.String `tfsdk:"event_type"` + FunctionArn types.String `tfsdk:"function_arn"` + } + + type tfFunctionAssociations struct { + Items fwtypes.ListNestedObjectValueOf[tfFunctionAssociation] `tfsdk:"items"` + Quantity types.Int64 `tfsdk:"quantity"` + } + + source := awsFunctionAssociations{ + Quantity: aws.Int32(1), + Items: []awsFunctionAssociation{ + { + EventType: "viewer-request", + FunctionArn: aws.String("arn:aws:cloudfront::123456789012:function/test-1"), + }, + }, + } + + var target tfFunctionAssociations + diags := Flatten(ctx, source, &target) + + if diags.HasError() { + t.Fatalf("Flatten failed: %v", diags) + } + + // Without xmlwrapper tag, should map fields directly (not treat as wrapper) + if target.Items.IsNull() { + t.Error("Expected non-null Items") + } + if target.Quantity.IsNull() || target.Quantity.ValueInt64() != 1 { + t.Errorf("Expected Quantity=1, got %v", target.Quantity) + } +} + +// Test nested XML wrappers: wrapper inside wrapper +// Example: CacheBehaviors (Rule 1) → CacheBehavior → TrustedKeyGroups (Rule 2) +func TestNestedXMLWrappers(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + // AWS types: Outer wrapper (Rule 1) containing inner wrapper (Rule 2) + type awsTrustedKeyGroups struct { + Items []string + Quantity *int32 + Enabled *bool + } + + type awsCacheBehavior struct { + PathPattern *string + TargetOriginId *string + TrustedKeyGroups *awsTrustedKeyGroups + } + + type awsCacheBehaviors struct { + Items []awsCacheBehavior + Quantity *int32 + } + + // Terraform types: Both levels have xmlwrapper tags + type tfTrustedKeyGroups struct { + Items fwtypes.ListValueOf[types.String] `tfsdk:"items" autoflex:",xmlwrapper=Items"` + Enabled types.Bool `tfsdk:"enabled"` + } + + type tfCacheBehavior struct { + PathPattern types.String `tfsdk:"path_pattern"` + TargetOriginId types.String `tfsdk:"target_origin_id"` + TrustedKeyGroups fwtypes.ListNestedObjectValueOf[tfTrustedKeyGroups] `tfsdk:"trusted_key_groups"` + } + + t.Run("Expand", func(t *testing.T) { + t.Parallel() + + // Terraform source with nested wrappers + type tfSource struct { + CacheBehaviors fwtypes.SetNestedObjectValueOf[tfCacheBehavior] `tfsdk:"cache_behaviors" autoflex:",xmlwrapper=Items"` + } + + source := tfSource{ + CacheBehaviors: fwtypes.NewSetNestedObjectValueOfValueSliceMust(ctx, []tfCacheBehavior{ + { + PathPattern: types.StringValue("/api/*"), + TargetOriginId: types.StringValue("api-origin"), + TrustedKeyGroups: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []tfTrustedKeyGroups{ + { + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{types.StringValue("key-group-1")}), + Enabled: types.BoolValue(true), + }, + }), + }, + }), + } + + type awsTarget struct { + CacheBehaviors *awsCacheBehaviors + } + + var target awsTarget + diags := Expand(ctx, source, &target) + + if diags.HasError() { + t.Fatalf("Expand failed: %v", diags) + } + + // Verify outer wrapper + if target.CacheBehaviors == nil { + t.Fatal("Expected non-nil CacheBehaviors") + } + if len(target.CacheBehaviors.Items) != 1 || *target.CacheBehaviors.Quantity != 1 { + t.Errorf("Outer wrapper incorrect: Items=%d, Quantity=%v", len(target.CacheBehaviors.Items), *target.CacheBehaviors.Quantity) + } + + // Verify inner wrapper + behavior := target.CacheBehaviors.Items[0] + if behavior.TrustedKeyGroups == nil { + t.Fatal("Expected non-nil TrustedKeyGroups") + } + if len(behavior.TrustedKeyGroups.Items) != 1 || *behavior.TrustedKeyGroups.Quantity != 1 { + t.Errorf("Inner wrapper incorrect: Items=%d, Quantity=%v", + len(behavior.TrustedKeyGroups.Items), *behavior.TrustedKeyGroups.Quantity) + } + if !*behavior.TrustedKeyGroups.Enabled { + t.Error("Expected Enabled=true") + } + }) + + t.Run("Flatten", func(t *testing.T) { + t.Parallel() + + // AWS source with nested wrappers + type awsSource struct { + CacheBehaviors *awsCacheBehaviors + } + + source := awsSource{ + CacheBehaviors: &awsCacheBehaviors{ + Quantity: aws.Int32(1), + Items: []awsCacheBehavior{ + { + PathPattern: aws.String("/api/*"), + TargetOriginId: aws.String("api-origin"), + TrustedKeyGroups: &awsTrustedKeyGroups{ + Enabled: aws.Bool(true), + Quantity: aws.Int32(1), + Items: []string{"key-group-1"}, + }, + }, + }, + }, + } + + type tfTarget struct { + CacheBehaviors fwtypes.SetNestedObjectValueOf[tfCacheBehavior] `tfsdk:"cache_behaviors" autoflex:",xmlwrapper=Items"` + } + + var target tfTarget + diags := Flatten(ctx, &source, &target) + + if diags.HasError() { + t.Fatalf("Flatten failed: %v", diags) + } + + // Verify outer wrapper flattened correctly + if target.CacheBehaviors.IsNull() { + t.Fatal("Expected non-null CacheBehaviors") + } + + // Extract the actual behavior structs + var behaviors []tfCacheBehavior + diags = target.CacheBehaviors.ElementsAs(ctx, &behaviors, false) + if diags.HasError() { + t.Fatalf("ElementsAs failed: %v", diags) + } + + if len(behaviors) != 1 { + t.Fatalf("Expected 1 cache behavior, got %d", len(behaviors)) + } + + // Verify inner wrapper flattened correctly + behavior := behaviors[0] + if behavior.TrustedKeyGroups.IsNull() { + t.Error("Expected non-null TrustedKeyGroups") + } + + var keyGroups []tfTrustedKeyGroups + diags = behavior.TrustedKeyGroups.ElementsAs(ctx, &keyGroups, false) + if diags.HasError() { + t.Fatalf("ElementsAs failed: %v", diags) + } + + if len(keyGroups) != 1 { + t.Fatalf("Expected 1 TrustedKeyGroups element, got %d", len(keyGroups)) + } + + keyGroup := keyGroups[0] + if keyGroup.Items.IsNull() { + t.Error("Expected non-null Items") + } + if keyGroup.Enabled.IsNull() || !keyGroup.Enabled.ValueBool() { + t.Error("Expected Enabled=true") + } + }) +} diff --git a/internal/framework/flex/autoflex_xml_wrapper_trace_test.go b/internal/framework/flex/autoflex_xml_wrapper_trace_test.go new file mode 100644 index 000000000000..27424cdd2e1f --- /dev/null +++ b/internal/framework/flex/autoflex_xml_wrapper_trace_test.go @@ -0,0 +1,84 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package flex + +import ( + "bytes" + "context" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflogtest" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" +) + +// Test to capture trace logs for Rule 2 expansion +func TestTraceRule2Expansion(t *testing.T) { + ctx := context.Background() + + // AWS types (Rule 2: Items + Quantity + Flying) + type Parrots struct { + Flying *bool + Quantity *int32 + Items []string + } + type Birds struct { + Parrots *Parrots + } + + // TF models + type parrotModel struct { + Flying types.Bool `tfsdk:"flying"` + Items fwtypes.ListValueOf[types.String] `tfsdk:"items" autoflex:",xmlwrapper=Items"` + } + type birdModel struct { + Parrot fwtypes.ListNestedObjectValueOf[parrotModel] `tfsdk:"parrot"` + } + + source := &birdModel{ + Parrot: fwtypes.NewListNestedObjectValueOfValueSliceMust[parrotModel](ctx, []parrotModel{ + { + Flying: types.BoolValue(true), + Items: fwtypes.NewListValueOfMust[types.String](ctx, []attr.Value{ + types.StringValue("macaw"), + }), + }, + }), + } + + target := &Birds{} + + var buf bytes.Buffer + ctx = tflogtest.RootLogger(ctx, &buf) + ctx = registerTestingLogger(ctx) + + diags := Expand(ctx, source, target) + if diags.HasError() { + t.Fatalf("Expand failed: %v", diags) + } + + // Print logs + t.Logf("\n=== CAPTURED LOGS ===\n%s\n", buf.String()) + + // Check result + if target.Parrots == nil { + t.Fatal("Expected non-nil Parrots") + } + if target.Parrots.Quantity == nil { + t.Error("Expected Quantity to be set, got nil") + } else if *target.Parrots.Quantity != 1 { + t.Errorf("Expected Quantity=1, got %d", *target.Parrots.Quantity) + } + + expected := &Birds{Parrots: &Parrots{ + Flying: aws.Bool(true), + Items: []string{"macaw"}, + Quantity: aws.Int32(1), + }} + + t.Logf("\nExpected: %+v", expected.Parrots) + t.Logf("Got: %+v", target.Parrots) +} diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_list_source_and_empty_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_list_source_and_empty_pointer_struct_target.golden index f3ff93d40a80..5644aef62b83 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_list_source_and_empty_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_list_source_and_empty_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_list_source_and_empty_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_list_source_and_empty_struct_target.golden index 6708ad340f0b..e701c4c84d4e 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_list_source_and_empty_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_list_source_and_empty_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_set_source_and_empty_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_set_source_and_empty_pointer_struct_target.golden index 286adced823d..f2247ea22dbf 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_set_source_and_empty_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_set_source_and_empty_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_set_source_and_empty_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_set_source_and_empty_struct_target.golden index c2acc70f34cd..0592414e97bd 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_set_source_and_empty_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/empty_set_source_and_empty_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_list_source_and_nonempty_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_list_source_and_nonempty_pointer_struct_target.golden index 0e21c5101c79..29545eb120da 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_list_source_and_nonempty_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_list_source_and_nonempty_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_list_source_and_nonempty_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_list_source_and_nonempty_struct_target.golden index ce730022a36c..42db23ee7189 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_list_source_and_nonempty_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_list_source_and_nonempty_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_set_source_and_nonempty_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_set_source_and_nonempty_pointer_struct_target.golden index 8e410ffa504f..1c2f7a04de90 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_set_source_and_nonempty_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_set_source_and_nonempty_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_set_source_and_nonempty_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_set_source_and_nonempty_struct_target.golden index 038985ddb445..e1df7ca0ba2e 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_set_source_and_nonempty_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/nonempty_set_source_and_nonempty_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_list_source_and_single_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_list_source_and_single_pointer_struct_target.golden index c3322509983c..892fde8ff6c7 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_list_source_and_single_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_list_source_and_single_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "ptr", + "target_type": "*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.Expander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_list_source_and_single_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_list_source_and_single_struct_target.golden index caec9d19cc3f..335256d031ec 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_list_source_and_single_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_list_source_and_single_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "struct", + "target_type": "flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.Expander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_set_source_and_single_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_set_source_and_single_pointer_struct_target.golden index dacd8f8ffcb9..0ec4012fbefd 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_set_source_and_single_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_set_source_and_single_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "ptr", + "target_type": "*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.Expander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_set_source_and_single_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_set_source_and_single_struct_target.golden index 82c7f37bec28..9ea2c309f713 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_set_source_and_single_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_expander/single_set_source_and_single_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "struct", + "target_type": "flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.Expander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/empty_list_source_and_empty_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/empty_list_source_and_empty_interface_target.golden index ae4966774845..7ef9bce0e663 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/empty_list_source_and_empty_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/empty_list_source_and_empty_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "slice", + "target_type": "[]flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/empty_set_source_and_empty_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/empty_set_source_and_empty_interface_target.golden index c9108b1a6305..9407afc301f3 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/empty_set_source_and_empty_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/empty_set_source_and_empty_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "slice", + "target_type": "[]flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/nonempty_list_source_and_nonempty_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/nonempty_list_source_and_nonempty_interface_target.golden index e1c231304daa..0c63fd7a397b 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/nonempty_list_source_and_nonempty_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/nonempty_list_source_and_nonempty_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "slice", + "target_type": "[]flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/nonempty_set_source_and_nonempty_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/nonempty_set_source_and_nonempty_interface_target.golden index 48ae8631feef..88b1a1469c3b 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/nonempty_set_source_and_nonempty_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/nonempty_set_source_and_nonempty_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "slice", + "target_type": "[]flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_list_nonexpander_source_and_single_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_list_nonexpander_source_and_single_interface_target.golden index d38d794034ae..b098cac6a723 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_list_nonexpander_source_and_single_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_list_nonexpander_source_and_single_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfSingleStringField]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "interface", + "target_type": "flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "error", "@message": "AutoFlex Expand; incompatible types", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_list_source_and_single_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_list_source_and_single_interface_target.golden index 27744b3b2fdf..e02e5541c408 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_list_source_and_single_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_list_source_and_single_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "interface", + "target_type": "flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.Expander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_set_source_and_single_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_set_source_and_single_interface_target.golden index 176a6ea7160c..14c8c3a51255 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_set_source_and_single_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface/single_set_source_and_single_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "interface", + "target_type": "flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.Expander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/empty_list_source_and_empty_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/empty_list_source_and_empty_interface_target.golden index 2a93001c2728..aa3dc14373eb 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/empty_list_source_and_empty_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/empty_list_source_and_empty_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "slice", + "target_type": "[]flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/empty_set_source_and_empty_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/empty_set_source_and_empty_interface_target.golden index c919b18e2635..e6e216bf0766 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/empty_set_source_and_empty_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/empty_set_source_and_empty_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "slice", + "target_type": "[]flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/nonempty_list_source_and_nonempty_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/nonempty_list_source_and_nonempty_interface_target.golden index 5baaf2ad00ac..03f36b308858 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/nonempty_list_source_and_nonempty_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/nonempty_list_source_and_nonempty_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "slice", + "target_type": "[]flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/nonempty_set_source_and_nonempty_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/nonempty_set_source_and_nonempty_interface_target.golden index 54aa02f83e27..a8625db949dd 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/nonempty_set_source_and_nonempty_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/nonempty_set_source_and_nonempty_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "slice", + "target_type": "[]flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_list_nonexpander_source_and_single_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_list_nonexpander_source_and_single_interface_target.golden index d38d794034ae..b098cac6a723 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_list_nonexpander_source_and_single_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_list_nonexpander_source_and_single_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfSingleStringField]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "interface", + "target_type": "flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "error", "@message": "AutoFlex Expand; incompatible types", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_list_source_and_single_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_list_source_and_single_interface_target.golden index 86a41142900f..b44c533a7fd3 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_list_source_and_single_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_list_source_and_single_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "interface", + "target_type": "flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.TypedExpander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_set_source_and_single_interface_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_set_source_and_single_interface_target.golden index 645829332df0..516b05239c10 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_set_source_and_single_interface_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_interface_typed_expander/single_set_source_and_single_interface_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "target_kind": "interface", + "target_type": "flex.awsInterfaceInterface", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.TypedExpander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_list_source_and_empty_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_list_source_and_empty_pointer_struct_target.golden index eb53598edd31..197692f9bd62 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_list_source_and_empty_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_list_source_and_empty_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_list_source_and_empty_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_list_source_and_empty_struct_target.golden index a09e3f000dc3..2dedde7cd09f 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_list_source_and_empty_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_list_source_and_empty_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_set_source_and_empty_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_set_source_and_empty_pointer_struct_target.golden index afeb99b3ad31..fc3cd5183475 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_set_source_and_empty_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_set_source_and_empty_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_set_source_and_empty_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_set_source_and_empty_struct_target.golden index 5837169fde59..fa2035d8282f 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_set_source_and_empty_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/empty_set_source_and_empty_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_list_source_and_nonempty_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_list_source_and_nonempty_pointer_struct_target.golden index a26d0dd2cb85..778191edeab8 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_list_source_and_nonempty_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_list_source_and_nonempty_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_list_source_and_nonempty_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_list_source_and_nonempty_struct_target.golden index 6b79b8ad4cad..1cd6fb32d94e 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_list_source_and_nonempty_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_list_source_and_nonempty_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_set_source_and_nonempty_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_set_source_and_nonempty_pointer_struct_target.golden index 8855a5436f90..7d6e98e4ed9e 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_set_source_and_nonempty_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_set_source_and_nonempty_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_set_source_and_nonempty_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_set_source_and_nonempty_struct_target.golden index 0a6788498de5..61d636f34276 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_set_source_and_nonempty_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/nonempty_set_source_and_nonempty_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "slice", + "target_type": "[]flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_list_source_and_single_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_list_source_and_single_pointer_struct_target.golden index 6e84c20c0136..f2bb18999c86 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_list_source_and_single_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_list_source_and_single_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "ptr", + "target_type": "*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.TypedExpander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_list_source_and_single_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_list_source_and_single_struct_target.golden index 42beb476f607..be70b4640d80 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_list_source_and_single_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_list_source_and_single_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "struct", + "target_type": "flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.TypedExpander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_set_source_and_single_pointer_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_set_source_and_single_pointer_struct_target.golden index 699477997c71..ed426428c6bf 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_set_source_and_single_pointer_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_set_source_and_single_pointer_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "ptr", + "target_type": "*flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.TypedExpander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_set_source_and_single_struct_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_set_source_and_single_struct_target.golden index 8c58f338ed29..144114cb5ae8 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_set_source_and_single_struct_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/expand_typed_expander/single_set_source_and_single_struct_target.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "Field1", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfTypedExpander]", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "target_kind": "struct", + "target_type": "flex.awsExpander", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "info", "@message": "Source implements flex.TypedExpander", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_pointer_struct_list_source_and_empty_list_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_pointer_struct_list_source_and_empty_list_target.golden index 23e6c89ef88a..c5573a416288 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_pointer_struct_list_source_and_empty_list_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_pointer_struct_list_source_and_empty_list_target.golden @@ -43,6 +43,8 @@ "autoflex.source.size": 0, "autoflex.source.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "source_type": "[]*flex.awsExpander", + "target_type": "ListNestedObjectTypeOf[flex.tfFlexer]" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_pointer_struct_list_source_and_empty_set_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_pointer_struct_list_source_and_empty_set_target.golden index fe01250ab60f..7390cfafec4e 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_pointer_struct_list_source_and_empty_set_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_pointer_struct_list_source_and_empty_set_target.golden @@ -43,6 +43,8 @@ "autoflex.source.size": 0, "autoflex.source.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "source_type": "[]*flex.awsExpander", + "target_type": "SetNestedObjectTypeOf[flex.tfFlexer]" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_struct_list_source_and_empty_list_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_struct_list_source_and_empty_list_target.golden index 6cb0e36d141b..242e4950de00 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_struct_list_source_and_empty_list_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_struct_list_source_and_empty_list_target.golden @@ -43,6 +43,8 @@ "autoflex.source.size": 0, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "source_type": "[]flex.awsExpander", + "target_type": "ListNestedObjectTypeOf[flex.tfFlexer]" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_struct_list_source_and_empty_set_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_struct_list_source_and_empty_set_target.golden index 1f6fa5558634..4b50d19b0b59 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_struct_list_source_and_empty_set_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/empty_struct_list_source_and_empty_set_target.golden @@ -43,6 +43,8 @@ "autoflex.source.size": 0, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "source_type": "[]flex.awsExpander", + "target_type": "SetNestedObjectTypeOf[flex.tfFlexer]" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_pointer_struct_list_source_and_nonempty_list_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_pointer_struct_list_source_and_nonempty_list_target.golden index 55359a7d3469..cdc37836f0fc 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_pointer_struct_list_source_and_nonempty_list_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_pointer_struct_list_source_and_nonempty_list_target.golden @@ -43,7 +43,19 @@ "autoflex.source.size": 2, "autoflex.source.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "source_type": "[]*flex.awsExpander", + "target_type": "ListNestedObjectTypeOf[flex.tfFlexer]" + }, + { + "@level": "debug", + "@message": "DEBUG: First element of nested object collection", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "element_type": "*flex.awsExpander" }, { "@level": "info", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_pointer_struct_list_source_and_nonempty_set_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_pointer_struct_list_source_and_nonempty_set_target.golden index 204ca5b2b33c..f4aab304bf2d 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_pointer_struct_list_source_and_nonempty_set_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_pointer_struct_list_source_and_nonempty_set_target.golden @@ -43,7 +43,19 @@ "autoflex.source.size": 2, "autoflex.source.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "source_type": "[]*flex.awsExpander", + "target_type": "SetNestedObjectTypeOf[flex.tfFlexer]" + }, + { + "@level": "debug", + "@message": "DEBUG: First element of nested object collection", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "[]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "element_type": "*flex.awsExpander" }, { "@level": "info", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_struct_list_source_and_nonempty_list_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_struct_list_source_and_nonempty_list_target.golden index a747025227c9..4795b790fbae 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_struct_list_source_and_nonempty_list_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_struct_list_source_and_nonempty_list_target.golden @@ -43,7 +43,32 @@ "autoflex.source.size": 2, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "source_type": "[]flex.awsExpander", + "target_type": "ListNestedObjectTypeOf[flex.tfFlexer]" + }, + { + "@level": "debug", + "@message": "DEBUG: First element of nested object collection", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "element_type": "flex.awsExpander" + }, + { + "@level": "debug", + "@message": "DEBUG: Struct field", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "field_name": "AWSField", + "field_type": "string", + "is_nil": false, + "is_zero": false }, { "@level": "info", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_struct_list_source_and_set_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_struct_list_source_and_set_target.golden index edbdf2508df8..47b737f1adef 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_struct_list_source_and_set_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_flattener/nonempty_struct_list_source_and_set_target.golden @@ -43,7 +43,32 @@ "autoflex.source.size": 2, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "source_type": "[]flex.awsExpander", + "target_type": "SetNestedObjectTypeOf[flex.tfFlexer]" + }, + { + "@level": "debug", + "@message": "DEBUG: First element of nested object collection", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "element_type": "flex.awsExpander" + }, + { + "@level": "debug", + "@message": "DEBUG: Struct field", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsExpander", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfFlexer]", + "field_name": "AWSField", + "field_type": "string", + "is_nil": false, + "is_zero": false }, { "@level": "info", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/empty_interface_list_source_and_empty_list_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/empty_interface_list_source_and_empty_list_target.golden index f343de973cd7..e695f188c63c 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/empty_interface_list_source_and_empty_list_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/empty_interface_list_source_and_empty_list_target.golden @@ -43,6 +43,8 @@ "autoflex.source.size": 0, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "source_type": "[]flex.awsInterfaceInterface", + "target_type": "ListNestedObjectTypeOf[flex.tfInterfaceFlexer]" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/empty_interface_list_source_and_empty_set_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/empty_interface_list_source_and_empty_set_target.golden index 2c5d83aa605c..a15ce418c386 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/empty_interface_list_source_and_empty_set_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/empty_interface_list_source_and_empty_set_target.golden @@ -43,6 +43,8 @@ "autoflex.source.size": 0, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "source_type": "[]flex.awsInterfaceInterface", + "target_type": "SetNestedObjectTypeOf[flex.tfInterfaceFlexer]" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nil_interface_list_source_and_empty_list_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nil_interface_list_source_and_empty_list_target.golden index d04531c3214b..f8c61af34d8a 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nil_interface_list_source_and_empty_list_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nil_interface_list_source_and_empty_list_target.golden @@ -37,7 +37,7 @@ }, { "@level": "trace", - "@message": "Flattening with NullValue", + "@message": "Flattening with EmptyValue (for nested object collection)", "@module": "provider.autoflex", "autoflex.source.path": "Field1", "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nil_interface_list_source_and_empty_set_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nil_interface_list_source_and_empty_set_target.golden index 7b4cfca7e6c5..978b3733aa77 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nil_interface_list_source_and_empty_set_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nil_interface_list_source_and_empty_set_target.golden @@ -37,7 +37,7 @@ }, { "@level": "trace", - "@message": "Flattening with NullValue", + "@message": "Flattening with EmptyValue (for nested object collection)", "@module": "provider.autoflex", "autoflex.source.path": "Field1", "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nonempty_interface_list_source_and_nonempty_list_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nonempty_interface_list_source_and_nonempty_list_target.golden index 31fa554b7f5e..ffde8f063034 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nonempty_interface_list_source_and_nonempty_list_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nonempty_interface_list_source_and_nonempty_list_target.golden @@ -43,7 +43,19 @@ "autoflex.source.size": 2, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "source_type": "[]flex.awsInterfaceInterface", + "target_type": "ListNestedObjectTypeOf[flex.tfInterfaceFlexer]" + }, + { + "@level": "debug", + "@message": "DEBUG: First element of nested object collection", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "element_type": "flex.awsInterfaceInterface" }, { "@level": "info", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nonempty_interface_list_source_and_nonempty_set_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nonempty_interface_list_source_and_nonempty_set_target.golden index 465de2af1aef..007e6df91fc5 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nonempty_interface_list_source_and_nonempty_set_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_interface/nonempty_interface_list_source_and_nonempty_set_target.golden @@ -43,7 +43,19 @@ "autoflex.source.size": 2, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", "autoflex.target.path": "Field1", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "source_type": "[]flex.awsInterfaceInterface", + "target_type": "SetNestedObjectTypeOf[flex.tfInterfaceFlexer]" + }, + { + "@level": "debug", + "@message": "DEBUG: First element of nested object collection", + "@module": "provider.autoflex", + "autoflex.source.path": "Field1", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsInterfaceInterface", + "autoflex.target.path": "Field1", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfInterfaceFlexer]", + "element_type": "flex.awsInterfaceInterface" }, { "@level": "info", diff --git a/internal/framework/flex/testdata/autoflex/dispatch/flatten_logging_collections/zero_value_slice_or_map_of_primitive_types_source_and_collection_of_primtive_types_target.golden b/internal/framework/flex/testdata/autoflex/dispatch/flatten_logging_collections/zero_value_slice_or_map_of_primitive_types_source_and_collection_of_primtive_types_target.golden index 3327a2557bec..0f40992eb9c4 100644 --- a/internal/framework/flex/testdata/autoflex/dispatch/flatten_logging_collections/zero_value_slice_or_map_of_primitive_types_source_and_collection_of_primtive_types_target.golden +++ b/internal/framework/flex/testdata/autoflex/dispatch/flatten_logging_collections/zero_value_slice_or_map_of_primitive_types_source_and_collection_of_primtive_types_target.golden @@ -37,7 +37,7 @@ }, { "@level": "trace", - "@message": "Flattening with ListNull", + "@message": "Flattening with ListValue (empty for nil)", "@module": "provider.autoflex", "autoflex.source.path": "Field1", "autoflex.source.type": "[]string", @@ -66,7 +66,7 @@ }, { "@level": "trace", - "@message": "Flattening with ListNull", + "@message": "Flattening with ListValue (empty for nil)", "@module": "provider.autoflex", "autoflex.source.path": "Field2", "autoflex.source.type": "[]*string", @@ -95,7 +95,7 @@ }, { "@level": "trace", - "@message": "Flattening with SetNull", + "@message": "Flattening with SetValue (empty for nil)", "@module": "provider.autoflex", "autoflex.source.path": "Field3", "autoflex.source.type": "[]string", @@ -124,7 +124,7 @@ }, { "@level": "trace", - "@message": "Flattening with SetNull", + "@message": "Flattening with SetValue (empty for nil)", "@module": "provider.autoflex", "autoflex.source.path": "Field4", "autoflex.source.type": "[]*string", diff --git a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_enum_key.golden b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_enum_key.golden index 8dbfbb2f169f..c8350b9da701 100644 --- a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_enum_key.golden +++ b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_enum_key.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "MapBlock", "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "MapBlock", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfMapBlockElementEnumKey]", + "autoflex.target.path": "MapBlock", + "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement", + "target_kind": "map", + "target_type": "map[string]flex.awsMapBlockElement", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Skipping map block key", diff --git a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_list.golden b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_list.golden index e54e86d0bc74..8c76eaa65624 100644 --- a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_list.golden +++ b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_list.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "MapBlock", "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "MapBlock", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfMapBlockElement]", + "autoflex.target.path": "MapBlock", + "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement", + "target_kind": "map", + "target_type": "map[string]flex.awsMapBlockElement", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Skipping map block key", diff --git a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_ptr_both.golden b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_ptr_both.golden index 6a02d02a92e1..fa991398652e 100644 --- a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_ptr_both.golden +++ b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_ptr_both.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "MapBlock", "autoflex.target.type": "map[string]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "MapBlock", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfMapBlockElement]", + "autoflex.target.path": "MapBlock", + "autoflex.target.type": "map[string]*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement", + "target_kind": "map", + "target_type": "map[string]*flex.awsMapBlockElement", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Skipping map block key", diff --git a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_ptr_source.golden b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_ptr_source.golden index e54e86d0bc74..8c76eaa65624 100644 --- a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_ptr_source.golden +++ b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_ptr_source.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "MapBlock", "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "MapBlock", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfMapBlockElement]", + "autoflex.target.path": "MapBlock", + "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement", + "target_kind": "map", + "target_type": "map[string]flex.awsMapBlockElement", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Skipping map block key", diff --git a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_set.golden b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_set.golden index 3daa5017e71c..9445e6f458bd 100644 --- a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_set.golden +++ b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_key_set.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "MapBlock", "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "MapBlock", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfMapBlockElement]", + "autoflex.target.path": "MapBlock", + "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement", + "target_kind": "map", + "target_type": "map[string]flex.awsMapBlockElement", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Skipping map block key", diff --git a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_list_no_key.golden b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_list_no_key.golden index 03ea0705e150..39aaab0aa5f2 100644 --- a/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_list_no_key.golden +++ b/internal/framework/flex/testdata/autoflex/maps/expand_map_block/map_block_list_no_key.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "MapBlock", "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "MapBlock", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfMapBlockElementNoKey]", + "autoflex.target.path": "MapBlock", + "autoflex.target.type": "map[string]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapBlockElement", + "target_kind": "map", + "target_type": "map[string]flex.awsMapBlockElement", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "error", "@message": "Source has no map block key", diff --git a/internal/framework/flex/testdata/autoflex/maps/expand_maps/nested_string_map.golden b/internal/framework/flex/testdata/autoflex/maps/expand_maps/nested_string_map.golden index 49685f4aaec1..dd5f11fbda0b 100644 --- a/internal/framework/flex/testdata/autoflex/maps/expand_maps/nested_string_map.golden +++ b/internal/framework/flex/testdata/autoflex/maps/expand_maps/nested_string_map.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "FieldOuter", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapOfString" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "FieldOuter", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfMapOfString]", + "autoflex.target.path": "FieldOuter", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsMapOfString", + "target_kind": "struct", + "target_type": "flex.awsMapOfString", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Matched fields", diff --git a/internal/framework/flex/testdata/autoflex/nested/expand_nested_complex/complex_source_and_complex_target.golden b/internal/framework/flex/testdata/autoflex/nested/expand_nested_complex/complex_source_and_complex_target.golden index e990ead30a89..88f8bccd5ae9 100644 --- a/internal/framework/flex/testdata/autoflex/nested/expand_nested_complex/complex_source_and_complex_target.golden +++ b/internal/framework/flex/testdata/autoflex/nested/expand_nested_complex/complex_source_and_complex_target.golden @@ -55,6 +55,19 @@ "autoflex.target.path": "Field2", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsNestedObjectPointer" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field2", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfListOfNestedObject]", + "autoflex.target.path": "Field2", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsNestedObjectPointer", + "target_kind": "ptr", + "target_type": "*flex.awsNestedObjectPointer", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Matched fields", @@ -75,6 +88,19 @@ "autoflex.target.path": "Field2.Field1", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsSingleStringValue" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field2[0].Field1", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfSingleStringField]", + "autoflex.target.path": "Field2.Field1", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsSingleStringValue", + "target_kind": "ptr", + "target_type": "*flex.awsSingleStringValue", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Matched fields", @@ -145,6 +171,19 @@ "autoflex.target.path": "Field4", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsSingleInt64Value" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "Field4", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfSingleInt64Field]", + "autoflex.target.path": "Field4", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsSingleInt64Value", + "target_kind": "slice", + "target_type": "[]flex.awsSingleInt64Value", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/nested/flatten_nested_complex/complex_source_and_complex_target.golden b/internal/framework/flex/testdata/autoflex/nested/flatten_nested_complex/complex_source_and_complex_target.golden index 614f7ae03292..8490bab546e4 100644 --- a/internal/framework/flex/testdata/autoflex/nested/flatten_nested_complex/complex_source_and_complex_target.golden +++ b/internal/framework/flex/testdata/autoflex/nested/flatten_nested_complex/complex_source_and_complex_target.golden @@ -153,7 +153,32 @@ "autoflex.source.size": 3, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsSingleInt64Value", "autoflex.target.path": "Field4", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfSingleInt64Field]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfSingleInt64Field]", + "source_type": "[]flex.awsSingleInt64Value", + "target_type": "SetNestedObjectTypeOf[flex.tfSingleInt64Field]" + }, + { + "@level": "debug", + "@message": "DEBUG: First element of nested object collection", + "@module": "provider.autoflex", + "autoflex.source.path": "Field4", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsSingleInt64Value", + "autoflex.target.path": "Field4", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfSingleInt64Field]", + "element_type": "flex.awsSingleInt64Value" + }, + { + "@level": "debug", + "@message": "DEBUG: Struct field", + "@module": "provider.autoflex", + "autoflex.source.path": "Field4", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsSingleInt64Value", + "autoflex.target.path": "Field4", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfSingleInt64Field]", + "field_name": "Field1", + "field_type": "int64", + "is_nil": false, + "is_zero": false }, { "@level": "trace", diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/expand_no_xmlwrapper/valid_function_associations.golden b/internal/framework/flex/testdata/autoflex/xml_compat/expand_no_xmlwrapper/valid_function_associations.golden index 2b6bca7ff6b6..3b0afeb9856b 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/expand_no_xmlwrapper/valid_function_associations.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/expand_no_xmlwrapper/valid_function_associations.golden @@ -35,6 +35,19 @@ "autoflex.target.path": "FunctionAssociations", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "FunctionAssociations", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationsTF]", + "autoflex.target.path": "FunctionAssociations", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", + "target_kind": "ptr", + "target_type": "*flex.FunctionAssociations", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Matched fields", @@ -55,6 +68,19 @@ "autoflex.target.path": "FunctionAssociations.Items", "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "FunctionAssociations[0].Items", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "autoflex.target.path": "FunctionAssociations.Items", + "autoflex.target.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", + "target_kind": "slice", + "target_type": "[]flex.FunctionAssociation", + "xmlWrapper": false, + "xmlWrapperField": "" + }, { "@level": "trace", "@message": "Expanding nested object collection", diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/empty_function_associations.golden b/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/empty_function_associations.golden index 5c51df3bbd46..4def77b7d0af 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/empty_function_associations.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/empty_function_associations.golden @@ -15,6 +15,17 @@ "autoflex.target.path": "", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigAWS" }, + { + "@level": "trace", + "@message": "TRACE: Found xmlwrapper tag", + "@module": "provider.autoflex", + "autoflex.source.fieldname": "FunctionAssociations", + "autoflex.source.path": "", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", + "autoflex.target.path": "", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigAWS", + "xmlWrapperField": "items" + }, { "@level": "trace", "@message": "Matched fields", @@ -35,6 +46,19 @@ "autoflex.target.path": "FunctionAssociations", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations" }, + { + "@level": "trace", + "@message": "TRACE: nestedObjectCollection entry", + "@module": "provider.autoflex", + "autoflex.source.path": "FunctionAssociations", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "autoflex.target.path": "FunctionAssociations", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", + "target_kind": "ptr", + "target_type": "*flex.FunctionAssociations", + "xmlWrapper": true, + "xmlWrapperField": "items" + }, { "@level": "trace", "@message": "Expanding NestedObjectCollection to XML wrapper", @@ -48,23 +72,31 @@ }, { "@level": "trace", - "@message": "Converting nested objects to items", + "@message": "Expanding NestedObjectCollection to XML wrapper", "@module": "provider.autoflex", "autoflex.source.path": "FunctionAssociations", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", "autoflex.target.path": "FunctionAssociations", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "items_count": 0, - "items_type": "[]flex.FunctionAssociation" + "source_type": "SetNestedObjectTypeOf[flex.FunctionAssociationTF]", + "target_type": "flex.FunctionAssociations" }, { "@level": "trace", - "@message": "Successfully expanded NestedObjectCollection to XML wrapper", + "@message": "TRACE: Using Rule 1 - direct collection to XML wrapper", "@module": "provider.autoflex", "autoflex.source.path": "FunctionAssociations", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", "autoflex.target.path": "FunctionAssociations", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "items_count": 0 + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations" + }, + { + "@level": "trace", + "@message": "Expanding Rule 1 XML wrapper (direct collection)", + "@module": "provider.autoflex", + "autoflex.source.path": "FunctionAssociations", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "autoflex.target.path": "FunctionAssociations", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/single_function_association.golden b/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/single_function_association.golden index 914f24645a32..cf8b18cdce3c 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/single_function_association.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/single_function_association.golden @@ -15,6 +15,17 @@ "autoflex.target.path": "", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigAWS" }, + { + "@level": "trace", + "@message": "TRACE: Found xmlwrapper tag", + "@module": "provider.autoflex", + "autoflex.source.fieldname": "FunctionAssociations", + "autoflex.source.path": "", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", + "autoflex.target.path": "", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigAWS", + "xmlWrapperField": "items" + }, { "@level": "trace", "@message": "Matched fields", @@ -37,83 +48,66 @@ }, { "@level": "trace", - "@message": "Expanding NestedObjectCollection to XML wrapper", + "@message": "TRACE: nestedObjectCollection entry", "@module": "provider.autoflex", "autoflex.source.path": "FunctionAssociations", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", "autoflex.target.path": "FunctionAssociations", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "source_type": "SetNestedObjectTypeOf[flex.FunctionAssociationTF]", - "target_type": "flex.FunctionAssociations" + "target_kind": "ptr", + "target_type": "*flex.FunctionAssociations", + "xmlWrapper": true, + "xmlWrapperField": "items" }, { "@level": "trace", - "@message": "Converting nested objects to items", + "@message": "Expanding NestedObjectCollection to XML wrapper", "@module": "provider.autoflex", "autoflex.source.path": "FunctionAssociations", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", "autoflex.target.path": "FunctionAssociations", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "items_count": 1, - "items_type": "[]flex.FunctionAssociation" - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" + "source_type": "SetNestedObjectTypeOf[flex.FunctionAssociationTF]", + "target_type": "flex.FunctionAssociations" }, { "@level": "trace", - "@message": "Matched fields", - "@module": "provider.autoflex", - "autoflex.source.fieldname": "EventType", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF", - "autoflex.target.fieldname": "EventType", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" - }, - { - "@level": "info", - "@message": "Converting", + "@message": "Expanding NestedObjectCollection to XML wrapper", "@module": "provider.autoflex", - "autoflex.source.path": "EventType", - "autoflex.source.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue", - "autoflex.target.path": "EventType", - "autoflex.target.type": "string" + "autoflex.source.path": "FunctionAssociations", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "autoflex.target.path": "FunctionAssociations", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", + "source_type": "SetNestedObjectTypeOf[flex.FunctionAssociationTF]", + "target_type": "flex.FunctionAssociations" }, { "@level": "trace", - "@message": "Matched fields", + "@message": "TRACE: Checking for Rule 2 pattern", "@module": "provider.autoflex", - "autoflex.source.fieldname": "FunctionARN", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF", - "autoflex.target.fieldname": "FunctionARN", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" + "autoflex.source.path": "FunctionAssociations", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "autoflex.target.path": "FunctionAssociations", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", + "fromSlice_len": 1, + "nested_kind": "ptr" }, { - "@level": "info", - "@message": "Converting", + "@level": "trace", + "@message": "TRACE: Using Rule 1 - direct collection to XML wrapper", "@module": "provider.autoflex", - "autoflex.source.path": "FunctionARN", - "autoflex.source.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue", - "autoflex.target.path": "FunctionARN", - "autoflex.target.type": "*string" + "autoflex.source.path": "FunctionAssociations", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "autoflex.target.path": "FunctionAssociations", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations" }, { "@level": "trace", - "@message": "Successfully expanded NestedObjectCollection to XML wrapper", + "@message": "Expanding Rule 1 XML wrapper (direct collection)", "@module": "provider.autoflex", "autoflex.source.path": "FunctionAssociations", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", "autoflex.target.path": "FunctionAssociations", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "items_count": 1 + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/valid_function_associations.golden b/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/valid_function_associations.golden index 95d26e0d9b45..4def77b7d0af 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/valid_function_associations.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper/valid_function_associations.golden @@ -15,6 +15,17 @@ "autoflex.target.path": "", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigAWS" }, + { + "@level": "trace", + "@message": "TRACE: Found xmlwrapper tag", + "@module": "provider.autoflex", + "autoflex.source.fieldname": "FunctionAssociations", + "autoflex.source.path": "", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", + "autoflex.target.path": "", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigAWS", + "xmlWrapperField": "items" + }, { "@level": "trace", "@message": "Matched fields", @@ -37,132 +48,55 @@ }, { "@level": "trace", - "@message": "Expanding NestedObjectCollection to XML wrapper", + "@message": "TRACE: nestedObjectCollection entry", "@module": "provider.autoflex", "autoflex.source.path": "FunctionAssociations", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", "autoflex.target.path": "FunctionAssociations", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "source_type": "SetNestedObjectTypeOf[flex.FunctionAssociationTF]", - "target_type": "flex.FunctionAssociations" + "target_kind": "ptr", + "target_type": "*flex.FunctionAssociations", + "xmlWrapper": true, + "xmlWrapperField": "items" }, { "@level": "trace", - "@message": "Converting nested objects to items", + "@message": "Expanding NestedObjectCollection to XML wrapper", "@module": "provider.autoflex", "autoflex.source.path": "FunctionAssociations", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", "autoflex.target.path": "FunctionAssociations", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "items_count": 2, - "items_type": "[]flex.FunctionAssociation" - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" - }, - { - "@level": "trace", - "@message": "Matched fields", - "@module": "provider.autoflex", - "autoflex.source.fieldname": "EventType", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF", - "autoflex.target.fieldname": "EventType", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "EventType", - "autoflex.source.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue", - "autoflex.target.path": "EventType", - "autoflex.target.type": "string" - }, - { - "@level": "trace", - "@message": "Matched fields", - "@module": "provider.autoflex", - "autoflex.source.fieldname": "FunctionARN", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF", - "autoflex.target.fieldname": "FunctionARN", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "FunctionARN", - "autoflex.source.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue", - "autoflex.target.path": "FunctionARN", - "autoflex.target.type": "*string" - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" + "source_type": "SetNestedObjectTypeOf[flex.FunctionAssociationTF]", + "target_type": "flex.FunctionAssociations" }, { "@level": "trace", - "@message": "Matched fields", - "@module": "provider.autoflex", - "autoflex.source.fieldname": "EventType", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF", - "autoflex.target.fieldname": "EventType", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" - }, - { - "@level": "info", - "@message": "Converting", + "@message": "Expanding NestedObjectCollection to XML wrapper", "@module": "provider.autoflex", - "autoflex.source.path": "EventType", - "autoflex.source.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue", - "autoflex.target.path": "EventType", - "autoflex.target.type": "string" + "autoflex.source.path": "FunctionAssociations", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "autoflex.target.path": "FunctionAssociations", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", + "source_type": "SetNestedObjectTypeOf[flex.FunctionAssociationTF]", + "target_type": "flex.FunctionAssociations" }, { "@level": "trace", - "@message": "Matched fields", - "@module": "provider.autoflex", - "autoflex.source.fieldname": "FunctionARN", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF", - "autoflex.target.fieldname": "FunctionARN", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation" - }, - { - "@level": "info", - "@message": "Converting", + "@message": "TRACE: Using Rule 1 - direct collection to XML wrapper", "@module": "provider.autoflex", - "autoflex.source.path": "FunctionARN", - "autoflex.source.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue", - "autoflex.target.path": "FunctionARN", - "autoflex.target.type": "*string" + "autoflex.source.path": "FunctionAssociations", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "autoflex.target.path": "FunctionAssociations", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations" }, { "@level": "trace", - "@message": "Successfully expanded NestedObjectCollection to XML wrapper", + "@message": "Expanding Rule 1 XML wrapper (direct collection)", "@module": "provider.autoflex", "autoflex.source.path": "FunctionAssociations", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", "autoflex.target.path": "FunctionAssociations", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "items_count": 2 + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper_direct/direct_xml_wrapper.golden b/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper_direct/direct_xml_wrapper.golden index feac7ec774d3..2d7b7c842741 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper_direct/direct_xml_wrapper.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/expand_xmlwrapper_direct/direct_xml_wrapper.golden @@ -15,6 +15,17 @@ "autoflex.target.path": "", "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DirectWrapperAWS" }, + { + "@level": "trace", + "@message": "TRACE: Found xmlwrapper tag", + "@module": "provider.autoflex", + "autoflex.source.fieldname": "Items", + "autoflex.source.path": "", + "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DirectWrapperTF", + "autoflex.target.path": "", + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DirectWrapperAWS", + "xmlWrapperField": "items" + }, { "@level": "trace", "@message": "Matched fields", @@ -34,18 +45,5 @@ "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetValueOf[github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue]", "autoflex.target.path": "Items", "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DirectXMLWrapper" - }, - { - "@level": "trace", - "@message": "Successfully expanded to XML wrapper", - "@module": "provider.autoflex", - "autoflex.source.path": "Items", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.SetValueOf[github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue]", - "autoflex.target.path": "Items", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DirectXMLWrapper", - "items_count": 2, - "source_type": "basetypes.SetValue", - "target_type": "flex.DirectXMLWrapper", - "wrapper_field": "Items" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_no_xmlwrapper/complex_type__function_associations.golden b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_no_xmlwrapper/complex_type__function_associations.golden index 15b240a7e704..c6b0205fbb7f 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_no_xmlwrapper/complex_type__function_associations.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_no_xmlwrapper/complex_type__function_associations.golden @@ -63,7 +63,45 @@ "autoflex.source.size": 2, "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", "autoflex.target.path": "FunctionAssociations.Items", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]" + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "source_type": "[]flex.FunctionAssociation", + "target_type": "ListNestedObjectTypeOf[flex.FunctionAssociationTF]" + }, + { + "@level": "debug", + "@message": "DEBUG: First element of nested object collection", + "@module": "provider.autoflex", + "autoflex.source.path": "FunctionAssociations.Items", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", + "autoflex.target.path": "FunctionAssociations.Items", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "element_type": "flex.FunctionAssociation" + }, + { + "@level": "debug", + "@message": "DEBUG: Struct field", + "@module": "provider.autoflex", + "autoflex.source.path": "FunctionAssociations.Items", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", + "autoflex.target.path": "FunctionAssociations.Items", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "field_name": "EventType", + "field_type": "string", + "is_nil": false, + "is_zero": false + }, + { + "@level": "debug", + "@message": "DEBUG: Struct field", + "@module": "provider.autoflex", + "autoflex.source.path": "FunctionAssociations.Items", + "autoflex.source.type": "[]github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", + "autoflex.target.path": "FunctionAssociations.Items", + "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ListNestedObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]", + "field_name": "FunctionARN", + "field_type": "*string", + "is_nil": false, + "is_zero": false }, { "@level": "trace", diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/complex_type__function_associations.golden b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/complex_type__function_associations.golden index e481e95568ce..7a5b852acc13 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/complex_type__function_associations.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/complex_type__function_associations.golden @@ -16,198 +16,23 @@ "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF" }, { - "@level": "trace", - "@message": "Converting entire XML wrapper struct to collection field", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "flex.FunctionAssociations", - "autoflex.target.fieldname": "FunctionAssociations", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", - "wrapper_field": "items" - }, - { - "@level": "trace", - "@message": "Starting XML wrapper flatten", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", - "source_type": "flex.FunctionAssociations", - "target_type": "SetNestedObjectTypeOf[flex.FunctionAssociationTF]", - "wrapper_field": "items" - }, - { - "@level": "trace", - "@message": "Found Items field", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", - "items_is_nil": false, - "items_kind": "slice", - "items_len": 2, - "items_type": "[]flex.FunctionAssociation" - }, - { - "@level": "trace", - "@message": "Using target element type", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", - "element_type": "ObjectTypeOf[flex.FunctionAssociationTF]" - }, - { - "@level": "trace", - "@message": "Converting items to set elements", + "@level": "debug", + "@message": "No corresponding field", "@module": "provider.autoflex", + "autoflex.source.fieldname": "Items", "autoflex.source.path": "", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", - "items_count": 2 - }, - { - "@level": "trace", - "@message": "Processing item", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", - "index": 0, - "item_kind": "struct", - "item_value": { - "EventType": "viewer-request", - "FunctionARN": "arn:aws:cloudfront::123456789012:function/example-function" - } - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", - "autoflex.target.path": "", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]" - }, - { - "@level": "trace", - "@message": "Matched fields", - "@module": "provider.autoflex", - "autoflex.source.fieldname": "EventType", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", - "autoflex.target.fieldname": "EventType", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF" - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "EventType", - "autoflex.source.type": "string", - "autoflex.target.path": "EventType", - "autoflex.target.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue" - }, - { - "@level": "trace", - "@message": "Matched fields", - "@module": "provider.autoflex", - "autoflex.source.fieldname": "FunctionARN", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", - "autoflex.target.fieldname": "FunctionARN", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF" - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "FunctionARN", - "autoflex.source.type": "*string", - "autoflex.target.path": "FunctionARN", - "autoflex.target.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue" - }, - { - "@level": "trace", - "@message": "Processing item", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", - "index": 1, - "item_kind": "struct", - "item_value": { - "EventType": "viewer-response", - "FunctionARN": "arn:aws:cloudfront::123456789012:function/another-function" - } - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", - "autoflex.target.path": "", - "autoflex.target.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/types.ObjectValueOf[github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF]" - }, - { - "@level": "trace", - "@message": "Matched fields", - "@module": "provider.autoflex", - "autoflex.source.fieldname": "EventType", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", - "autoflex.target.fieldname": "EventType", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF" - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "EventType", - "autoflex.source.type": "string", - "autoflex.target.path": "EventType", - "autoflex.target.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue" - }, - { - "@level": "trace", - "@message": "Matched fields", - "@module": "provider.autoflex", - "autoflex.source.fieldname": "FunctionARN", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociation", - "autoflex.target.fieldname": "FunctionARN", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociationTF" - }, - { - "@level": "info", - "@message": "Converting", - "@module": "provider.autoflex", - "autoflex.source.path": "FunctionARN", - "autoflex.source.type": "*string", - "autoflex.target.path": "FunctionARN", - "autoflex.target.type": "github.com/hashicorp/terraform-plugin-framework/types/basetypes.StringValue" + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF" }, { - "@level": "trace", - "@message": "Creating set value", + "@level": "debug", + "@message": "No corresponding field", "@module": "provider.autoflex", + "autoflex.source.fieldname": "Quantity", "autoflex.source.path": "", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.FunctionAssociations", "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF", - "element_count": 2, - "element_type": "ObjectTypeOf[flex.FunctionAssociationTF]" + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.DistributionConfigTF" } -] +] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/empty_slice_to_null_set.golden b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/empty_slice_to_null_set.golden index 9a5732a1645f..889c40cecae1 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/empty_slice_to_null_set.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/empty_slice_to_null_set.golden @@ -16,55 +16,20 @@ "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten" }, { - "@level": "trace", - "@message": "Converting entire XML wrapper struct to collection field", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "flex.awsStatusCodesForFlatten", - "autoflex.target.fieldname": "StatusCodes", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "wrapper_field": "items" - }, - { - "@level": "trace", - "@message": "Starting XML wrapper flatten", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "source_type": "flex.awsStatusCodesForFlatten", - "target_type": "SetTypeOf[basetypes.Int64Value]", - "wrapper_field": "items" - }, - { - "@level": "trace", - "@message": "Found Items field", + "@level": "debug", + "@message": "No corresponding field", "@module": "provider.autoflex", + "autoflex.source.fieldname": "Items", "autoflex.source.path": "", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "items_is_nil": true, - "items_kind": "slice", - "items_len": 0, - "items_type": "[]int32" - }, - { - "@level": "trace", - "@message": "Using target element type", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "element_type": "basetypes.Int64Type" + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten" }, { - "@level": "trace", - "@message": "Flattening XML wrapper with SetNull", + "@level": "debug", + "@message": "No corresponding field", "@module": "provider.autoflex", + "autoflex.source.fieldname": "Quantity", "autoflex.source.path": "", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", "autoflex.target.path": "", diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/int32_slice_to_set.golden b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/int32_slice_to_set.golden index 2fdb6ce9c50c..889c40cecae1 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/int32_slice_to_set.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/int32_slice_to_set.golden @@ -16,94 +16,23 @@ "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten" }, { - "@level": "trace", - "@message": "Converting entire XML wrapper struct to collection field", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "flex.awsStatusCodesForFlatten", - "autoflex.target.fieldname": "StatusCodes", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "wrapper_field": "items" - }, - { - "@level": "trace", - "@message": "Starting XML wrapper flatten", + "@level": "debug", + "@message": "No corresponding field", "@module": "provider.autoflex", + "autoflex.source.fieldname": "Items", "autoflex.source.path": "", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "source_type": "flex.awsStatusCodesForFlatten", - "target_type": "SetTypeOf[basetypes.Int64Value]", - "wrapper_field": "items" - }, - { - "@level": "trace", - "@message": "Found Items field", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "items_is_nil": false, - "items_kind": "slice", - "items_len": 2, - "items_type": "[]int32" - }, - { - "@level": "trace", - "@message": "Using target element type", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "element_type": "basetypes.Int64Type" - }, - { - "@level": "trace", - "@message": "Converting items to set elements", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "items_count": 2 - }, - { - "@level": "trace", - "@message": "Processing item", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "index": 0, - "item_kind": "int32", - "item_value": 400 - }, - { - "@level": "trace", - "@message": "Processing item", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "index": 1, - "item_kind": "int32", - "item_value": 404 + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten" }, { - "@level": "trace", - "@message": "Creating set value", + "@level": "debug", + "@message": "No corresponding field", "@module": "provider.autoflex", + "autoflex.source.fieldname": "Quantity", "autoflex.source.path": "", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsStatusCodesForFlatten", "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten", - "element_count": 2, - "element_type": "basetypes.Int64Type" + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfStatusCodesModelForFlatten" } ] \ No newline at end of file diff --git a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/string_slice_to_list.golden b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/string_slice_to_list.golden index a1b7a404ca0b..abe89ec89bbd 100644 --- a/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/string_slice_to_list.golden +++ b/internal/framework/flex/testdata/autoflex/xml_compat/flatten_xmlwrapper/string_slice_to_list.golden @@ -16,100 +16,20 @@ "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten" }, { - "@level": "trace", - "@message": "Converting entire XML wrapper struct to collection field", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "flex.awsHeadersForFlatten", - "autoflex.target.fieldname": "Headers", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten", - "wrapper_field": "items" - }, - { - "@level": "trace", - "@message": "Starting XML wrapper flatten", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsHeadersForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten", - "source_type": "flex.awsHeadersForFlatten", - "target_type": "ListTypeOf[basetypes.StringValue]", - "wrapper_field": "items" - }, - { - "@level": "trace", - "@message": "Found Items field", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsHeadersForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten", - "items_is_nil": false, - "items_kind": "slice", - "items_len": 2, - "items_type": "[]string" - }, - { - "@level": "trace", - "@message": "Using target element type", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsHeadersForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten", - "element_type": "basetypes.StringType" - }, - { - "@level": "trace", - "@message": "Converting items to list elements", + "@level": "debug", + "@message": "No corresponding field", "@module": "provider.autoflex", + "autoflex.source.fieldname": "Items", "autoflex.source.path": "", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsHeadersForFlatten", "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten", - "items_count": 2 - }, - { - "@level": "trace", - "@message": "Processing item", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsHeadersForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten", - "index": 0, - "item_kind": "string", - "item_value": "accept" - }, - { - "@level": "trace", - "@message": "Processing item", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsHeadersForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten", - "index": 1, - "item_kind": "string", - "item_value": "content-type" - }, - { - "@level": "trace", - "@message": "Creating list value", - "@module": "provider.autoflex", - "autoflex.source.path": "", - "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsHeadersForFlatten", - "autoflex.target.path": "", - "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten", - "element_count": 2, - "element_type": "basetypes.StringType" + "autoflex.target.type": "*github.com/hashicorp/terraform-provider-aws/internal/framework/flex.tfHeadersModelForFlatten" }, { - "@level": "trace", - "@message": "Setting target list value", + "@level": "debug", + "@message": "No corresponding field", "@module": "provider.autoflex", + "autoflex.source.fieldname": "Quantity", "autoflex.source.path": "", "autoflex.source.type": "github.com/hashicorp/terraform-provider-aws/internal/framework/flex.awsHeadersForFlatten", "autoflex.target.path": "",