Skip to content

Commit facbe19

Browse files
committed
Reproduce issue with client error handling + fix
Signed-off-by: James <539129+jamesrom@users.noreply.github.com>
1 parent 74a6754 commit facbe19

File tree

2 files changed

+77
-1
lines changed

2 files changed

+77
-1
lines changed

client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func NewClient[Req, Res any](httpClient HTTPClient, url string, options ...Clien
9595
response, err := receiveUnaryResponse[Res](conn, config.Initializer)
9696
if err != nil {
9797
_ = conn.CloseResponse()
98-
return nil, err
98+
return response, err
9999
}
100100
return response, conn.CloseResponse()
101101
})

client_error_handling_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2021-2024 The Connect Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package connect
16+
17+
import (
18+
"context"
19+
"errors"
20+
"net/http"
21+
"testing"
22+
23+
"connectrpc.com/connect/internal/assert"
24+
pingv1 "connectrpc.com/connect/internal/gen/connect/ping/v1"
25+
"connectrpc.com/connect/internal/memhttp/memhttptest"
26+
)
27+
28+
var ErrOKToIgnore = errors.New("some error that is ok to ignore")
29+
30+
func TestClientErrorHandling(t *testing.T) {
31+
t.Parallel()
32+
mux := http.NewServeMux()
33+
mux.Handle("/connect.ping.v1.PingService/Ping", NewUnaryHandler(
34+
"/connect.ping.v1.PingService/Ping",
35+
func(ctx context.Context, r *Request[pingv1.PingRequest]) (*Response[pingv1.PingResponse], error) {
36+
return nil, NewError(CodeCanceled, ErrOKToIgnore)
37+
},
38+
))
39+
server := memhttptest.NewServer(t, mux)
40+
41+
client := NewClient[pingv1.PingRequest, pingv1.PingResponse](
42+
server.Client(),
43+
server.URL()+"/connect.ping.v1.PingService/Ping",
44+
WithInterceptors(
45+
errorHidingInterceptor{},
46+
),
47+
)
48+
ctx := context.Background()
49+
50+
_, err := client.CallUnary(ctx, NewRequest[pingv1.PingRequest](nil))
51+
assert.Nil(t, err)
52+
}
53+
54+
// errorHidingInterceptor is a simple interceptor that hides errors based on
55+
// some criteria (in this case, if the error is a CodeCanceled error). It is
56+
// use to reproduce an issue with error handling in the client, where the
57+
// type information is lost between unaryFunc and client.callUnary.
58+
type errorHidingInterceptor struct{}
59+
60+
func (e errorHidingInterceptor) WrapStreamingClient(StreamingClientFunc) StreamingClientFunc {
61+
panic("unimplemented")
62+
}
63+
64+
func (e errorHidingInterceptor) WrapStreamingHandler(StreamingHandlerFunc) StreamingHandlerFunc {
65+
panic("unimplemented")
66+
}
67+
68+
func (e errorHidingInterceptor) WrapUnary(next UnaryFunc) UnaryFunc {
69+
return func(ctx context.Context, req AnyRequest) (_ AnyResponse, retErr error) {
70+
res, err := next(ctx, req)
71+
if CodeOf(err) == CodeCanceled { // some criteria for ignored errors
72+
return res, nil
73+
}
74+
return res, err
75+
}
76+
}

0 commit comments

Comments
 (0)