options: Add Apply()

This commit is contained in:
Alexander Neumann 2017-03-25 16:53:30 +01:00
parent f587a5f4f0
commit 2924ebc124
2 changed files with 156 additions and 0 deletions

View file

@ -1,8 +1,11 @@
package options
import (
"reflect"
"restic/errors"
"strconv"
"strings"
"time"
)
// Options holds options in the form key=value.
@ -64,3 +67,59 @@ func (o Options) Extract(ns string) Options {
return opts
}
// Apply sets the options on dst via reflection, using the struct tag `option`.
func (o Options) Apply(dst interface{}) error {
v := reflect.ValueOf(dst).Elem()
fields := make(map[string]reflect.StructField)
for i := 0; i < v.NumField(); i++ {
f := v.Type().Field(i)
tag := f.Tag.Get("option")
if tag == "" {
continue
}
if _, ok := fields[tag]; ok {
panic("option tag " + tag + " is not unique in " + v.Type().Name())
}
fields[tag] = f
}
for key, value := range o {
field, ok := fields[key]
if !ok {
return errors.Fatalf("option %v is not known", key)
}
i := field.Index[0]
switch v.Type().Field(i).Type.Name() {
case "string":
v.Field(i).SetString(value)
case "int":
vi, err := strconv.ParseInt(value, 0, 32)
if err != nil {
return err
}
v.Field(i).SetInt(vi)
case "Duration":
d, err := time.ParseDuration(value)
if err != nil {
return err
}
v.Field(i).SetInt(int64(d))
default:
panic("type " + v.Type().Field(i).Type.Name() + " not handled")
}
}
return nil
}

View file

@ -4,6 +4,7 @@ import (
"fmt"
"reflect"
"testing"
"time"
)
var optsTests = []struct {
@ -117,3 +118,99 @@ func TestOptionsExtract(t *testing.T) {
})
}
}
// Target is used for Apply() tests
type Target struct {
Name string `option:"name"`
ID int `option:"id"`
Timeout time.Duration `option:"timeout"`
Other string
}
var setTests = []struct {
input Options
output Target
}{
{
Options{
"name": "foobar",
},
Target{
Name: "foobar",
},
},
{
Options{
"name": "foobar",
"id": "1234",
},
Target{
Name: "foobar",
ID: 1234,
},
},
{
Options{
"timeout": "10m3s",
},
Target{
Timeout: time.Duration(10*time.Minute + 3*time.Second),
},
},
}
func TestOptionsApply(t *testing.T) {
for i, test := range setTests {
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
var dst Target
err := test.input.Apply(&dst)
if err != nil {
t.Fatal(err)
}
if dst != test.output {
t.Fatalf("wrong result, want:\n %#v\ngot:\n %#v", test.output, dst)
}
})
}
}
var invalidSetTests = []struct {
input Options
err string
}{
{
Options{
"first_name": "foobar",
},
"option first_name is not known",
},
{
Options{
"id": "foobar",
},
`strconv.ParseInt: parsing "foobar": invalid syntax`,
},
{
Options{
"timeout": "2134",
},
`time: missing unit in duration 2134`,
},
}
func TestOptionsApplyInvalid(t *testing.T) {
for i, test := range invalidSetTests {
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
var dst Target
err := test.input.Apply(&dst)
if err == nil {
t.Fatalf("expected error %v not found", test.err)
}
if err.Error() != test.err {
t.Fatalf("expected error %q, got %q", test.err, err.Error())
}
})
}
}