Rework tree from map (#139)
* Make TreeFromMap reflect to construct tree * Fix wording of invalid value type in writeTo Fixes #138, #139, #134 ⚠️ TreeFromMap signature changed to `TreeFromMap(map[string]interface{}) (*TomlTree, error)`
This commit is contained in:
+4
-3
@@ -662,10 +662,11 @@ func TestTomlValueStringRepresentation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestToStringMapStringString(t *testing.T) {
|
||||
in := map[string]interface{}{"m": TreeFromMap(map[string]interface{}{
|
||||
"v": &tomlValue{"abc", Position{0, 0}}})}
|
||||
tree, err := TreeFromMap(map[string]interface{}{"m": map[string]interface{}{"v": "abc"}})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
want := "\n[m]\n v = \"abc\"\n"
|
||||
tree := TreeFromMap(in)
|
||||
got := tree.String()
|
||||
|
||||
if got != want {
|
||||
|
||||
@@ -28,10 +28,12 @@ func newTomlTree() *TomlTree {
|
||||
}
|
||||
|
||||
// TreeFromMap initializes a new TomlTree object using the given map.
|
||||
func TreeFromMap(m map[string]interface{}) *TomlTree {
|
||||
return &TomlTree{
|
||||
values: m,
|
||||
func TreeFromMap(m map[string]interface{}) (*TomlTree, error) {
|
||||
result, err := toTree(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.(*TomlTree), nil
|
||||
}
|
||||
|
||||
// Has returns a boolean indicating if the given key exists.
|
||||
|
||||
+5
-2
@@ -121,8 +121,11 @@ func TestTomlQuery(t *testing.T) {
|
||||
|
||||
func TestTomlFromMap(t *testing.T) {
|
||||
simpleMap := map[string]interface{}{"hello": 42}
|
||||
tree := TreeFromMap(simpleMap)
|
||||
if tree.Get("hello") != 42 {
|
||||
tree, err := TreeFromMap(simpleMap)
|
||||
if err != nil {
|
||||
t.Fatal("unexpected error:", err)
|
||||
}
|
||||
if tree.Get("hello") != int64(42) {
|
||||
t.Fatal("hello should be 42, not", tree.Get("hello"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// supported values:
|
||||
// string, bool, int64, uint64, float64, time.Time, int, int8, int16, int32, uint, uint8, uint16, uint32, float32
|
||||
|
||||
var kindToTypeMapping = map[reflect.Kind]reflect.Type{
|
||||
reflect.Bool: reflect.TypeOf(true),
|
||||
reflect.String: reflect.TypeOf(""),
|
||||
reflect.Float32: reflect.TypeOf(float64(1)),
|
||||
reflect.Float64: reflect.TypeOf(float64(1)),
|
||||
reflect.Int: reflect.TypeOf(int64(1)),
|
||||
reflect.Int8: reflect.TypeOf(int64(1)),
|
||||
reflect.Int16: reflect.TypeOf(int64(1)),
|
||||
reflect.Int32: reflect.TypeOf(int64(1)),
|
||||
reflect.Int64: reflect.TypeOf(int64(1)),
|
||||
reflect.Uint: reflect.TypeOf(uint64(1)),
|
||||
reflect.Uint8: reflect.TypeOf(uint64(1)),
|
||||
reflect.Uint16: reflect.TypeOf(uint64(1)),
|
||||
reflect.Uint32: reflect.TypeOf(uint64(1)),
|
||||
reflect.Uint64: reflect.TypeOf(uint64(1)),
|
||||
}
|
||||
|
||||
func simpleValueCoercion(object interface{}) (interface{}, error) {
|
||||
switch original := object.(type) {
|
||||
case string, bool, int64, uint64, float64, time.Time:
|
||||
return original, nil
|
||||
case int:
|
||||
return int64(original), nil
|
||||
case int8:
|
||||
return int64(original), nil
|
||||
case int16:
|
||||
return int64(original), nil
|
||||
case int32:
|
||||
return int64(original), nil
|
||||
case uint:
|
||||
return uint64(original), nil
|
||||
case uint8:
|
||||
return uint64(original), nil
|
||||
case uint16:
|
||||
return uint64(original), nil
|
||||
case uint32:
|
||||
return uint64(original), nil
|
||||
case float32:
|
||||
return float64(original), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot convert type %T to TomlTree", object)
|
||||
}
|
||||
}
|
||||
|
||||
func sliceToTree(object interface{}) (interface{}, error) {
|
||||
// arrays are a bit tricky, since they can represent either a
|
||||
// collection of simple values, which is represented by one
|
||||
// *tomlValue, or an array of tables, which is represented by an
|
||||
// array of *TomlTree.
|
||||
|
||||
// holding the assumption that this function is called from toTree only when value.Kind() is Array or Slice
|
||||
value := reflect.ValueOf(object)
|
||||
insideType := value.Type().Elem()
|
||||
length := value.Len()
|
||||
if insideType.Kind() == reflect.Map {
|
||||
// this is considered as an array of tables
|
||||
tablesArray := make([]*TomlTree, 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
table := value.Index(i)
|
||||
tree, err := toTree(table.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tablesArray = append(tablesArray, tree.(*TomlTree))
|
||||
}
|
||||
return tablesArray, nil
|
||||
}
|
||||
|
||||
sliceType := kindToTypeMapping[insideType.Kind()]
|
||||
if sliceType == nil {
|
||||
sliceType = insideType
|
||||
}
|
||||
|
||||
arrayValue := reflect.MakeSlice(reflect.SliceOf(sliceType), 0, length)
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
val := value.Index(i).Interface()
|
||||
simpleValue, err := simpleValueCoercion(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue))
|
||||
}
|
||||
return &tomlValue{arrayValue.Interface(), Position{}}, nil
|
||||
}
|
||||
|
||||
func toTree(object interface{}) (interface{}, error) {
|
||||
value := reflect.ValueOf(object)
|
||||
|
||||
if value.Kind() == reflect.Map {
|
||||
values := map[string]interface{}{}
|
||||
keys := value.MapKeys()
|
||||
for _, key := range keys {
|
||||
k, ok := key.Interface().(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("map key needs to be a string, not %T", key.Interface())
|
||||
}
|
||||
|
||||
v := value.MapIndex(key)
|
||||
newValue, err := toTree(v.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values[k] = newValue
|
||||
}
|
||||
return &TomlTree{values, Position{}}, nil
|
||||
}
|
||||
|
||||
if value.Kind() == reflect.Array || value.Kind() == reflect.Slice {
|
||||
return sliceToTree(object)
|
||||
}
|
||||
|
||||
simpleValue, err := simpleValueCoercion(object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tomlValue{simpleValue, Position{}}, nil
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func validate(t *testing.T, path string, object interface{}) {
|
||||
switch o := object.(type) {
|
||||
case *TomlTree:
|
||||
for key, tree := range o.values {
|
||||
validate(t, path+"."+key, tree)
|
||||
}
|
||||
case []*TomlTree:
|
||||
for index, tree := range o {
|
||||
validate(t, path+"."+string(index), tree)
|
||||
}
|
||||
case *tomlValue:
|
||||
switch o.value.(type) {
|
||||
case int64, uint64, bool, string, float64, time.Time,
|
||||
[]int64, []uint64, []bool, []string, []float64, []time.Time:
|
||||
return // ok
|
||||
default:
|
||||
t.Fatalf("tomlValue at key %s containing incorrect type %T", path, o.value)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("value at key %s is of incorrect type %T", path, object)
|
||||
}
|
||||
t.Log("validation ok", path)
|
||||
}
|
||||
|
||||
func validateTree(t *testing.T, tree *TomlTree) {
|
||||
validate(t, "", tree)
|
||||
}
|
||||
|
||||
func TestTomlTreeCreateToTree(t *testing.T) {
|
||||
data := map[string]interface{}{
|
||||
"a_string": "bar",
|
||||
"an_int": 42,
|
||||
"time": time.Now(),
|
||||
"int8": int8(2),
|
||||
"int16": int16(2),
|
||||
"int32": int32(2),
|
||||
"uint8": uint8(2),
|
||||
"uint16": uint16(2),
|
||||
"uint32": uint32(2),
|
||||
"float32": float32(2),
|
||||
"a_bool": false,
|
||||
"nested": map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
"array": []string{"a", "b", "c"},
|
||||
"array_uint": []uint{uint(1), uint(2)},
|
||||
"array_table": []map[string]interface{}{map[string]interface{}{"sub_map": 52}},
|
||||
"array_times": []time.Time{time.Now(), time.Now()},
|
||||
"map_times": map[string]time.Time{"now": time.Now()},
|
||||
}
|
||||
tree, err := TreeFromMap(data)
|
||||
if err != nil {
|
||||
t.Fatal("unexpected error:", err)
|
||||
}
|
||||
validateTree(t, tree)
|
||||
}
|
||||
|
||||
func TestTomlTreeCreateToTreeInvalidLeafType(t *testing.T) {
|
||||
_, err := TreeFromMap(map[string]interface{}{"foo": t})
|
||||
expected := "cannot convert type *testing.T to TomlTree"
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("expected error %s, got %s", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTomlTreeCreateToTreeInvalidMapKeyType(t *testing.T) {
|
||||
_, err := TreeFromMap(map[string]interface{}{"foo": map[int]interface{}{2: 1}})
|
||||
expected := "map key needs to be a string, not int"
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("expected error %s, got %s", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTomlTreeCreateToTreeInvalidArrayMemberType(t *testing.T) {
|
||||
_, err := TreeFromMap(map[string]interface{}{"foo": []*testing.T{t}})
|
||||
expected := "cannot convert type *testing.T to TomlTree"
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("expected error %s, got %s", expected, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTomlTreeCreateToTreeInvalidTableGroupType(t *testing.T) {
|
||||
_, err := TreeFromMap(map[string]interface{}{"foo": []map[string]interface{}{map[string]interface{}{"hello": t}}})
|
||||
expected := "cannot convert type *testing.T to TomlTree"
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("expected error %s, got %s", expected, err.Error())
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -95,7 +95,7 @@ func (t *TomlTree) writeTo(w io.Writer, indent, keyspace string, bytesCount int6
|
||||
for _, k := range simpleValuesKeys {
|
||||
v, ok := t.values[k].(*tomlValue)
|
||||
if !ok {
|
||||
return bytesCount, fmt.Errorf("invalid key type at %s: %T", k, t.values[k])
|
||||
return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
|
||||
}
|
||||
|
||||
repr, err := tomlValueStringRepresentation(v.value)
|
||||
@@ -201,9 +201,6 @@ func (t *TomlTree) ToMap() map[string]interface{} {
|
||||
result[k] = array
|
||||
case *TomlTree:
|
||||
result[k] = node.ToMap()
|
||||
case map[string]interface{}:
|
||||
sub := TreeFromMap(node)
|
||||
result[k] = sub.ToMap()
|
||||
case *tomlValue:
|
||||
result[k] = node.value
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ func TestTomlTreeWriteToMapSimple(t *testing.T) {
|
||||
func TestTomlTreeWriteToInvalidTreeSimpleValue(t *testing.T) {
|
||||
tree := TomlTree{values: map[string]interface{}{"foo": int8(1)}}
|
||||
_, err := tree.ToTomlString()
|
||||
assertErrorString(t, "invalid key type at foo: int8", err)
|
||||
assertErrorString(t, "invalid value type at foo: int8", err)
|
||||
}
|
||||
|
||||
func TestTomlTreeWriteToInvalidTreeTomlValue(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user