Skip to content

Commit 9d9a96f

Browse files
authored
peer and metadata: Implement the Stringer interface for Peer and Metadata (#7137)
1 parent 911d549 commit 9d9a96f

File tree

4 files changed

+169
-0
lines changed

4 files changed

+169
-0
lines changed

metadata/metadata.go

+15
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ func Pairs(kv ...string) MD {
9090
return md
9191
}
9292

93+
// String implements the Stringer interface for pretty-printing a MD.
94+
// Ordering of the values is non-deterministic as it ranges over a map.
95+
func (md MD) String() string {
96+
var sb strings.Builder
97+
fmt.Fprintf(&sb, "MD{")
98+
for k, v := range md {
99+
if sb.Len() > 3 {
100+
fmt.Fprintf(&sb, ", ")
101+
}
102+
fmt.Fprintf(&sb, "%s=[%s]", k, strings.Join(v, ", "))
103+
}
104+
fmt.Fprintf(&sb, "}")
105+
return sb.String()
106+
}
107+
93108
// Len returns the number of items in md.
94109
func (md MD) Len() int {
95110
return len(md)

metadata/metadata_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"context"
2323
"reflect"
2424
"strconv"
25+
"strings"
2526
"testing"
2627
"time"
2728

@@ -338,6 +339,26 @@ func (s) TestAppendToOutgoingContext_FromKVSlice(t *testing.T) {
338339
}
339340
}
340341

342+
func TestStringerMD(t *testing.T) {
343+
for _, test := range []struct {
344+
md MD
345+
want []string
346+
}{
347+
{MD{}, []string{"MD{}"}},
348+
{MD{"k1": []string{}}, []string{"MD{k1=[]}"}},
349+
{MD{"k1": []string{"v1", "v2"}}, []string{"MD{k1=[v1, v2]}"}},
350+
{MD{"k1": []string{"v1"}}, []string{"MD{k1=[v1]}"}},
351+
{MD{"k1": []string{"v1", "v2"}, "k2": []string{}, "k3": []string{"1", "2", "3"}}, []string{"MD{", "k1=[v1, v2]", "k2=[]", "k3=[1, 2, 3]", "}"}},
352+
} {
353+
got := test.md.String()
354+
for _, want := range test.want {
355+
if !strings.Contains(got, want) {
356+
t.Fatalf("Metadata string %q is missing %q", got, want)
357+
}
358+
}
359+
}
360+
}
361+
341362
// Old/slow approach to adding metadata to context
342363
func Benchmark_AddingMetadata_ContextManipulationApproach(b *testing.B) {
343364
// TODO: Add in N=1-100 tests once Go1.6 support is removed.

peer/peer.go

+30
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ package peer
2222

2323
import (
2424
"context"
25+
"fmt"
2526
"net"
27+
"strings"
2628

2729
"google.golang.org/grpc/credentials"
2830
)
@@ -39,6 +41,34 @@ type Peer struct {
3941
AuthInfo credentials.AuthInfo
4042
}
4143

44+
// String ensures the Peer types implements the Stringer interface in order to
45+
// allow to print a context with a peerKey value effectively.
46+
func (p *Peer) String() string {
47+
if p == nil {
48+
return "Peer<nil>"
49+
}
50+
sb := &strings.Builder{}
51+
sb.WriteString("Peer{")
52+
if p.Addr != nil {
53+
fmt.Fprintf(sb, "Addr: '%s', ", p.Addr.String())
54+
} else {
55+
fmt.Fprintf(sb, "Addr: <nil>, ")
56+
}
57+
if p.LocalAddr != nil {
58+
fmt.Fprintf(sb, "LocalAddr: '%s', ", p.LocalAddr.String())
59+
} else {
60+
fmt.Fprintf(sb, "LocalAddr: <nil>, ")
61+
}
62+
if p.AuthInfo != nil {
63+
fmt.Fprintf(sb, "AuthInfo: '%s'", p.AuthInfo.AuthType())
64+
} else {
65+
fmt.Fprintf(sb, "AuthInfo: <nil>")
66+
}
67+
sb.WriteString("}")
68+
69+
return sb.String()
70+
}
71+
4272
type peerKey struct{}
4373

4474
// NewContext creates a new context with peer information attached.

peer/peer_test.go

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
*
3+
* Copyright 2024 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package peer
20+
21+
import (
22+
"context"
23+
"fmt"
24+
"testing"
25+
26+
"google.golang.org/grpc/credentials"
27+
)
28+
29+
// A struct that implements AuthInfo interface and implements CommonAuthInfo() method.
30+
type testAuthInfo struct {
31+
credentials.CommonAuthInfo
32+
}
33+
34+
func (ta testAuthInfo) AuthType() string {
35+
return fmt.Sprintf("testAuthInfo-%d", ta.SecurityLevel)
36+
}
37+
38+
type addr struct {
39+
ipAddress string
40+
}
41+
42+
func (addr) Network() string { return "" }
43+
44+
func (a *addr) String() string { return a.ipAddress }
45+
46+
func TestPeerStringer(t *testing.T) {
47+
testCases := []struct {
48+
name string
49+
peer *Peer
50+
want string
51+
}{
52+
{
53+
name: "+Addr-LocalAddr+ValidAuth",
54+
peer: &Peer{Addr: &addr{"example.com:1234"}, AuthInfo: testAuthInfo{credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity}}},
55+
want: "Peer{Addr: 'example.com:1234', LocalAddr: <nil>, AuthInfo: 'testAuthInfo-3'}",
56+
},
57+
{
58+
name: "+Addr+LocalAddr+ValidAuth",
59+
peer: &Peer{Addr: &addr{"example.com:1234"}, LocalAddr: &addr{"example.com:1234"}, AuthInfo: testAuthInfo{credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity}}},
60+
want: "Peer{Addr: 'example.com:1234', LocalAddr: 'example.com:1234', AuthInfo: 'testAuthInfo-3'}",
61+
},
62+
{
63+
name: "+Addr-LocalAddr+emptyAuth",
64+
peer: &Peer{Addr: &addr{"1.2.3.4:1234"}, AuthInfo: testAuthInfo{credentials.CommonAuthInfo{}}},
65+
want: "Peer{Addr: '1.2.3.4:1234', LocalAddr: <nil>, AuthInfo: 'testAuthInfo-0'}",
66+
},
67+
{
68+
name: "-Addr-LocalAddr+emptyAuth",
69+
peer: &Peer{AuthInfo: testAuthInfo{}},
70+
want: "Peer{Addr: <nil>, LocalAddr: <nil>, AuthInfo: 'testAuthInfo-0'}",
71+
},
72+
{
73+
name: "zeroedPeer",
74+
peer: &Peer{},
75+
want: "Peer{Addr: <nil>, LocalAddr: <nil>, AuthInfo: <nil>}",
76+
},
77+
{
78+
name: "nilPeer",
79+
peer: nil,
80+
want: "Peer<nil>",
81+
},
82+
}
83+
for _, tc := range testCases {
84+
t.Run(tc.name, func(t *testing.T) {
85+
ctx := NewContext(context.Background(), tc.peer)
86+
p, ok := FromContext(ctx)
87+
if !ok {
88+
t.Fatalf("Unable to get peer from context")
89+
}
90+
if p.String() != tc.want {
91+
t.Fatalf("Error using peer String(): expected %q, got %q", tc.want, p.String())
92+
}
93+
})
94+
}
95+
}
96+
97+
func TestPeerStringerOnContext(t *testing.T) {
98+
ctx := NewContext(context.Background(), &Peer{Addr: &addr{"1.2.3.4:1234"}, AuthInfo: testAuthInfo{credentials.CommonAuthInfo{SecurityLevel: credentials.PrivacyAndIntegrity}}})
99+
want := "context.Background.WithValue(type peer.peerKey, val Peer{Addr: '1.2.3.4:1234', LocalAddr: <nil>, AuthInfo: 'testAuthInfo-3'})"
100+
if got := fmt.Sprintf("%v", ctx); got != want {
101+
t.Fatalf("Unexpected stringer output, got: %q; want: %q", got, want)
102+
}
103+
}

0 commit comments

Comments
 (0)