211 lines
5.5 KiB
Go
211 lines
5.5 KiB
Go
package interpres
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// decoder maps a parsed TOML tree onto Go values via reflection.
|
|
type decoder struct {
|
|
disallowUnknown bool
|
|
}
|
|
|
|
func newDecoder() *decoder { return &decoder{} }
|
|
|
|
var timeType = reflect.TypeOf(time.Time{})
|
|
|
|
func (d *decoder) decode(tree map[string]any, v any) error {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() != reflect.Pointer || rv.IsNil() {
|
|
return fmt.Errorf("interpres: decode target must be a non-nil pointer")
|
|
}
|
|
return d.assign(tree, rv.Elem())
|
|
}
|
|
|
|
// assign stores data into dst, converting between the TOML value kinds and the
|
|
// destination's Go type.
|
|
func (d *decoder) assign(data any, dst reflect.Value) error {
|
|
if dst.Kind() == reflect.Pointer {
|
|
if dst.IsNil() {
|
|
dst.Set(reflect.New(dst.Type().Elem()))
|
|
}
|
|
return d.assign(data, dst.Elem())
|
|
}
|
|
|
|
// An interface{} destination takes the value as-is.
|
|
if dst.Kind() == reflect.Interface && dst.NumMethod() == 0 {
|
|
dst.Set(reflect.ValueOf(data))
|
|
return nil
|
|
}
|
|
|
|
switch v := data.(type) {
|
|
case map[string]any:
|
|
return d.assignTable(v, dst)
|
|
case []map[string]any:
|
|
return d.assignTableSlice(v, dst)
|
|
case []any:
|
|
return d.assignSlice(v, dst)
|
|
case string:
|
|
return setBasic(dst, reflect.ValueOf(v), "string")
|
|
case bool:
|
|
return setBasic(dst, reflect.ValueOf(v), "bool")
|
|
case int64:
|
|
return setInt(dst, v)
|
|
case float64:
|
|
return setFloat(dst, v)
|
|
case time.Time:
|
|
if dst.Type() != timeType {
|
|
return fmt.Errorf("interpres: cannot assign datetime to %s", dst.Type())
|
|
}
|
|
dst.Set(reflect.ValueOf(v))
|
|
return nil
|
|
default:
|
|
rv := reflect.ValueOf(data)
|
|
if rv.IsValid() && dst.Type() == rv.Type() {
|
|
dst.Set(rv)
|
|
return nil
|
|
}
|
|
return fmt.Errorf("interpres: cannot assign %T to %s", data, dst.Type())
|
|
}
|
|
}
|
|
|
|
func (d *decoder) assignTable(tbl map[string]any, dst reflect.Value) error {
|
|
switch dst.Kind() {
|
|
case reflect.Struct:
|
|
return d.assignStruct(tbl, dst)
|
|
case reflect.Map:
|
|
return d.assignMap(tbl, dst)
|
|
default:
|
|
return fmt.Errorf("interpres: cannot assign table to %s", dst.Type())
|
|
}
|
|
}
|
|
|
|
func (d *decoder) assignStruct(tbl map[string]any, dst reflect.Value) error {
|
|
fields := structFields(dst.Type())
|
|
for key, val := range tbl {
|
|
field, ok := fields[strings.ToLower(key)]
|
|
if !ok {
|
|
if d.disallowUnknown {
|
|
return fmt.Errorf("interpres: unknown field %q for %s", key, dst.Type())
|
|
}
|
|
continue
|
|
}
|
|
if err := d.assign(val, dst.Field(field)); err != nil {
|
|
return fmt.Errorf("%s: %w", key, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) assignMap(tbl map[string]any, dst reflect.Value) error {
|
|
if dst.Type().Key().Kind() != reflect.String {
|
|
return fmt.Errorf("interpres: map key must be a string, got %s", dst.Type().Key())
|
|
}
|
|
if dst.IsNil() {
|
|
dst.Set(reflect.MakeMap(dst.Type()))
|
|
}
|
|
elemType := dst.Type().Elem()
|
|
for key, val := range tbl {
|
|
elem := reflect.New(elemType).Elem()
|
|
if err := d.assign(val, elem); err != nil {
|
|
return fmt.Errorf("%s: %w", key, err)
|
|
}
|
|
dst.SetMapIndex(reflect.ValueOf(key), elem)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) assignSlice(items []any, dst reflect.Value) error {
|
|
if dst.Kind() != reflect.Slice {
|
|
return fmt.Errorf("interpres: cannot assign array to %s", dst.Type())
|
|
}
|
|
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
|
for i, item := range items {
|
|
if err := d.assign(item, out.Index(i)); err != nil {
|
|
return fmt.Errorf("[%d]: %w", i, err)
|
|
}
|
|
}
|
|
dst.Set(out)
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) assignTableSlice(items []map[string]any, dst reflect.Value) error {
|
|
if dst.Kind() != reflect.Slice {
|
|
return fmt.Errorf("interpres: cannot assign array of tables to %s", dst.Type())
|
|
}
|
|
out := reflect.MakeSlice(dst.Type(), len(items), len(items))
|
|
for i, item := range items {
|
|
if err := d.assign(item, out.Index(i)); err != nil {
|
|
return fmt.Errorf("[%d]: %w", i, err)
|
|
}
|
|
}
|
|
dst.Set(out)
|
|
return nil
|
|
}
|
|
|
|
// --- low-level setters -----------------------------------------------------
|
|
|
|
func setBasic(dst, val reflect.Value, kind string) error {
|
|
if dst.Kind() != val.Kind() {
|
|
return fmt.Errorf("interpres: cannot assign %s to %s", kind, dst.Type())
|
|
}
|
|
dst.Set(val)
|
|
return nil
|
|
}
|
|
|
|
func setInt(dst reflect.Value, v int64) error {
|
|
switch dst.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if dst.OverflowInt(v) {
|
|
return fmt.Errorf("interpres: integer %d overflows %s", v, dst.Type())
|
|
}
|
|
dst.SetInt(v)
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
if v < 0 {
|
|
return fmt.Errorf("interpres: cannot assign negative %d to %s", v, dst.Type())
|
|
}
|
|
dst.SetUint(uint64(v))
|
|
case reflect.Float32, reflect.Float64:
|
|
dst.SetFloat(float64(v))
|
|
default:
|
|
return fmt.Errorf("interpres: cannot assign integer to %s", dst.Type())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setFloat(dst reflect.Value, v float64) error {
|
|
switch dst.Kind() {
|
|
case reflect.Float32, reflect.Float64:
|
|
dst.SetFloat(v)
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("interpres: cannot assign float to %s", dst.Type())
|
|
}
|
|
}
|
|
|
|
// structFields builds a lower-cased lookup of field name → field index for the
|
|
// exported fields of t, honouring `toml:"name"` tags.
|
|
func structFields(t reflect.Type) map[string]int {
|
|
fields := make(map[string]int, t.NumField())
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
if f.PkgPath != "" { // unexported
|
|
continue
|
|
}
|
|
name := f.Name
|
|
if tag, ok := f.Tag.Lookup("toml"); ok {
|
|
tag = strings.Split(tag, ",")[0]
|
|
if tag == "-" {
|
|
continue
|
|
}
|
|
if tag != "" {
|
|
name = tag
|
|
}
|
|
}
|
|
fields[strings.ToLower(name)] = i
|
|
}
|
|
return fields
|
|
}
|