Преглед изворни кода

Add Args helper for constructing command argument lists.

Args includes the functionality of redisx.AppendStruct. Mark
redisx.AppendStruct as deprecated.

Fixes #18 and #19.
Gary Burd пре 12 година
родитељ
комит
de0f4c50fc
4 измењених фајлова са 148 додато и 2 уклоњено
  1. 3 2
      redis/doc.go
  2. 56 0
      redis/scan.go
  3. 86 0
      redis/scan_test.go
  4. 3 0
      redisx/struct.go

+ 3 - 2
redis/doc.go

@@ -46,8 +46,9 @@
 //  bulk                []byte or nil if value not present.
 //  multi-bulk          []interface{} or nil if value not present.
 //
-// Applications can use type assertions or type switches to determine the type
-// of a reply.
+// The Redis command reference (http://redis.io/commands) documents the Redis
+// type returned for each command. Use type assertions to convert from
+// interface{} to the specific Go type for the command result.
 //
 // Pipelining
 //

+ 56 - 0
redis/scan.go

@@ -362,3 +362,59 @@ func ScanStruct(src []interface{}, dest interface{}) error {
 	}
 	return nil
 }
+
+// Args is a helper for constructing command arguments from structured values.
+type Args []interface{}
+
+// Add returns the result of appending value to args.
+func (args Args) Add(value interface{}) Args {
+	return append(args, value)
+}
+
+// AddFlat returns the result of appending the flattened value of v to args.
+//
+// Maps are flattened by appending the alternating keys and map values to args.
+//
+// Slices are flattened by appending the slice elements to args.
+//
+// Structs are flattened by appending the alternating field names and field
+// values to args. If v is a nil struct pointer, then nothing is appended. The
+// 'redis' field tag overrides struct field names. See ScanStruct for more
+// information on the use of the 'redis' field tag.
+//
+// Other types are appended to args as is.
+func (args Args) AddFlat(v interface{}) Args {
+	rv := reflect.ValueOf(v)
+	switch rv.Kind() {
+	case reflect.Struct:
+		args = flattenStruct(args, rv)
+	case reflect.Slice:
+		for i := 0; i < rv.Len(); i++ {
+			args = append(args, rv.Index(i).Interface())
+		}
+	case reflect.Map:
+		for _, k := range rv.MapKeys() {
+			args = append(args, k.Interface(), rv.MapIndex(k).Interface())
+		}
+	case reflect.Ptr:
+		if rv.Type().Elem().Kind() == reflect.Struct {
+			if !rv.IsNil() {
+				args = flattenStruct(args, rv.Elem())
+			}
+		} else {
+			args = append(args, v)
+		}
+	default:
+		args = append(args, v)
+	}
+	return args
+}
+
+func flattenStruct(args Args, v reflect.Value) Args {
+	ss := structSpecForType(v.Type())
+	for _, fs := range ss.l {
+		fv := v.FieldByIndex(fs.index)
+		args = append(args, fs.name, fv.Interface())
+	}
+	return args
+}

+ 86 - 0
redis/scan_test.go

@@ -182,3 +182,89 @@ func TestScanStruct(t *testing.T) {
 		}
 	}
 }
+
+var argsTests = []struct {
+	title    string
+	actual   redis.Args
+	expected redis.Args
+}{
+	{"struct ptr",
+		redis.Args{}.AddFlat(&struct {
+			I  int    `redis:"i"`
+			U  uint   `redis:"u"`
+			S  string `redis:"s"`
+			P  []byte `redis:"p"`
+			Bt bool
+			Bf bool
+		}{
+			-1234, 5678, "hello", []byte("world"), true, false,
+		}),
+		redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "Bt", true, "Bf", false},
+	},
+	{"struct",
+		redis.Args{}.AddFlat(struct{ I int }{123}),
+		redis.Args{"I", 123},
+	},
+	{"slice",
+		redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2),
+		redis.Args{1, "a", "b", "c", 2},
+	},
+}
+
+func TestArgs(t *testing.T) {
+	for _, tt := range argsTests {
+		if !reflect.DeepEqual(tt.actual, tt.expected) {
+			t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected)
+		}
+	}
+}
+
+func ExampleArgs() {
+	c, err := dial()
+	if err != nil {
+		panic(err)
+	}
+	defer c.Close()
+
+	var p1, p2 struct {
+		Title  string `redis:"title"`
+		Author string `redis:"author"`
+		Body   string `redis:"body"`
+	}
+
+	p1.Title = "Example"
+	p1.Author = "Gary"
+	p1.Body = "Hello"
+
+	if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil {
+		panic(err)
+	}
+
+	m := map[string]string{
+		"title":  "Example2",
+		"author": "Steve",
+		"body":   "Map",
+	}
+
+	if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil {
+		panic(err)
+	}
+
+	for _, id := range []string{"id1", "id2"} {
+
+		v, err := redis.Values(c.Do("HGETALL", id))
+		if err != nil {
+			panic(err)
+		}
+
+		if err := redis.ScanStruct(v, &p2); err != nil {
+			panic(err)
+		}
+
+		fmt.Printf("%+v\n", p2)
+	}
+
+	// Output:
+	// {Title:Example Author:Gary Body:Hello}
+	// {Title:Example2 Author:Steve Body:Map}
+}

+ 3 - 0
redisx/struct.go

@@ -20,6 +20,8 @@ import (
 	"strconv"
 )
 
+// ScanStruct is deprecated. Use redis.ScanStruct instead.
+//
 // ScanStruct scans a reply containing alternating names and values to a
 // struct. The HGETALL and CONFIG GET commands return replies in this format.
 //
@@ -97,6 +99,7 @@ func ScanStruct(reply interface{}, dst interface{}) error {
 	return nil
 }
 
+// AppendStruct is deprecated. Use redis.Args{}.AddFlat() instead.
 func AppendStruct(args []interface{}, src interface{}) []interface{} {
 	v := reflect.ValueOf(src)
 	if v.Kind() == reflect.Ptr {