Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions libs/structs/structdiff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,14 @@ func getForceSendFields(v reflect.Value) []string {
if !v.IsValid() || v.Kind() != reflect.Struct {
return nil
}
fsField := v.FieldByName("ForceSendFields")
if !fsField.IsValid() || fsField.Kind() != reflect.Slice {
// Only the struct's own ForceSendFields: one promoted from an embedded struct names that
// struct's fields, since the SDK marshaller rejects names its struct does not have.
sf, ok := v.Type().FieldByName("ForceSendFields")
if !ok || len(sf.Index) != 1 {
return nil
}
fsField := v.Field(sf.Index[0])
if fsField.Kind() != reflect.Slice {
return nil
}
result, ok := reflect.TypeAssert[[]string](fsField)
Expand Down
28 changes: 28 additions & 0 deletions libs/structs/structdiff/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1125,3 +1125,31 @@ func TestGetStructDiffPathsSharingPrefix(t *testing.T) {
assert.Equal(t, Change{Path: changes[2].Path, Old: nil, New: "2"}, changes[2])
assert.Equal(t, Change{Path: changes[3].Path, Old: "1", New: nil}, changes[3])
}

type PromotedInner struct {
A string `json:"a,omitempty"`
ForceSendFields []string `json:"-"`
}

type promotedOuter struct {
PromotedInner
Own string `json:"own,omitempty"`
}

// The embedded struct's ForceSendFields does not force the outer struct's own fields;
// the SDK marshaller would reject a name its struct does not have.
func TestGetStructDiffEmbeddedForceSendFieldsDoNotApplyToOuterFields(t *testing.T) {
forced := promotedOuter{ForceSendFields: []string{"A", "Own"}}
plain := promotedOuter{}

changes, err := GetStructDiff(forced, plain, nil)
require.NoError(t, err)
assert.Equal(t, []ResolvedChange{{Field: "a", Old: "", New: nil}}, resolveChanges(changes))
assert.False(t, IsEqual(forced, plain))

forced.ForceSendFields = []string{"Own"}
changes, err = GetStructDiff(forced, plain, nil)
require.NoError(t, err)
assert.Empty(t, changes)
assert.True(t, IsEqual(forced, plain))
}
10 changes: 8 additions & 2 deletions libs/structs/structwalk/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,14 @@ func getForceSendFields(v reflect.Value) []string {
if !v.IsValid() || v.Kind() != reflect.Struct {
return nil
}
fsField := v.FieldByName("ForceSendFields")
if !fsField.IsValid() || fsField.Kind() != reflect.Slice {
// Only the struct's own ForceSendFields: one promoted from an embedded struct names that
// struct's fields, since the SDK marshaller rejects names its struct does not have.
sf, ok := v.Type().FieldByName("ForceSendFields")
if !ok || len(sf.Index) != 1 {
return nil
}
fsField := v.Field(sf.Index[0])
if fsField.Kind() != reflect.Slice {
return nil
}
result, ok := reflect.TypeAssert[[]string](fsField)
Expand Down
17 changes: 17 additions & 0 deletions libs/structs/structwalk/walk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,20 @@ func walkPaths(t *testing.T, value any) []string {
slices.Sort(paths)
return paths
}

type PromotedInner struct {
A string `json:"a,omitempty"`
ForceSendFields []string `json:"-"`
}

type promotedOuter struct {
PromotedInner
Own string `json:"own,omitempty"`
}

// The embedded struct's ForceSendFields does not force the outer struct's own fields;
// the SDK marshaller would reject a name its struct does not have.
func TestEmbeddedForceSendFieldsDoNotApplyToOuterFields(t *testing.T) {
v := promotedOuter{ForceSendFields: []string{"A", "Own"}}
assert.Equal(t, map[string]any{"a": ""}, flatten(t, v))
}
Loading