Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

packer-sdc: return errors on duplicate tag/field #164

Merged
merged 1 commit into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,18 @@ func (cmd *Command) Run(args []string) int {
}
// make sure each type is found once where somehow sometimes they can be found twice
typeNames = append(typeNames[:pos], typeNames[pos+1:]...)
flatenedStruct := getMapstructureSquashedStruct(obj.Pkg(), utStruct)
flatenedStruct = addCtyTagToStruct(flatenedStruct)
flatenedStruct, err := getMapstructureSquashedStruct(obj.Pkg(), utStruct)
if err != nil {
log.Printf("%s.%s: %s", obj.Pkg().Name(), obj.Id(), err)
return 1
}

flatenedStruct, err = addCtyTagToStruct(flatenedStruct)
if err != nil {
log.Printf("%s.%s: %s", obj.Pkg().Name(), obj.Id(), err)
return 1
}

newStructName := "Flat" + id.Name
structs = append(structs, StructDef{
OriginalStructName: id.Name,
Expand Down Expand Up @@ -422,7 +432,7 @@ func getUsedImports(s *types.Struct) map[NamePath]*types.Package {
return res
}

func addCtyTagToStruct(s *types.Struct) *types.Struct {
func addCtyTagToStruct(s *types.Struct) (*types.Struct, error) {
vars, tags := structFields(s)
for i := range tags {
field, tag := vars[i], tags[i]
Expand All @@ -437,10 +447,16 @@ func addCtyTagToStruct(s *types.Struct) *types.Struct {
_ = st.Set(&structtag.Tag{Key: "hcl", Name: ctyAccessor})
tags[i] = st.String()
}
return types.NewStruct(uniqueTags("cty", vars, tags))

vars, tags, err := uniqueTags("cty", vars, tags)
if err != nil {
return nil, fmt.Errorf("failed to add tag to struct: %s", err)
}

return types.NewStruct(vars, tags), nil
}

func uniqueTags(tagName string, fields []*types.Var, tags []string) ([]*types.Var, []string) {
func uniqueTags(tagName string, fields []*types.Var, tags []string) ([]*types.Var, []string, error) {
outVars := []*types.Var{}
outTags := []string{}
uniqueTags := map[string]bool{}
Expand All @@ -450,20 +466,19 @@ func uniqueTags(tagName string, fields []*types.Var, tags []string) ([]*types.Va
h, err := structtag.Get(tagName)
if err == nil {
if uniqueTags[h.Name] {
log.Printf("skipping field %s ( duplicate `%s` %s tag )", field.Name(), h.Name, tagName)
continue
return nil, nil, fmt.Errorf("field %q: duplicate tag %q", field.Name(), tagName)
}
uniqueTags[h.Name] = true
}
outVars = append(outVars, field)
outTags = append(outTags, tag)
}
return outVars, outTags
return outVars, outTags, nil
}

// getMapstructureSquashedStruct will return the same struct but embedded
// fields with a `mapstructure:",squash"` tag will be un-nested.
func getMapstructureSquashedStruct(topPkg *types.Package, utStruct *types.Struct) *types.Struct {
func getMapstructureSquashedStruct(topPkg *types.Package, utStruct *types.Struct) (*types.Struct, error) {
res := &types.Struct{}
for i := 0; i < utStruct.NumFields(); i++ {
field, tag := utStruct.Field(i), utStruct.Tag(i)
Expand Down Expand Up @@ -498,7 +513,16 @@ func getMapstructureSquashedStruct(topPkg *types.Package, utStruct *types.Struct
continue
}

res = squashStructs(res, getMapstructureSquashedStruct(topPkg, utStruct))
sqStr, err := getMapstructureSquashedStruct(topPkg, utStruct)
if err != nil {
return nil, err
}

res, err = squashStructs(res, sqStr)
if err != nil {
return nil, err
}

continue
}
}
Expand Down Expand Up @@ -553,9 +577,12 @@ func getMapstructureSquashedStruct(topPkg *types.Package, utStruct *types.Struct
// non optional fields should be non pointers.
field = makePointer(field)
}
res = addFieldToStruct(res, field, tag)
res, err = addFieldToStruct(res, field, tag)
if err != nil {
return nil, err
}
}
return res
return res, nil
}

func flattenNamed(f *types.Named, underlying types.Type) *types.Named {
Expand All @@ -568,32 +595,45 @@ func makePointer(field *types.Var) *types.Var {
return types.NewField(field.Pos(), field.Pkg(), field.Name(), types.NewPointer(field.Type()), field.Embedded())
}

func addFieldToStruct(s *types.Struct, field *types.Var, tag string) *types.Struct {
func addFieldToStruct(s *types.Struct, field *types.Var, tag string) (*types.Struct, error) {
sf, st := structFields(s)
return types.NewStruct(uniqueFields(append(sf, field), append(st, tag)))

vars, tags, err := uniqueFields(append(sf, field), append(st, tag))
if err != nil {
return nil, err
}

str := types.NewStruct(vars, tags)
return str, nil
}

func squashStructs(a, b *types.Struct) *types.Struct {
func squashStructs(a, b *types.Struct) (*types.Struct, error) {
va, ta := structFields(a)
vb, tb := structFields(b)
return types.NewStruct(uniqueFields(append(va, vb...), append(ta, tb...)))

vars, tags, err := uniqueFields(append(va, vb...), append(ta, tb...))
if err != nil {
return nil, fmt.Errorf("failed to squash struct: %s", err)
}

str := types.NewStruct(vars, tags)
return str, nil
}

func uniqueFields(fields []*types.Var, tags []string) ([]*types.Var, []string) {
func uniqueFields(fields []*types.Var, tags []string) ([]*types.Var, []string, error) {
outVars := []*types.Var{}
outTags := []string{}
fieldNames := map[string]bool{}
for i := range fields {
field, tag := fields[i], tags[i]
if fieldNames[field.Name()] {
log.Printf("skipping duplicate %s field", field.Name())
continue
return nil, nil, fmt.Errorf("duplicate field %q", field.Name())
}
fieldNames[field.Name()] = true
outVars = append(outVars, field)
outTags = append(outTags, tag)
}
return outVars, outTags
return outVars, outTags, nil
}

func structFields(s *types.Struct) (vars []*types.Var, tags []string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ func TestCommand_Run(t *testing.T) {
Expected: []string{"../test-data/packer-plugin-happycloud/builder/happycloud/config.hcl2spec.go"},
},
},
{
[]string{"-type", "Config", "../test-data/field-conflict/test_mapstructure_field_conflict.go"},
1,
FileCheck{},
},
{
[]string{"-type", "Config", "../test-data/tag-conflict/test_mapstructure_tag_conflict.go"},
1,
FileCheck{},
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%s", tt.args), func(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package test

type NestedOne struct {
Arg int `mapstructure:"test"`
}

type NestedTwo struct {
Arg int `mapstructure:"test"`
}

type Config struct {
NestedOne `mapstructure:",squash"`
NestedTwo `mapstructure:",squash"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package test

type NestedOne struct {
Arg int `mapstructure:"test"`
}

type NestedTwo struct {
Args int `mapstructure:"test"`
}

type Config struct {
NestedOne `mapstructure:",squash"`
NestedTwo `mapstructure:",squash"`
}