// Package dsn provide parse dsn and bind to struct // see http://git.bilibili.co/platform/go-common/issues/279 package dsn import ( "net/url" "reflect" "strings" "gopkg.in/go-playground/validator.v9" ) var _validator *validator.Validate func init() { _validator = validator.New() } // DSN a DSN represents a parsed DSN as same as url.URL. type DSN struct { *url.URL } // Bind dsn to specify struct and validate use use go-playground/validator format // // The bind of each struct field can be customized by the format string // stored under the 'dsn' key in the struct field's tag. The format string // gives the name of the field, possibly followed by a comma-separated // list of options. The name may be empty in order to specify options // without overriding the default field name. // // A two type data you can bind to struct // built-in values, use below keys to bind built-in value // username // password // address // network // the value in query string, use query.{name} to bind value in query string // // As a special case, if the field tag is "-", the field is always omitted. // NOTE: that a field with name "-" can still be generated using the tag "-,". // // Examples of struct field tags and their meanings: // // Field bind username // Field string `dsn:"username"` // // Field is ignored by this package. // Field string `dsn:"-"` // // Field bind value from query // Field string `dsn:"query.name"` // func (d *DSN) Bind(v interface{}) (url.Values, error) { assignFuncs := make(map[string]assignFunc) if d.User != nil { username := d.User.Username() password, ok := d.User.Password() if ok { assignFuncs["password"] = stringsAssignFunc(password) } assignFuncs["username"] = stringsAssignFunc(username) } assignFuncs["address"] = addressesAssignFunc(d.Addresses()) assignFuncs["network"] = stringsAssignFunc(d.Scheme) query, err := bindQuery(d.Query(), v, assignFuncs) if err != nil { return nil, err } return query, _validator.Struct(v) } func addressesAssignFunc(addresses []string) assignFunc { return func(v reflect.Value, to tagOpt) error { if v.Kind() == reflect.String { if addresses[0] == "" && to.Default != "" { v.SetString(to.Default) } else { v.SetString(addresses[0]) } return nil } if !(v.Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.String) { return &BindTypeError{Value: strings.Join(addresses, ","), Type: v.Type()} } vals := reflect.MakeSlice(v.Type(), len(addresses), len(addresses)) for i, address := range addresses { vals.Index(i).SetString(address) } if v.CanSet() { v.Set(vals) } return nil } } // Addresses parse host split by ',' // For Unix networks, return ['path'] func (d *DSN) Addresses() []string { switch d.Scheme { case "unix", "unixgram", "unixpacket": return []string{d.Path} } return strings.Split(d.Host, ",") } // Parse parses rawdsn into a URL structure. func Parse(rawdsn string) (*DSN, error) { u, err := url.Parse(rawdsn) return &DSN{URL: u}, err }