Skip to content

Commit 7379bd7

Browse files
*: Initial commit
0 parents  commit 7379bd7

File tree

10 files changed

+1122
-0
lines changed

10 files changed

+1122
-0
lines changed

backend/config.go

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package backend
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"net/url"
8+
"strconv"
9+
"time"
10+
11+
goredis "github.com/redis/go-redis/v9"
12+
)
13+
14+
type Config struct {
15+
Network string
16+
Addr string
17+
DB int
18+
19+
ClientName string
20+
21+
Dial func(context.Context, string, string) (net.Conn, error)
22+
ProvideCredentials func(context.Context) (string, string, error)
23+
24+
DialTimeout time.Duration
25+
ReadTimeout time.Duration
26+
WriteTimeout time.Duration
27+
PoolTimeout time.Duration
28+
29+
ConnMaxIdleTime time.Duration
30+
ConnMaxLifeTime time.Duration
31+
32+
MaxIdleConns int
33+
MaxOpenConns int
34+
}
35+
36+
func (c Config) toOptions() *goredis.Options {
37+
return &goredis.Options{
38+
Network: c.Network,
39+
Addr: c.Addr,
40+
DB: c.DB,
41+
ClientName: c.ClientName,
42+
Dialer: c.Dial,
43+
CredentialsProviderContext: c.ProvideCredentials,
44+
ContextTimeoutEnabled: true,
45+
DialTimeout: c.DialTimeout,
46+
ReadTimeout: c.ReadTimeout,
47+
WriteTimeout: c.WriteTimeout,
48+
PoolTimeout: c.PoolTimeout,
49+
ConnMaxIdleTime: c.ConnMaxIdleTime,
50+
ConnMaxLifetime: c.ConnMaxLifeTime,
51+
MaxIdleConns: c.MaxIdleConns,
52+
PoolSize: c.MaxOpenConns,
53+
}
54+
}
55+
56+
func (c Config) DSN() (string, error) {
57+
var (
58+
u url.URL
59+
vs url.Values
60+
)
61+
62+
switch c.Network {
63+
case "", "tcp":
64+
u.Scheme = "redis"
65+
u.Host = c.Addr
66+
u.Path = "/" + strconv.Itoa(c.DB)
67+
case "unix":
68+
u.Scheme = "unix"
69+
u.Path = c.Addr
70+
71+
vs = url.Values{"db": {strconv.Itoa(c.DB)}}
72+
default:
73+
return "", fmt.Errorf("unsupported network: %q", c.Network)
74+
}
75+
76+
if fn := c.ProvideCredentials; fn != nil {
77+
name, pwd, err := fn(context.Background())
78+
79+
if err != nil {
80+
return "", err
81+
}
82+
83+
u.User = url.UserPassword(name, pwd)
84+
}
85+
86+
if len(vs) > 0 {
87+
u.RawQuery = vs.Encode()
88+
}
89+
90+
return u.String(), nil
91+
}
92+
93+
func ParseURL(url string) (Config, error) {
94+
opts, err := goredis.ParseURL(url)
95+
96+
if err != nil {
97+
return Config{}, err
98+
}
99+
100+
c := Config{
101+
Network: opts.Network,
102+
Addr: opts.Addr,
103+
DB: opts.DB,
104+
ClientName: opts.ClientName,
105+
DialTimeout: opts.DialTimeout,
106+
ReadTimeout: opts.ReadTimeout,
107+
WriteTimeout: opts.WriteTimeout,
108+
PoolTimeout: opts.PoolTimeout,
109+
ConnMaxIdleTime: opts.ConnMaxIdleTime,
110+
ConnMaxLifeTime: opts.ConnMaxLifetime,
111+
MaxIdleConns: opts.MaxIdleConns,
112+
MaxOpenConns: opts.PoolSize,
113+
}
114+
115+
if opts.Username != "" || opts.Password != "" {
116+
c.ProvideCredentials = func(ctx context.Context) (string, string, error) {
117+
return opts.Username, opts.Password, nil
118+
}
119+
}
120+
121+
return c, nil
122+
}

backend/convert.go

+238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package backend
2+
3+
import (
4+
"bytes"
5+
"database/sql/driver"
6+
"errors"
7+
"fmt"
8+
"reflect"
9+
"strconv"
10+
11+
"github.com/upfluence/redis"
12+
)
13+
14+
var errNilPtr = errors.New("destination pointer is nil")
15+
16+
func convertAssign(dest, src any) error {
17+
if sc, ok := dest.(redis.ValueScanner); ok {
18+
return sc.Scan(src)
19+
}
20+
21+
// Common cases, without reflect.
22+
switch s := src.(type) {
23+
case string:
24+
switch d := dest.(type) {
25+
case *string:
26+
if d == nil {
27+
return errNilPtr
28+
}
29+
*d = s
30+
return nil
31+
case *[]byte:
32+
if d == nil {
33+
return errNilPtr
34+
}
35+
*d = []byte(s)
36+
return nil
37+
}
38+
case []byte:
39+
switch d := dest.(type) {
40+
case *string:
41+
if d == nil {
42+
return errNilPtr
43+
}
44+
*d = string(s)
45+
return nil
46+
case *any:
47+
if d == nil {
48+
return errNilPtr
49+
}
50+
*d = bytes.Clone(s)
51+
return nil
52+
case *[]byte:
53+
if d == nil {
54+
return errNilPtr
55+
}
56+
*d = bytes.Clone(s)
57+
return nil
58+
}
59+
case nil:
60+
return redis.Empty
61+
}
62+
63+
var sv reflect.Value
64+
65+
switch d := dest.(type) {
66+
case *string:
67+
sv = reflect.ValueOf(src)
68+
switch sv.Kind() {
69+
case reflect.Bool,
70+
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
71+
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
72+
reflect.Float32, reflect.Float64:
73+
*d = asString(src)
74+
return nil
75+
}
76+
case *[]byte:
77+
sv = reflect.ValueOf(src)
78+
if b, ok := asBytes(sv); ok {
79+
*d = b
80+
return nil
81+
}
82+
case *bool:
83+
bv, err := driver.Bool.ConvertValue(src)
84+
if err == nil {
85+
*d = bv.(bool)
86+
}
87+
return err
88+
case *any:
89+
*d = src
90+
return nil
91+
}
92+
93+
dpv := reflect.ValueOf(dest)
94+
95+
if dpv.Kind() != reflect.Pointer {
96+
return errors.New("destination not a pointer")
97+
}
98+
99+
if dpv.IsNil() {
100+
return errNilPtr
101+
}
102+
103+
if !sv.IsValid() {
104+
sv = reflect.ValueOf(src)
105+
}
106+
107+
dv := reflect.Indirect(dpv)
108+
109+
if sv.IsValid() && sv.Type().AssignableTo(dv.Type()) {
110+
switch b := src.(type) {
111+
case []byte:
112+
dv.Set(reflect.ValueOf(bytes.Clone(b)))
113+
default:
114+
dv.Set(sv)
115+
}
116+
return nil
117+
}
118+
119+
if dv.Kind() == sv.Kind() && sv.Type().ConvertibleTo(dv.Type()) {
120+
dv.Set(sv.Convert(dv.Type()))
121+
return nil
122+
}
123+
124+
switch dv.Kind() {
125+
case reflect.Pointer:
126+
if src == nil {
127+
dv.SetZero()
128+
return nil
129+
}
130+
dv.Set(reflect.New(dv.Type().Elem()))
131+
return convertAssign(dv.Interface(), src)
132+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
133+
if src == nil {
134+
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
135+
}
136+
s := asString(src)
137+
i64, err := strconv.ParseInt(s, 10, dv.Type().Bits())
138+
if err != nil {
139+
err = strconvErr(err)
140+
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
141+
}
142+
dv.SetInt(i64)
143+
return nil
144+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
145+
if src == nil {
146+
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
147+
}
148+
s := asString(src)
149+
u64, err := strconv.ParseUint(s, 10, dv.Type().Bits())
150+
if err != nil {
151+
err = strconvErr(err)
152+
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
153+
}
154+
dv.SetUint(u64)
155+
return nil
156+
case reflect.Float32, reflect.Float64:
157+
if src == nil {
158+
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
159+
}
160+
s := asString(src)
161+
f64, err := strconv.ParseFloat(s, dv.Type().Bits())
162+
if err != nil {
163+
err = strconvErr(err)
164+
return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err)
165+
}
166+
dv.SetFloat(f64)
167+
return nil
168+
case reflect.String:
169+
if src == nil {
170+
return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind())
171+
}
172+
switch v := src.(type) {
173+
case string:
174+
dv.SetString(v)
175+
return nil
176+
case []byte:
177+
dv.SetString(string(v))
178+
return nil
179+
}
180+
}
181+
182+
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, dest)
183+
}
184+
185+
func strconvErr(err error) error {
186+
if ne, ok := err.(*strconv.NumError); ok {
187+
return ne.Err
188+
}
189+
return err
190+
}
191+
192+
func asString(src any) string {
193+
switch v := src.(type) {
194+
case string:
195+
return v
196+
case []byte:
197+
return string(v)
198+
}
199+
200+
rv := reflect.ValueOf(src)
201+
202+
switch rv.Kind() {
203+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
204+
return strconv.FormatInt(rv.Int(), 10)
205+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
206+
return strconv.FormatUint(rv.Uint(), 10)
207+
case reflect.Float64:
208+
return strconv.FormatFloat(rv.Float(), 'g', -1, 64)
209+
case reflect.Float32:
210+
return strconv.FormatFloat(rv.Float(), 'g', -1, 32)
211+
case reflect.Bool:
212+
return strconv.FormatBool(rv.Bool())
213+
}
214+
215+
return fmt.Sprintf("%v", src)
216+
}
217+
218+
func asBytes(rv reflect.Value) ([]byte, bool) {
219+
var buf []byte
220+
221+
switch rv.Kind() {
222+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
223+
return strconv.AppendInt(buf, rv.Int(), 10), true
224+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
225+
return strconv.AppendUint(buf, rv.Uint(), 10), true
226+
case reflect.Float32:
227+
return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 32), true
228+
case reflect.Float64:
229+
return strconv.AppendFloat(buf, rv.Float(), 'g', -1, 64), true
230+
case reflect.Bool:
231+
return strconv.AppendBool(buf, rv.Bool()), true
232+
case reflect.String:
233+
s := rv.String()
234+
return append(buf, s...), true
235+
}
236+
237+
return buf, false
238+
}

0 commit comments

Comments
 (0)