From 6fed9dd0dd62718f78eca11e30a71c2712636fbd Mon Sep 17 00:00:00 2001 From: Mikhail Osipov Date: Wed, 16 Dec 2020 15:27:48 +0300 Subject: hook and socket args check fix, tests --- pkg/server/opts/opts.go | 149 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 3 deletions(-) (limited to 'pkg/server/opts') diff --git a/pkg/server/opts/opts.go b/pkg/server/opts/opts.go index 22383d8..c5729a3 100644 --- a/pkg/server/opts/opts.go +++ b/pkg/server/opts/opts.go @@ -1,6 +1,19 @@ package opts -import "strings" +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +type fieldSpec struct { + name string + inline bool + required bool + positive bool + defaultValue string +} type Opts map[string]string @@ -20,7 +33,137 @@ func Parse(s string) (string, Opts) { return v[0], m } -func (m Opts) Bool(key string) bool { - _, ok := m[key] +func (opts Opts) Bool(key string) bool { + _, ok := opts[key] return ok } + +func (opts Opts) Configure(v interface{}) error { + rv := reflect.ValueOf(v) + + 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()) + } + + return opts.configure(rv.Elem()) +} + +func getFieldSpec(field reflect.StructField) (fs fieldSpec) { + fs.name = strings.ToLower(field.Name) + + for _, s := range strings.Split(field.Tag.Get("opts"), ",") { + switch { + case s == "inline": + fs.inline = true + case s == "required": + fs.required = true + case s == "positive": + fs.positive = true + case strings.HasPrefix(s, "default:"): + fs.defaultValue = s[8:] + } + } + + return +} + +func (opts Opts) setValue(v reflect.Value, fs fieldSpec) error { + s, ok := opts[fs.name] + kind := v.Kind() + + if kind == reflect.Bool { + if s != "" { + return fmt.Errorf("%s: boolean option does not need a value", fs.name) + } + + if ok { + v.SetBool(true) + } + + return nil + } + + if s == "" { + if fs.required { + return fmt.Errorf("%s: expect value", fs.name) + } + + s = fs.defaultValue + } + + if s == "" { + return nil + } + + switch kind { + case reflect.String: + v.SetString(s) + case reflect.Int: + n, err := strconv.Atoi(s) + if err != nil { + return fmt.Errorf("%s: %w", fs.name, err) + } + if n <= 0 { + return fmt.Errorf("%s: expect positive", fs.name) + } + v.SetInt(int64(n)) + default: + return fmt.Errorf("%s: unsupported type '%s'", fs.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 + } + + var visit func(v reflect.Value) error + + visit = func(v reflect.Value) error { + t := v.Type() + + for n := 0; n < v.NumField(); n++ { + fs := getFieldSpec(t.Field(n)) + fv := v.Field(n) + + 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 + } + + delete(m, fs.name) + } + } + + return nil + } + + if err := visit(v); err != nil { + return err + } + + if len(m) > 0 { + var ss []string + + for s := range m { + ss = append(ss, s) + } + + return fmt.Errorf("unknown options: %s", strings.Join(ss, ",")) + } + + return nil +} -- cgit v1.2.3-70-g09d2