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 }