package properties import ( "fmt" "reflect" "strconv" "strings" "time" ) // Decode assigns property values to exported fields of a struct. // // Decode traverses v recursively and returns an error if a value cannot be // converted to the field type or a required value is missing for a field. // // The following type dependent decodings are used: // // String, boolean, numeric fields have the value of the property key assigned. // The property key name is the name of the field. A different key and a default // value can be set in the field's tag. Fields without default value are // required. If the value cannot be converted to the field type an error is // returned. // // time.Duration fields have the result of time.ParseDuration() assigned. // // time.Time fields have the vaule of time.Parse() assigned. The default layout // is time.RFC3339 but can be set in the field's tag. // // Arrays and slices of string, boolean, numeric, time.Duration and time.Time // fields have the value interpreted as a comma separated list of values. The // individual values are trimmed of whitespace and empty values are ignored. A // default value can be provided as a semicolon separated list in the field's // tag. // // Struct fields are decoded recursively using the field name plus "." as // prefix. The prefix (without dot) can be overridden in the field's tag. // Default values are not supported in the field's tag. Specify them on the // fields of the inner struct instead. // // Map fields must have a key of type string and are decoded recursively by // using the field's name plus ".' as prefix and the next element of the key // name as map key. The prefix (without dot) can be overridden in the field's // tag. Default values are not supported. // // Examples: // // // Field is ignored. // Field int `properties:"-"` // // // Field is assigned value of 'Field'. // Field int // // // Field is assigned value of 'myName'. // Field int `properties:"myName"` // // // Field is assigned value of key 'myName' and has a default // // value 15 if the key does not exist. // Field int `properties:"myName,default=15"` // // // Field is assigned value of key 'Field' and has a default // // value 15 if the key does not exist. // Field int `properties:",default=15"` // // // Field is assigned value of key 'date' and the date // // is in format 2006-01-02 // Field time.Time `properties:"date,layout=2006-01-02"` // // // Field is assigned the non-empty and whitespace trimmed // // values of key 'Field' split by commas. // Field []string // // // Field is assigned the non-empty and whitespace trimmed // // values of key 'Field' split by commas and has a default // // value ["a", "b", "c"] if the key does not exist. // Field []string `properties:",default=a;b;c"` // // // Field is decoded recursively with "Field." as key prefix. // Field SomeStruct // // // Field is decoded recursively with "myName." as key prefix. // Field SomeStruct `properties:"myName"` // // // Field is decoded recursively with "Field." as key prefix // // and the next dotted element of the key as map key. // Field map[string]string // // // Field is decoded recursively with "myName." as key prefix // // and the next dotted element of the key as map key. // Field map[string]string `properties:"myName"` func (p *Properties) Decode(x interface{}) error { t, v := reflect.TypeOf(x), reflect.ValueOf(x) if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct { return fmt.Errorf("not a pointer to struct: %s", t) } if err := dec(p, "", nil, nil, v); err != nil { return err } return nil } func dec(p *Properties, key string, def *string, opts map[string]string, v reflect.Value) error { t := v.Type() // value returns the property value for key or the default if provided. value := func() (string, error) { if val, ok := p.Get(key); ok { return val, nil } if def != nil { return *def, nil } return "", fmt.Errorf("missing required key %s", key) } // conv converts a string to a value of the given type. conv := func(s string, t reflect.Type) (val reflect.Value, err error) { var v interface{} switch { case isDuration(t): v, err = time.ParseDuration(s) case isTime(t): layout := opts["layout"] if layout == "" { layout = time.RFC3339 } v, err = time.Parse(layout, s) case isBool(t): v, err = boolVal(s), nil case isString(t): v, err = s, nil case isFloat(t): v, err = strconv.ParseFloat(s, 64) case isInt(t): v, err = strconv.ParseInt(s, 10, 64) case isUint(t): v, err = strconv.ParseUint(s, 10, 64) default: return reflect.Zero(t), fmt.Errorf("unsupported type %s", t) } if err != nil { return reflect.Zero(t), err } return reflect.ValueOf(v).Convert(t), nil } // keydef returns the property key and the default value based on the // name of the struct field and the options in the tag. keydef := func(f reflect.StructField) (string, *string, map[string]string) { key, opts := parseTag(f.Tag.Get("properties")) var def *string if d, ok := opts["default"]; ok { def = &d } if key != "" { return key, def, opts } return f.Name, def, opts } switch { case isDuration(t) || isTime(t) || isBool(t) || isString(t) || isFloat(t) || isInt(t) || isUint(t): s, err := value() if err != nil { return err } val, err := conv(s, t) if err != nil { return err } v.Set(val) case isPtr(t): return dec(p, key, def, opts, v.Elem()) case isStruct(t): for i := 0; i < v.NumField(); i++ { fv := v.Field(i) fk, def, opts := keydef(t.Field(i)) if !fv.CanSet() { return fmt.Errorf("cannot set ", t.Field(i).Name) } if fk == "-" { continue } if key != "" { fk = key + "." + fk } if err := dec(p, fk, def, opts, fv); err != nil { return err } } return nil case isArray(t): val, err := value() if err != nil { return err } vals := split(val, ";") a := reflect.MakeSlice(t, 0, len(vals)) for _, s := range vals { val, err := conv(s, t.Elem()) if err != nil { return err } a = reflect.Append(a, val) } v.Set(a) case isMap(t): valT := t.Elem() m := reflect.MakeMap(t) for postfix, _ := range p.FilterStripPrefix(key + ".").m { pp := strings.SplitN(postfix, ".", 2) mk, mv := pp[0], reflect.New(valT) if err := dec(p, key+"."+mk, nil, nil, mv); err != nil { return err } m.SetMapIndex(reflect.ValueOf(mk), mv.Elem()) } v.Set(m) default: return fmt.Errorf("unsupported type %s", t) } return nil } // split splits a string on sep, trims whitespace of elements // and omits empty elements func split(s string, sep string) []string { var a []string for _, v := range strings.Split(s, sep) { if v = strings.TrimSpace(v); v != "" { a = append(a, v) } } return a } // parseTag parses a "key,k=v,k=v,..." func parseTag(tag string) (key string, opts map[string]string) { opts = map[string]string{} for i, s := range strings.Split(tag, ",") { if i == 0 { key = s continue } pp := strings.SplitN(s, "=", 2) if len(pp) == 1 { opts[pp[0]] = "" } else { opts[pp[0]] = pp[1] } } return key, opts } func isArray(t reflect.Type) bool { return t.Kind() == reflect.Array || t.Kind() == reflect.Slice } func isBool(t reflect.Type) bool { return t.Kind() == reflect.Bool } func isDuration(t reflect.Type) bool { return t == reflect.TypeOf(time.Second) } func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map } func isNumeric(t reflect.Type) bool { return isInt(t) || isUint(t) || isFloat(t) } func isPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr } func isString(t reflect.Type) bool { return t.Kind() == reflect.String } func isStruct(t reflect.Type) bool { return t.Kind() == reflect.Struct } func isTime(t reflect.Type) bool { return t == reflect.TypeOf(time.Time{}) } func isFloat(t reflect.Type) bool { return t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64 } func isInt(t reflect.Type) bool { return t.Kind() == reflect.Int || t.Kind() == reflect.Int8 || t.Kind() == reflect.Int16 || t.Kind() == reflect.Int32 || t.Kind() == reflect.Int64 } func isUint(t reflect.Type) bool { return t.Kind() == reflect.Uint || t.Kind() == reflect.Uint8 || t.Kind() == reflect.Uint16 || t.Kind() == reflect.Uint32 || t.Kind() == reflect.Uint64 }