Skip to content

Commit a200ba9

Browse files
authored
Merge pull request #43738 from marcinbelczewski/b-autoflex-fields-matchin-never-reaches-suffix-check
fix: avoid short-circuiting suffix match when prefix option is set in findFieldFuzzy
2 parents e9eb536 + 5989321 commit a200ba9

File tree

4 files changed

+113
-16
lines changed

4 files changed

+113
-16
lines changed

internal/framework/flex/auto_expand.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,7 @@ func expandStruct(ctx context.Context, sourcePath path.Path, from any, targetPat
11651165
fromFieldName := fromField.Name
11661166
_, fromFieldOpts := autoflexTags(fromField)
11671167

1168-
toField, ok := findFieldFuzzy(ctx, fromFieldName, typeFrom, typeTo, flexer)
1168+
toField, ok := (&fuzzyFieldFinder{}).findField(ctx, fromFieldName, typeFrom, typeTo, flexer)
11691169
if !ok {
11701170
// Corresponding field not found in to.
11711171
tflog.SubsystemDebug(ctx, subsystemName, "No corresponding field", map[string]any{

internal/framework/flex/auto_expand_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5958,3 +5958,88 @@ func runAutoExpandTestCases(t *testing.T, testCases autoFlexTestCases) {
59585958
})
59595959
}
59605960
}
5961+
5962+
func TestFindFieldFuzzy_Combinations(t *testing.T) {
5963+
t.Parallel()
5964+
5965+
type builder func() (typeFrom reflect.Type, typeTo reflect.Type, fieldNameFrom string, expectedFieldName string)
5966+
5967+
cases := map[string]struct {
5968+
prefix string
5969+
suffix string
5970+
build builder
5971+
}{
5972+
// 1) suffix-only on target; source has neither
5973+
"suffix on target only (prefix configured but not applied)": {
5974+
prefix: "Cluster",
5975+
suffix: "Input",
5976+
build: func() (reflect.Type, reflect.Type, string, string) {
5977+
type source struct{ ExecutionConfig string }
5978+
type target struct{ ExecutionConfigInput string }
5979+
return reflect.TypeFor[source](), reflect.TypeFor[target](), "ExecutionConfig", "ExecutionConfigInput"
5980+
},
5981+
},
5982+
// 2) trim prefix on source, then add suffix
5983+
"trim prefix on source then add suffix": {
5984+
prefix: "Cluster",
5985+
suffix: "Input",
5986+
build: func() (reflect.Type, reflect.Type, string, string) {
5987+
type source struct{ ClusterExecutionConfig string }
5988+
type target struct{ ExecutionConfigInput string }
5989+
return reflect.TypeFor[source](), reflect.TypeFor[target](), "ClusterExecutionConfig", "ExecutionConfigInput"
5990+
},
5991+
},
5992+
// 3) add prefix and suffix on target (source has neither)
5993+
"add prefix and suffix on target": {
5994+
prefix: "Cluster",
5995+
suffix: "Input",
5996+
build: func() (reflect.Type, reflect.Type, string, string) {
5997+
type source struct{ ExecutionConfig string }
5998+
type target struct{ ClusterExecutionConfigInput string }
5999+
return reflect.TypeFor[source](), reflect.TypeFor[target](), "ExecutionConfig", "ClusterExecutionConfigInput"
6000+
},
6001+
},
6002+
// 4) trim suffix on source (target has neither)
6003+
"trim suffix on source": {
6004+
prefix: "Cluster",
6005+
suffix: "Input",
6006+
build: func() (reflect.Type, reflect.Type, string, string) {
6007+
type source struct{ ExecutionConfigInput string }
6008+
type target struct{ ExecutionConfig string }
6009+
return reflect.TypeFor[source](), reflect.TypeFor[target](), "ExecutionConfigInput", "ExecutionConfig"
6010+
},
6011+
},
6012+
// 5) trim both on source (target has neither)
6013+
"trim both prefix and suffix on source": {
6014+
prefix: "Cluster",
6015+
suffix: "Input",
6016+
build: func() (reflect.Type, reflect.Type, string, string) {
6017+
type source struct{ ClusterExecutionConfigInput string }
6018+
type target struct{ ExecutionConfig string }
6019+
return reflect.TypeFor[source](), reflect.TypeFor[target](), "ClusterExecutionConfigInput", "ExecutionConfig"
6020+
},
6021+
},
6022+
}
6023+
6024+
for name, tc := range cases {
6025+
t.Run(name, func(t *testing.T) {
6026+
t.Parallel()
6027+
6028+
typeFrom, typeTo, fieldNameFrom, expected := tc.build()
6029+
ctx := context.Background()
6030+
opts := []AutoFlexOptionsFunc{
6031+
WithFieldNamePrefix(tc.prefix),
6032+
WithFieldNameSuffix(tc.suffix),
6033+
}
6034+
flexer := newAutoExpander(opts)
6035+
6036+
field, found := (&fuzzyFieldFinder{}).findField(ctx, fieldNameFrom, typeFrom, typeTo, flexer)
6037+
if !found {
6038+
t.Fatalf("expected to find field, but found==false")
6039+
}
6040+
if field.Name != expected {
6041+
t.Fatalf("expected field name %q, got %q", expected, field.Name)
6042+
}
6043+
})
6044+
}
6045+
}

internal/framework/flex/auto_flatten.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1512,7 +1512,7 @@ func flattenStruct(ctx context.Context, sourcePath path.Path, from any, targetPa
15121512
for fromField := range flattenSourceFields(ctx, typeFrom, flexer.getOptions()) {
15131513
fromFieldName := fromField.Name
15141514

1515-
toField, ok := findFieldFuzzy(ctx, fromFieldName, typeFrom, typeTo, flexer)
1515+
toField, ok := (&fuzzyFieldFinder{}).findField(ctx, fromFieldName, typeFrom, typeTo, flexer)
15161516
if !ok {
15171517
// Corresponding field not found in to.
15181518
tflog.SubsystemDebug(ctx, subsystemName, "No corresponding field", map[string]any{

internal/framework/flex/autoflex.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,7 @@ import (
1717
tfreflect "github.com/hashicorp/terraform-provider-aws/internal/reflect"
1818
)
1919

20-
type fieldNamePrefixCtxKey string
21-
2220
const (
23-
fieldNamePrefixRecurse fieldNamePrefixCtxKey = "FIELD_NAME_PREFIX_RECURSE"
24-
fieldNameSuffixRecurse fieldNamePrefixCtxKey = "FIELD_NAME_SUFFIX_RECURSE"
25-
2621
mapBlockKeyFieldName = "MapBlockKey"
2722
)
2823

@@ -74,7 +69,12 @@ var (
7469
plural = pluralize.NewClient()
7570
)
7671

77-
func findFieldFuzzy(ctx context.Context, fieldNameFrom string, typeFrom reflect.Type, typeTo reflect.Type, flexer autoFlexer) (reflect.StructField, bool) {
72+
type fuzzyFieldFinder struct {
73+
prefixRecursionDepth int
74+
suffixRecursionDepth int
75+
}
76+
77+
func (fff *fuzzyFieldFinder) findField(ctx context.Context, fieldNameFrom string, typeFrom reflect.Type, typeTo reflect.Type, flexer autoFlexer) (reflect.StructField, bool) { //nolint:unparam
7878
// first precedence is exact match (case sensitive)
7979
if fieldTo, ok := typeTo.FieldByName(fieldNameFrom); ok {
8080
return fieldTo, true
@@ -117,26 +117,38 @@ func findFieldFuzzy(ctx context.Context, fieldNameFrom string, typeFrom reflect.
117117
// fourth precedence is using field name prefix
118118
if v := opts.fieldNamePrefix; v != "" {
119119
v = strings.ReplaceAll(v, " ", "")
120-
if ctx.Value(fieldNamePrefixRecurse) == nil {
120+
if fff.prefixRecursionDepth == 0 {
121121
// so it will only recurse once
122-
ctx = context.WithValue(ctx, fieldNamePrefixRecurse, true)
122+
fff.prefixRecursionDepth++
123123
if trimmed, ok := strings.CutPrefix(fieldNameFrom, v); ok {
124-
return findFieldFuzzy(ctx, trimmed, typeFrom, typeTo, flexer)
124+
if fieldTo, ok := fff.findField(ctx, trimmed, typeFrom, typeTo, flexer); ok {
125+
fff.prefixRecursionDepth--
126+
return fieldTo, true
127+
}
128+
} else {
129+
if fieldTo, ok := fff.findField(ctx, v+fieldNameFrom, typeFrom, typeTo, flexer); ok {
130+
fff.prefixRecursionDepth--
131+
return fieldTo, true
132+
}
125133
}
126-
return findFieldFuzzy(ctx, v+fieldNameFrom, typeFrom, typeTo, flexer)
134+
// no match via prefix mutation; fall through to suffix handling on the original name
127135
}
128136
}
129137

130138
// fifth precedence is using field name suffix
131139
if v := opts.fieldNameSuffix; v != "" {
132140
v = strings.ReplaceAll(v, " ", "")
133-
if ctx.Value(fieldNameSuffixRecurse) == nil {
141+
if fff.suffixRecursionDepth == 0 {
134142
// so it will only recurse once
135-
ctx = context.WithValue(ctx, fieldNameSuffixRecurse, true)
143+
fff.suffixRecursionDepth++
136144
if strings.HasSuffix(fieldNameFrom, v) {
137-
return findFieldFuzzy(ctx, strings.TrimSuffix(fieldNameFrom, v), typeFrom, typeTo, flexer)
145+
fieldTo, ok := fff.findField(ctx, strings.TrimSuffix(fieldNameFrom, v), typeFrom, typeTo, flexer)
146+
fff.suffixRecursionDepth--
147+
return fieldTo, ok
138148
}
139-
return findFieldFuzzy(ctx, fieldNameFrom+v, typeFrom, typeTo, flexer)
149+
fieldTo, ok := fff.findField(ctx, fieldNameFrom+v, typeFrom, typeTo, flexer)
150+
fff.suffixRecursionDepth--
151+
return fieldTo, ok
140152
}
141153
}
142154

0 commit comments

Comments
 (0)