Skip to content

Commit 99e2f22

Browse files
authored
Merge pull request hashicorp#275 from hashicorp/fix-unusedKeys
Fix unused keys
2 parents 65a6292 + e94e92c commit 99e2f22

File tree

5 files changed

+105
-5
lines changed

5 files changed

+105
-5
lines changed

Diff for: decoder.go

+35-4
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,11 @@ func (d *decoder) decodeMap(name string, node ast.Node, result reflect.Value) er
404404
}
405405

406406
func (d *decoder) decodePtr(name string, node ast.Node, result reflect.Value) error {
407+
// if pointer is not nil, decode into existing value
408+
if !result.IsNil() {
409+
return d.decode(name, node, result.Elem())
410+
}
411+
407412
// Create an element of the concrete (non pointer) type and decode
408413
// into that. Then set the value of the pointer to this type.
409414
resultType := result.Type()
@@ -512,7 +517,7 @@ func expandObject(node ast.Node, result reflect.Value) ast.Node {
512517
// we need to un-flatten the ast enough to decode
513518
newNode := &ast.ObjectItem{
514519
Keys: []*ast.ObjectKey{
515-
&ast.ObjectKey{
520+
{
516521
Token: keyToken,
517522
},
518523
},
@@ -631,10 +636,19 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value)
631636
}
632637
}
633638

634-
usedKeys := make(map[string]struct{})
635639
decodedFields := make([]string, 0, len(fields))
636640
decodedFieldsVal := make([]reflect.Value, 0)
637641
unusedKeysVal := make([]reflect.Value, 0)
642+
643+
// fill unusedNodeKeys with keys from the AST
644+
// a slice because we have to do equals case fold to match Filter
645+
unusedNodeKeys := make([]string, 0)
646+
for _, item := range list.Items {
647+
for _, k := range item.Keys {
648+
unusedNodeKeys = append(unusedNodeKeys, k.Token.Value().(string))
649+
}
650+
}
651+
638652
for _, f := range fields {
639653
field, fieldValue := f.field, f.val
640654
if !fieldValue.IsValid() {
@@ -689,8 +703,8 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value)
689703
continue
690704
}
691705

692-
// Track the used key
693-
usedKeys[fieldName] = struct{}{}
706+
// Track the used keys
707+
unusedNodeKeys = removeCaseFold(unusedNodeKeys, fieldName)
694708

695709
// Create the field name and decode. We range over the elements
696710
// because we actually want the value.
@@ -723,6 +737,14 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value)
723737
}
724738
}
725739

740+
if len(unusedNodeKeys) > 0 {
741+
// like decodedFields, populated the unusedKeys field(s)
742+
sort.Strings(unusedNodeKeys)
743+
for _, v := range unusedKeysVal {
744+
v.Set(reflect.ValueOf(unusedNodeKeys))
745+
}
746+
}
747+
726748
return nil
727749
}
728750

@@ -734,3 +756,12 @@ func findNodeType() reflect.Type {
734756
value := reflect.ValueOf(nodeContainer).FieldByName("Node")
735757
return value.Type()
736758
}
759+
760+
func removeCaseFold(xs []string, y string) []string {
761+
for i, x := range xs {
762+
if strings.EqualFold(x, y) {
763+
return append(xs[:i], xs[i+1:]...)
764+
}
765+
}
766+
return xs
767+
}

Diff for: decoder_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,35 @@ func TestDecode_structurePtr(t *testing.T) {
616616
}
617617
}
618618

619+
func TestDecode_nonNilStructurePtr(t *testing.T) {
620+
type V struct {
621+
Key int
622+
Foo string
623+
DontChange string
624+
}
625+
626+
actual := &V{
627+
Key: 42,
628+
Foo: "foo",
629+
DontChange: "don't change me",
630+
}
631+
632+
err := Decode(&actual, testReadFile(t, "flat.hcl"))
633+
if err != nil {
634+
t.Fatalf("err: %s", err)
635+
}
636+
637+
expected := &V{
638+
Key: 7,
639+
Foo: "bar",
640+
DontChange: "don't change me",
641+
}
642+
643+
if !reflect.DeepEqual(actual, expected) {
644+
t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
645+
}
646+
}
647+
619648
func TestDecode_structureArray(t *testing.T) {
620649
// This test is extracted from a failure in Consul (consul.io),
621650
// hence the interesting structure naming.
@@ -786,6 +815,36 @@ func TestDecode_structureMapInvalid(t *testing.T) {
786815
}
787816
}
788817

818+
func TestDecode_structureMapExtraKeys(t *testing.T) {
819+
type hclVariable struct {
820+
A int
821+
B int
822+
Found []string `hcl:",decodedFields"`
823+
Extra []string `hcl:",unusedKeys"`
824+
}
825+
826+
q := hclVariable{
827+
A: 1,
828+
B: 2,
829+
Found: []string{"A", "B"},
830+
Extra: []string{"extra1", "extra2"},
831+
}
832+
833+
var p hclVariable
834+
ast, _ := Parse(testReadFile(t, "structure_map_extra_keys.hcl"))
835+
DecodeObject(&p, ast)
836+
if !reflect.DeepEqual(p, q) {
837+
t.Fatal("not equal")
838+
}
839+
840+
var j hclVariable
841+
ast, _ = Parse(testReadFile(t, "structure_map_extra_keys.json"))
842+
DecodeObject(&j, ast)
843+
if !reflect.DeepEqual(p, j) {
844+
t.Fatal("not equal")
845+
}
846+
}
847+
789848
func TestDecode_interfaceNonPointer(t *testing.T) {
790849
var value interface{}
791850
err := Decode(value, testReadFile(t, "basic_int_string.hcl"))

Diff for: hcl/parser/parser_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ func TestListType_lineComment(t *testing.T) {
240240
comment := l.comment[i]
241241

242242
if (lt.LineComment == nil) != (comment == "") {
243-
t.Fatalf("bad: %s", lt)
243+
t.Fatalf("bad: %s", lt.Token.Value())
244244
}
245245

246246
if comment == "" {

Diff for: test-fixtures/structure_map_extra_keys.hcl

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
a = 1
2+
b = 2
3+
extra1 = 3
4+
extra2 = 4

Diff for: test-fixtures/structure_map_extra_keys.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"a": 1,
3+
"b": 2,
4+
"extra1": 3,
5+
"extra2": 4
6+
}

0 commit comments

Comments
 (0)