diff options
Diffstat (limited to 'pkg/server/opts/opts.go')
| -rw-r--r-- | pkg/server/opts/opts.go | 168 |
1 files changed, 107 insertions, 61 deletions
diff --git a/pkg/server/opts/opts.go b/pkg/server/opts/opts.go index c5729a3..2c781e3 100644 --- a/pkg/server/opts/opts.go +++ b/pkg/server/opts/opts.go @@ -7,12 +7,24 @@ import ( "strings" ) -type fieldSpec struct { - name string - inline bool - required bool - positive bool - defaultValue string +type Kind = reflect.Kind + +const ( + Bool = reflect.Bool + Int = reflect.Int + String = reflect.String +) + +type Param struct { + Name string + Kind Kind + + Default string + Required bool + + Positive bool + + index []int } type Opts map[string]string @@ -38,47 +50,55 @@ func (opts Opts) Bool(key string) bool { return ok } -func (opts Opts) Configure(v interface{}) error { - rv := reflect.ValueOf(v) +func extract(i interface{}) (reflect.Value, error) { + v := reflect.ValueOf(i) + + var err error switch { - case rv.Kind() != reflect.Ptr: - return fmt.Errorf("opts: configure failed: non-pointer %s", rv.Type().String()) - case rv.IsNil(): - return fmt.Errorf("opts: configure failed: nil %s", rv.Type().String()) - case rv.Elem().Kind() != reflect.Struct: - return fmt.Errorf("opts: configure failed: non-struct %s", rv.Elem().Type().String()) + case v.Kind() != reflect.Ptr: + err = fmt.Errorf("non-pointer %s", v.Type().String()) + case v.IsNil(): + err = fmt.Errorf("nil %s", v.Type().String()) + case v.Elem().Kind() != reflect.Struct: + err = fmt.Errorf("non-struct %s", v.Elem().Type().String()) + } + + if err != nil { + return reflect.Value{}, err } - return opts.configure(rv.Elem()) + return v.Elem(), nil } -func getFieldSpec(field reflect.StructField) (fs fieldSpec) { - fs.name = strings.ToLower(field.Name) +func (opts Opts) Configure(i interface{}) error { + v, err := extract(i) + if err != nil { + return fmt.Errorf("opts: configure failed: %w", err) + } - for _, s := range strings.Split(field.Tag.Get("opts"), ",") { + return opts.configure(v) +} + +func (p *Param) parseTags(tags string) { + for _, s := range strings.Split(tags, ",") { switch { - case s == "inline": - fs.inline = true case s == "required": - fs.required = true + p.Required = true case s == "positive": - fs.positive = true + p.Positive = true case strings.HasPrefix(s, "default:"): - fs.defaultValue = s[8:] + p.Default = s[8:] } } - - return } -func (opts Opts) setValue(v reflect.Value, fs fieldSpec) error { - s, ok := opts[fs.name] - kind := v.Kind() +func (opts Opts) setValue(v reflect.Value, p Param) error { + s, ok := opts[p.Name] - if kind == reflect.Bool { + if p.Kind == Bool { if s != "" { - return fmt.Errorf("%s: boolean option does not need a value", fs.name) + return fmt.Errorf("%s: boolean option does not need a value", p.Name) } if ok { @@ -89,70 +109,96 @@ func (opts Opts) setValue(v reflect.Value, fs fieldSpec) error { } if s == "" { - if fs.required { - return fmt.Errorf("%s: expect value", fs.name) + if p.Required { + return fmt.Errorf("%s: expect value", p.Name) } - s = fs.defaultValue + s = p.Default } if s == "" { return nil } - switch kind { - case reflect.String: + switch p.Kind { + case String: v.SetString(s) - case reflect.Int: + case Int: n, err := strconv.Atoi(s) if err != nil { - return fmt.Errorf("%s: %w", fs.name, err) + return fmt.Errorf("%s: %w", p.Name, err) } if n <= 0 { - return fmt.Errorf("%s: expect positive", fs.name) + return fmt.Errorf("%s: expect positive", p.Name) } v.SetInt(int64(n)) default: - return fmt.Errorf("%s: unsupported type '%s'", fs.name, v.Type().String()) + return fmt.Errorf("%s: unsupported type '%s'", p.Name, v.Type().String()) } return nil } -func (opts Opts) configure(v reflect.Value) error { - m := map[string]bool{} - - for s := range opts { - m[s] = true +func Parametrize(t reflect.Type) []Param { + if t.Kind() != reflect.Struct { + return nil } - var visit func(v reflect.Value) error + return parametrize(reflect.New(t).Elem()) +} - visit = func(v reflect.Value) error { - t := v.Type() +func parametrize(v reflect.Value) (param []Param) { + var visit func(index []int, v reflect.Value) + visit = func(index []int, v reflect.Value) { for n := 0; n < v.NumField(); n++ { - fs := getFieldSpec(t.Field(n)) - fv := v.Field(n) + f := v.Type().Field(n) + x := append(append([]int{}, index...), f.Index...) - if fs.inline { - if err := visit(fv); err != nil { - return err - } - } else if fv.CanSet() { - if err := opts.setValue(v.Field(n), fs); err != nil { - return err - } + if f.Anonymous { + visit(x, v.Field(n)) + continue + } + + if !v.Field(n).CanSet() { + continue + } - delete(m, fs.name) + p := Param{ + Name: strings.ToLower(f.Name), + Kind: f.Type.Kind(), + + index: x, } + + p.parseTags(f.Tag.Get("opts")) + + param = append(param, p) } + } - return nil + visit(nil, v) + + return +} + +func (opts Opts) configure(v reflect.Value) error { + param := parametrize(v) + + m := map[string]bool{} + + for s := range opts { + m[s] = true } - if err := visit(v); err != nil { - return err + for _, p := range param { + f := v.FieldByIndex(p.index) + + if err := opts.setValue(f, p); err != nil { + return err + } + + delete(m, p.Name) } if len(m) > 0 { |
