commit
99e2f22d1c
39
decoder.go
39
decoder.go
@ -404,6 +404,11 @@ func (d *decoder) decodeMap(name string, node ast.Node, result reflect.Value) er
|
||||
}
|
||||
|
||||
func (d *decoder) decodePtr(name string, node ast.Node, result reflect.Value) error {
|
||||
// if pointer is not nil, decode into existing value
|
||||
if !result.IsNil() {
|
||||
return d.decode(name, node, result.Elem())
|
||||
}
|
||||
|
||||
// Create an element of the concrete (non pointer) type and decode
|
||||
// into that. Then set the value of the pointer to this type.
|
||||
resultType := result.Type()
|
||||
@ -512,7 +517,7 @@ func expandObject(node ast.Node, result reflect.Value) ast.Node {
|
||||
// we need to un-flatten the ast enough to decode
|
||||
newNode := &ast.ObjectItem{
|
||||
Keys: []*ast.ObjectKey{
|
||||
&ast.ObjectKey{
|
||||
{
|
||||
Token: keyToken,
|
||||
},
|
||||
},
|
||||
@ -631,10 +636,19 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value)
|
||||
}
|
||||
}
|
||||
|
||||
usedKeys := make(map[string]struct{})
|
||||
decodedFields := make([]string, 0, len(fields))
|
||||
decodedFieldsVal := make([]reflect.Value, 0)
|
||||
unusedKeysVal := make([]reflect.Value, 0)
|
||||
|
||||
// fill unusedNodeKeys with keys from the AST
|
||||
// a slice because we have to do equals case fold to match Filter
|
||||
unusedNodeKeys := make([]string, 0)
|
||||
for _, item := range list.Items {
|
||||
for _, k := range item.Keys {
|
||||
unusedNodeKeys = append(unusedNodeKeys, k.Token.Value().(string))
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
field, fieldValue := f.field, f.val
|
||||
if !fieldValue.IsValid() {
|
||||
@ -689,8 +703,8 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value)
|
||||
continue
|
||||
}
|
||||
|
||||
// Track the used key
|
||||
usedKeys[fieldName] = struct{}{}
|
||||
// Track the used keys
|
||||
unusedNodeKeys = removeCaseFold(unusedNodeKeys, fieldName)
|
||||
|
||||
// Create the field name and decode. We range over the elements
|
||||
// because we actually want the value.
|
||||
@ -723,6 +737,14 @@ func (d *decoder) decodeStruct(name string, node ast.Node, result reflect.Value)
|
||||
}
|
||||
}
|
||||
|
||||
if len(unusedNodeKeys) > 0 {
|
||||
// like decodedFields, populated the unusedKeys field(s)
|
||||
sort.Strings(unusedNodeKeys)
|
||||
for _, v := range unusedKeysVal {
|
||||
v.Set(reflect.ValueOf(unusedNodeKeys))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -734,3 +756,12 @@ func findNodeType() reflect.Type {
|
||||
value := reflect.ValueOf(nodeContainer).FieldByName("Node")
|
||||
return value.Type()
|
||||
}
|
||||
|
||||
func removeCaseFold(xs []string, y string) []string {
|
||||
for i, x := range xs {
|
||||
if strings.EqualFold(x, y) {
|
||||
return append(xs[:i], xs[i+1:]...)
|
||||
}
|
||||
}
|
||||
return xs
|
||||
}
|
||||
|
@ -616,6 +616,35 @@ func TestDecode_structurePtr(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_nonNilStructurePtr(t *testing.T) {
|
||||
type V struct {
|
||||
Key int
|
||||
Foo string
|
||||
DontChange string
|
||||
}
|
||||
|
||||
actual := &V{
|
||||
Key: 42,
|
||||
Foo: "foo",
|
||||
DontChange: "don't change me",
|
||||
}
|
||||
|
||||
err := Decode(&actual, testReadFile(t, "flat.hcl"))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := &V{
|
||||
Key: 7,
|
||||
Foo: "bar",
|
||||
DontChange: "don't change me",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_structureArray(t *testing.T) {
|
||||
// This test is extracted from a failure in Consul (consul.io),
|
||||
// hence the interesting structure naming.
|
||||
@ -786,6 +815,36 @@ func TestDecode_structureMapInvalid(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_structureMapExtraKeys(t *testing.T) {
|
||||
type hclVariable struct {
|
||||
A int
|
||||
B int
|
||||
Found []string `hcl:",decodedFields"`
|
||||
Extra []string `hcl:",unusedKeys"`
|
||||
}
|
||||
|
||||
q := hclVariable{
|
||||
A: 1,
|
||||
B: 2,
|
||||
Found: []string{"A", "B"},
|
||||
Extra: []string{"extra1", "extra2"},
|
||||
}
|
||||
|
||||
var p hclVariable
|
||||
ast, _ := Parse(testReadFile(t, "structure_map_extra_keys.hcl"))
|
||||
DecodeObject(&p, ast)
|
||||
if !reflect.DeepEqual(p, q) {
|
||||
t.Fatal("not equal")
|
||||
}
|
||||
|
||||
var j hclVariable
|
||||
ast, _ = Parse(testReadFile(t, "structure_map_extra_keys.json"))
|
||||
DecodeObject(&j, ast)
|
||||
if !reflect.DeepEqual(p, j) {
|
||||
t.Fatal("not equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecode_interfaceNonPointer(t *testing.T) {
|
||||
var value interface{}
|
||||
err := Decode(value, testReadFile(t, "basic_int_string.hcl"))
|
||||
|
@ -240,7 +240,7 @@ func TestListType_lineComment(t *testing.T) {
|
||||
comment := l.comment[i]
|
||||
|
||||
if (lt.LineComment == nil) != (comment == "") {
|
||||
t.Fatalf("bad: %s", lt)
|
||||
t.Fatalf("bad: %s", lt.Token.Value())
|
||||
}
|
||||
|
||||
if comment == "" {
|
||||
|
4
test-fixtures/structure_map_extra_keys.hcl
Normal file
4
test-fixtures/structure_map_extra_keys.hcl
Normal file
@ -0,0 +1,4 @@
|
||||
a = 1
|
||||
b = 2
|
||||
extra1 = 3
|
||||
extra2 = 4
|
6
test-fixtures/structure_map_extra_keys.json
Normal file
6
test-fixtures/structure_map_extra_keys.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"extra1": 3,
|
||||
"extra2": 4
|
||||
}
|
Loading…
Reference in New Issue
Block a user