diff --git a/message.go b/message.go index 5f8e720d..f5864b09 100644 --- a/message.go +++ b/message.go @@ -449,6 +449,29 @@ func (r RedisResult) CachePXAT() int64 { return r.val.CachePXAT() } +// String returns human-readable representation of RedisResult +func (r *RedisResult) String() string { + v, _ := (*prettyRedisResult)(r).MarshalJSON() + return string(v) +} + +type prettyRedisResult RedisResult + +// MarshalJSON implements json.Marshaler interface +func (r *prettyRedisResult) MarshalJSON() ([]byte, error) { + type PrettyRedisResult struct { + Error string `json:"Error,omitempty"` + Message *prettyRedisMessage `json:"Message,omitempty"` + } + obj := PrettyRedisResult{} + if r.err != nil { + obj.Error = r.err.Error() + } else { + obj.Message = (*prettyRedisMessage)(&r.val) + } + return json.Marshal(obj) +} + // RedisMessage is a redis response message, it may be a nil response type RedisMessage struct { attrs *RedisMessage @@ -1236,3 +1259,48 @@ func (m *RedisMessage) approximateSize() (s int) { } return } + +// String returns human-readable representation of RedisMessage +func (m *RedisMessage) String() string { + v, _ := (*prettyRedisMessage)(m).MarshalJSON() + return string(v) +} + +type prettyRedisMessage RedisMessage + +// MarshalJSON implements json.Marshaler interface +func (m *prettyRedisMessage) MarshalJSON() ([]byte, error) { + type PrettyRedisMessage struct { + Type string `json:"Type,omitempty"` + Error string `json:"Error,omitempty"` + Ttl string `json:"TTL,omitempty"` + Value any `json:"Value,omitempty"` + } + org := (*RedisMessage)(m) + strType, ok := typeNames[m.typ] + if !ok { + strType = "unknown" + } + obj := PrettyRedisMessage{Type: strType} + if m.ttl != [7]byte{} { + obj.Ttl = time.UnixMilli(org.CachePXAT()).UTC().String() + } + if err := org.Error(); err != nil { + obj.Error = err.Error() + } + switch m.typ { + case typeFloat, typeBlobString, typeSimpleString, typeVerbatimString, typeBigNumber: + obj.Value = m.string + case typeBool: + obj.Value = m.integer == 1 + case typeInteger: + obj.Value = m.integer + case typeMap, typeSet, typeArray: + values := make([]prettyRedisMessage, len(m.values)) + for i, value := range m.values { + values[i] = prettyRedisMessage(value) + } + obj.Value = values + } + return json.Marshal(obj) +} diff --git a/message_test.go b/message_test.go index dc2c9b44..d9b95ff7 100644 --- a/message_test.go +++ b/message_test.go @@ -1120,6 +1120,40 @@ func TestRedisResult(t *testing.T) { t.Fatal("CachePXAT <= 0") } }) + + t.Run("Stringer", func(t *testing.T) { + tests := []struct { + input RedisResult + expected string + }{ + { + input: RedisResult{ + val: RedisMessage{typ: '*', values: []RedisMessage{ + {typ: '*', values: []RedisMessage{ + {typ: ':', integer: 0}, + {typ: ':', integer: 0}, + {typ: '*', values: []RedisMessage{ // master + {typ: '+', string: "127.0.3.1"}, + {typ: ':', integer: 3}, + {typ: '+', string: ""}, + }}, + }}, + }}, + }, + expected: `{"Message":{"Type":"array","Value":[{"Type":"array","Value":[{"Type":"int64","Value":0},{"Type":"int64","Value":0},{"Type":"array","Value":[{"Type":"simple string","Value":"127.0.3.1"},{"Type":"int64","Value":3},{"Type":"simple string","Value":""}]}]}]}}`, + }, + { + input: RedisResult{err: errors.New("foo")}, + expected: `{"Error":"foo"}`, + }, + } + for _, test := range tests { + msg := test.input.String() + if msg != test.expected { + t.Fatalf("unexpected string. got %v expected %v", msg, test.expected) + } + } + }) } //gocyclo:ignore @@ -1672,4 +1706,56 @@ func TestRedisMessage(t *testing.T) { t.Fatal("CachePXAT <= 0") } }) + + t.Run("Stringer", func(t *testing.T) { + tests := []struct { + input RedisMessage + expected string + }{ + { + input: RedisMessage{typ: '*', values: []RedisMessage{ + {typ: '*', values: []RedisMessage{ + {typ: ':', integer: 0}, + {typ: ':', integer: 0}, + {typ: '*', values: []RedisMessage{ + {typ: '+', string: "127.0.3.1"}, + {typ: ':', integer: 3}, + {typ: '+', string: ""}, + }}, + }}, + }}, + expected: `{"Type":"array","Value":[{"Type":"array","Value":[{"Type":"int64","Value":0},{"Type":"int64","Value":0},{"Type":"array","Value":[{"Type":"simple string","Value":"127.0.3.1"},{"Type":"int64","Value":3},{"Type":"simple string","Value":""}]}]}]}`, + }, + { + input: RedisMessage{typ: '+', string: "127.0.3.1", ttl: [7]byte{97, 77, 74, 61, 138, 1, 0}}, + expected: `{"Type":"simple string","TTL":"2023-08-28 17:56:34.273 +0000 UTC","Value":"127.0.3.1"}`, + }, + { + input: RedisMessage{typ: '0'}, + expected: `{"Type":"unknown"}`, + }, + { + input: RedisMessage{typ: typeBool, integer: 1}, + expected: `{"Type":"boolean","Value":true}`, + }, + { + input: RedisMessage{typ: typeNull}, + expected: `{"Type":"null","Error":"redis nil message"}`, + }, + { + input: RedisMessage{typ: typeSimpleErr, string: "ERR foo"}, + expected: `{"Type":"simple error","Error":"foo"}`, + }, + { + input: RedisMessage{typ: typeBlobErr, string: "ERR foo"}, + expected: `{"Type":"blob error","Error":"foo"}`, + }, + } + for _, test := range tests { + msg := test.input.String() + if msg != test.expected { + t.Fatalf("unexpected string. got %v expected %v", msg, test.expected) + } + } + }) }