From b4f70cfad2173fb2ebfb64c50b59bf3b02105f89 Mon Sep 17 00:00:00 2001 From: Exca-DK Date: Mon, 28 Aug 2023 20:00:23 +0200 Subject: [PATCH 1/5] Implement stringer and marshaller interfaces --- message.go | 65 ++++++++++++++++++++++++++++++++++++++++++++ message_test.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/message.go b/message.go index 5f8e720d..cc5dc642 100644 --- a/message.go +++ b/message.go @@ -449,6 +449,30 @@ func (r RedisResult) CachePXAT() int64 { return r.val.CachePXAT() } +// String returns human-readable representation of RedisResult +func (r RedisResult) String() string { + v, err := r.MarshalJSON() + if err != nil { + return "" + } + return string(v) +} + +// MarshalJSON implements json.Marshaler interface +func (r *RedisResult) MarshalJSON() ([]byte, error) { + type PrettyRedisResult struct { + Error string `json:"Error,omitempty"` + Message *RedisMessage `json:"Message,omitempty"` + } + obj := PrettyRedisResult{} + if r.err != nil { + obj.Error = r.err.Error() + } else { + obj.Message = &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 +1260,44 @@ func (m *RedisMessage) approximateSize() (s int) { } return } + +// String returns human-readable representation of RedisMessage +func (m RedisMessage) String() string { + v, err := m.MarshalJSON() + if err != nil { + return "" + } + return string(v) +} + +// MarshalJSON implements json.Marshaler interface +func (m RedisMessage) 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"` + } + strType, ok := typeNames[m.typ] + if !ok { + strType = "unknown" + } + obj := PrettyRedisMessage{Type: strType} + if m.ttl != [7]byte{} { + obj.Ttl = time.UnixMilli(m.CachePXAT()).String() + } + if err := m.Error(); err != nil { + obj.Error = err.Error() + } + switch m.typ { + case typeFloat, typeBlobString, typeSimpleString, typeVerbatimString, typeBigNumber: + obj.Value = m.string + case typeBool: + obj.Value = strconv.FormatBool(m.integer == 1) + case typeInteger: + obj.Value = m.integer + case typeMap, typeSet, typeArray: + obj.Value = m.values + } + return json.Marshal(obj) +} diff --git a/message_test.go b/message_test.go index dc2c9b44..3f8ef6a6 100644 --- a/message_test.go +++ b/message_test.go @@ -1120,6 +1120,43 @@ func TestRedisResult(t *testing.T) { t.Fatal("CachePXAT <= 0") } }) + + t.Run("Marshalling", 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 { + marshalled, err := test.input.MarshalJSON() + if err != nil { + t.Fatalf("unexpected err %v", err) + } + if string(marshalled) != test.expected { + t.Fatalf("marshalling failed. got %v expected %v", string(marshalled), test.expected) + } + } + }) } //gocyclo:ignore @@ -1672,4 +1709,39 @@ func TestRedisMessage(t *testing.T) { t.Fatal("CachePXAT <= 0") } }) + + t.Run("Marshalling", 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{ // master + {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 19:56:34.273 +0200 CEST","Value":"127.0.3.1"}`, + }, + } + for _, test := range tests { + marshalled, err := test.input.MarshalJSON() + if err != nil { + t.Fatalf("unexpected err %v", err) + } + if string(marshalled) != test.expected { + t.Fatalf("marshalling failed. got %v expected %v", string(marshalled), test.expected) + } + } + }) } From 95bcae386419fd17d9515ea17977f1560db72adb Mon Sep 17 00:00:00 2001 From: Exca-DK Date: Mon, 28 Aug 2023 22:01:35 +0200 Subject: [PATCH 2/5] time as utc --- message.go | 2 +- message_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/message.go b/message.go index cc5dc642..4c6e5b91 100644 --- a/message.go +++ b/message.go @@ -1284,7 +1284,7 @@ func (m RedisMessage) MarshalJSON() ([]byte, error) { } obj := PrettyRedisMessage{Type: strType} if m.ttl != [7]byte{} { - obj.Ttl = time.UnixMilli(m.CachePXAT()).String() + obj.Ttl = time.UnixMilli(m.CachePXAT()).UTC().String() } if err := m.Error(); err != nil { obj.Error = err.Error() diff --git a/message_test.go b/message_test.go index 3f8ef6a6..a634bddc 100644 --- a/message_test.go +++ b/message_test.go @@ -1731,7 +1731,7 @@ func TestRedisMessage(t *testing.T) { }, { 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 19:56:34.273 +0200 CEST","Value":"127.0.3.1"}`, + expected: `{"Type":"simple string","Ttl":"2023-08-28 17:56:34.273 +0000 UTC","Value":"127.0.3.1"}`, }, } for _, test := range tests { From 77d90f4c112776f053c9703106f5fc25e1e4e258 Mon Sep 17 00:00:00 2001 From: Exca-DK Date: Tue, 29 Aug 2023 18:01:45 +0200 Subject: [PATCH 3/5] move pretty jsonMarshallers into aliased types --- message.go | 35 ++++++++++++++++++++++------------- message_test.go | 22 ++++++++-------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/message.go b/message.go index 4c6e5b91..60c98579 100644 --- a/message.go +++ b/message.go @@ -450,25 +450,27 @@ func (r RedisResult) CachePXAT() int64 { } // String returns human-readable representation of RedisResult -func (r RedisResult) String() string { - v, err := r.MarshalJSON() +func (r *RedisResult) String() string { + v, err := (*prettyRedisResult)(r).MarshalJSON() if err != nil { return "" } return string(v) } +type prettyRedisResult RedisResult + // MarshalJSON implements json.Marshaler interface -func (r *RedisResult) MarshalJSON() ([]byte, error) { +func (r *prettyRedisResult) MarshalJSON() ([]byte, error) { type PrettyRedisResult struct { - Error string `json:"Error,omitempty"` - Message *RedisMessage `json:"Message,omitempty"` + Error string `json:"Error,omitempty"` + Message *prettyRedisMessage `json:"Message,omitempty"` } obj := PrettyRedisResult{} if r.err != nil { obj.Error = r.err.Error() } else { - obj.Message = &r.val + obj.Message = (*prettyRedisMessage)(&r.val) } return json.Marshal(obj) } @@ -1262,42 +1264,49 @@ func (m *RedisMessage) approximateSize() (s int) { } // String returns human-readable representation of RedisMessage -func (m RedisMessage) String() string { - v, err := m.MarshalJSON() +func (m *RedisMessage) String() string { + v, err := (*prettyRedisMessage)(m).MarshalJSON() if err != nil { return "" } return string(v) } +type prettyRedisMessage RedisMessage + // MarshalJSON implements json.Marshaler interface -func (m RedisMessage) MarshalJSON() ([]byte, error) { +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(m.CachePXAT()).UTC().String() + obj.Ttl = time.UnixMilli(org.CachePXAT()).UTC().String() } - if err := m.Error(); err != nil { + 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 = strconv.FormatBool(m.integer == 1) + obj.Value = m.integer == 1 case typeInteger: obj.Value = m.integer case typeMap, typeSet, typeArray: - obj.Value = m.values + 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 a634bddc..ab9d1a22 100644 --- a/message_test.go +++ b/message_test.go @@ -1121,7 +1121,7 @@ func TestRedisResult(t *testing.T) { } }) - t.Run("Marshalling", func(t *testing.T) { + t.Run("Stringer", func(t *testing.T) { tests := []struct { input RedisResult expected string @@ -1148,12 +1148,9 @@ func TestRedisResult(t *testing.T) { }, } for _, test := range tests { - marshalled, err := test.input.MarshalJSON() - if err != nil { - t.Fatalf("unexpected err %v", err) - } - if string(marshalled) != test.expected { - t.Fatalf("marshalling failed. got %v expected %v", string(marshalled), test.expected) + msg := test.input.String() + if msg != test.expected { + t.Fatalf("unexpected string. got %v expected %v", msg, test.expected) } } }) @@ -1710,7 +1707,7 @@ func TestRedisMessage(t *testing.T) { } }) - t.Run("Marshalling", func(t *testing.T) { + t.Run("Stringer", func(t *testing.T) { tests := []struct { input RedisMessage expected string @@ -1735,12 +1732,9 @@ func TestRedisMessage(t *testing.T) { }, } for _, test := range tests { - marshalled, err := test.input.MarshalJSON() - if err != nil { - t.Fatalf("unexpected err %v", err) - } - if string(marshalled) != test.expected { - t.Fatalf("marshalling failed. got %v expected %v", string(marshalled), test.expected) + msg := test.input.String() + if msg != test.expected { + t.Fatalf("unexpected string. got %v expected %v", msg, test.expected) } } }) From 706a520a81110c2de5396b6fd1750365ab7b140e Mon Sep 17 00:00:00 2001 From: Exca-DK Date: Wed, 30 Aug 2023 05:09:40 +0200 Subject: [PATCH 4/5] capitalize ttl --- message.go | 2 +- message_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/message.go b/message.go index 60c98579..ad393487 100644 --- a/message.go +++ b/message.go @@ -1279,7 +1279,7 @@ func (m *prettyRedisMessage) MarshalJSON() ([]byte, error) { type PrettyRedisMessage struct { Type string `json:"Type,omitempty"` Error string `json:"Error,omitempty"` - Ttl string `json:"Ttl,omitempty"` + Ttl string `json:"TTL,omitempty"` Value any `json:"Value,omitempty"` } org := (*RedisMessage)(m) diff --git a/message_test.go b/message_test.go index ab9d1a22..145b424f 100644 --- a/message_test.go +++ b/message_test.go @@ -1728,7 +1728,7 @@ func TestRedisMessage(t *testing.T) { }, { 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"}`, + expected: `{"Type":"simple string","TTL":"2023-08-28 17:56:34.273 +0000 UTC","Value":"127.0.3.1"}`, }, } for _, test := range tests { From 3165dfcd1f46c325ce1def76431738fb8d85d983 Mon Sep 17 00:00:00 2001 From: Exca-DK Date: Wed, 30 Aug 2023 13:51:57 +0200 Subject: [PATCH 5/5] Ignore error in string method Add tests --- message.go | 10 ++-------- message_test.go | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/message.go b/message.go index ad393487..f5864b09 100644 --- a/message.go +++ b/message.go @@ -451,10 +451,7 @@ func (r RedisResult) CachePXAT() int64 { // String returns human-readable representation of RedisResult func (r *RedisResult) String() string { - v, err := (*prettyRedisResult)(r).MarshalJSON() - if err != nil { - return "" - } + v, _ := (*prettyRedisResult)(r).MarshalJSON() return string(v) } @@ -1265,10 +1262,7 @@ func (m *RedisMessage) approximateSize() (s int) { // String returns human-readable representation of RedisMessage func (m *RedisMessage) String() string { - v, err := (*prettyRedisMessage)(m).MarshalJSON() - if err != nil { - return "" - } + v, _ := (*prettyRedisMessage)(m).MarshalJSON() return string(v) } diff --git a/message_test.go b/message_test.go index 145b424f..d9b95ff7 100644 --- a/message_test.go +++ b/message_test.go @@ -1717,7 +1717,7 @@ func TestRedisMessage(t *testing.T) { {typ: '*', values: []RedisMessage{ {typ: ':', integer: 0}, {typ: ':', integer: 0}, - {typ: '*', values: []RedisMessage{ // master + {typ: '*', values: []RedisMessage{ {typ: '+', string: "127.0.3.1"}, {typ: ':', integer: 3}, {typ: '+', string: ""}, @@ -1730,6 +1730,26 @@ func TestRedisMessage(t *testing.T) { 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()