forked from TrueCloudLab/restic
options: Add Apply()
This commit is contained in:
parent
f587a5f4f0
commit
2924ebc124
2 changed files with 156 additions and 0 deletions
|
@ -1,8 +1,11 @@
|
||||||
package options
|
package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"restic/errors"
|
"restic/errors"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options holds options in the form key=value.
|
// Options holds options in the form key=value.
|
||||||
|
@ -64,3 +67,59 @@ func (o Options) Extract(ns string) Options {
|
||||||
|
|
||||||
return opts
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var optsTests = []struct {
|
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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue