Skip to content

Commit c29f793

Browse files
igor-tsiglyarIgor Tsiglyar
andauthored
protovalidate: add option to ignore certain message types (#684)
* protovalidate: add option to ignore certain message types * address review comments * adjust stream handler --------- Co-authored-by: Igor Tsiglyar <[email protected]>
1 parent f35f047 commit c29f793

File tree

5 files changed

+92
-11
lines changed

5 files changed

+92
-11
lines changed

interceptors/protovalidate/example_stream_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ func ExampleStreamServerInterceptor() {
2929
var (
3030
srv = grpc.NewServer(
3131
grpc.StreamInterceptor(
32-
protovalidate_middleware.StreamServerInterceptor(validator),
32+
protovalidate_middleware.StreamServerInterceptor(validator,
33+
protovalidate_middleware.WithIgnoreMessages(
34+
(&testvalidatev1.SendStreamRequest{}).ProtoReflect().Type(),
35+
)),
3336
),
3437
)
3538
svc = &StreamService{}

interceptors/protovalidate/example_unary_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ func ExampleUnaryServerInterceptor() {
3030
var (
3131
srv = grpc.NewServer(
3232
grpc.UnaryInterceptor(
33-
protovalidate_middleware.UnaryServerInterceptor(validator),
33+
protovalidate_middleware.UnaryServerInterceptor(validator,
34+
protovalidate_middleware.WithIgnoreMessages(
35+
(&testvalidatev1.SendRequest{}).ProtoReflect().Type(),
36+
),
37+
),
3438
),
3539
)
3640
svc = &UnaryService{}

interceptors/protovalidate/options.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) The go-grpc-middleware Authors.
2+
// Licensed under the Apache License 2.0.
3+
4+
// Copyright 2017 David Ackroyd. All Rights Reserved.
5+
// See LICENSE for licensing terms.
6+
7+
package protovalidate
8+
9+
import (
10+
"golang.org/x/exp/slices"
11+
"google.golang.org/protobuf/reflect/protoreflect"
12+
)
13+
14+
type options struct {
15+
ignoreMessages []protoreflect.MessageType
16+
}
17+
18+
// An Option lets you add options to protovalidate interceptors using With* funcs.
19+
type Option func(*options)
20+
21+
func evaluateOpts(opts []Option) *options {
22+
optCopy := &options{}
23+
for _, o := range opts {
24+
o(optCopy)
25+
}
26+
return optCopy
27+
}
28+
29+
// WithIgnoreMessages sets the messages that should be ignored by the validator. Use with
30+
// caution and ensure validation is performed elsewhere.
31+
func WithIgnoreMessages(msgs ...protoreflect.MessageType) Option {
32+
return func(o *options) {
33+
o.ignoreMessages = msgs
34+
}
35+
}
36+
37+
func (o *options) shouldIgnoreMessage(m protoreflect.MessageType) bool {
38+
return slices.ContainsFunc(o.ignoreMessages, func(t protoreflect.MessageType) bool {
39+
return m == t
40+
})
41+
}

interceptors/protovalidate/protovalidate.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ import (
1414
"google.golang.org/protobuf/proto"
1515
)
1616

17-
// Option interface is currently empty and serves as a placeholder for potential future implementations.
18-
// It allows adding new options without breaking existing code.
19-
type Option interface {
20-
unimplemented()
21-
}
22-
2317
// UnaryServerInterceptor returns a new unary server interceptor that validates incoming messages.
2418
func UnaryServerInterceptor(validator *protovalidate.Validator, opts ...Option) grpc.UnaryServerInterceptor {
2519
return func(
@@ -28,8 +22,12 @@ func UnaryServerInterceptor(validator *protovalidate.Validator, opts ...Option)
2822
info *grpc.UnaryServerInfo,
2923
handler grpc.UnaryHandler,
3024
) (resp interface{}, err error) {
25+
o := evaluateOpts(opts)
3126
switch msg := req.(type) {
3227
case proto.Message:
28+
if o.shouldIgnoreMessage(msg.ProtoReflect().Type()) {
29+
break
30+
}
3331
if err = validator.Validate(msg); err != nil {
3432
return nil, status.Error(codes.InvalidArgument, err.Error())
3533
}
@@ -54,6 +52,7 @@ func StreamServerInterceptor(validator *protovalidate.Validator, opts ...Option)
5452
wrapped := wrapServerStream(stream)
5553
wrapped.wrappedContext = ctx
5654
wrapped.validator = validator
55+
wrapped.options = evaluateOpts(opts)
5756

5857
return handler(srv, wrapped)
5958
}
@@ -64,7 +63,11 @@ func (w *wrappedServerStream) RecvMsg(m interface{}) error {
6463
return err
6564
}
6665

67-
if err := w.validator.Validate(m.(proto.Message)); err != nil {
66+
msg := m.(proto.Message)
67+
if w.options.shouldIgnoreMessage(msg.ProtoReflect().Type()) {
68+
return nil
69+
}
70+
if err := w.validator.Validate(msg); err != nil {
6871
return status.Error(codes.InvalidArgument, err.Error())
6972
}
7073

@@ -78,6 +81,7 @@ type wrappedServerStream struct {
7881
wrappedContext context.Context
7982

8083
validator *protovalidate.Validator
84+
options *options
8185
}
8286

8387
// Context returns the wrapper's WrappedContext, overwriting the nested grpc.ServerStream.Context()

interceptors/protovalidate/protovalidate_test.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"google.golang.org/grpc/credentials/insecure"
2020
"google.golang.org/grpc/status"
2121
"google.golang.org/grpc/test/bufconn"
22+
"google.golang.org/protobuf/reflect/protoreflect"
2223
)
2324

2425
func TestUnaryServerInterceptor(t *testing.T) {
@@ -50,6 +51,20 @@ func TestUnaryServerInterceptor(t *testing.T) {
5051
assert.Error(t, err)
5152
assert.Equal(t, codes.InvalidArgument, status.Code(err))
5253
})
54+
55+
interceptor = protovalidate_middleware.UnaryServerInterceptor(validator,
56+
protovalidate_middleware.WithIgnoreMessages(testvalidate.BadUnaryRequest.ProtoReflect().Type()),
57+
)
58+
59+
t.Run("invalid_email_ignored", func(t *testing.T) {
60+
info := &grpc.UnaryServerInfo{
61+
FullMethod: "FakeMethod",
62+
}
63+
64+
resp, err := interceptor(context.TODO(), testvalidate.BadUnaryRequest, info, handler)
65+
assert.Nil(t, err)
66+
assert.Equal(t, resp, "good")
67+
})
5368
}
5469

5570
type server struct {
@@ -69,15 +84,17 @@ func (g *server) SendStream(
6984

7085
const bufSize = 1024 * 1024
7186

72-
func startGrpcServer(t *testing.T) *grpc.ClientConn {
87+
func startGrpcServer(t *testing.T, ignoreMessages ...protoreflect.MessageType) *grpc.ClientConn {
7388
lis := bufconn.Listen(bufSize)
7489

7590
validator, err := protovalidate.New()
7691
assert.Nil(t, err)
7792

7893
s := grpc.NewServer(
7994
grpc.StreamInterceptor(
80-
protovalidate_middleware.StreamServerInterceptor(validator),
95+
protovalidate_middleware.StreamServerInterceptor(validator,
96+
protovalidate_middleware.WithIgnoreMessages(ignoreMessages...),
97+
),
8198
),
8299
)
83100
testvalidatev1.RegisterTestValidateServiceServer(s, &server{})
@@ -131,4 +148,16 @@ func TestStreamServerInterceptor(t *testing.T) {
131148
assert.Error(t, err)
132149
assert.Equal(t, codes.InvalidArgument, status.Code(err))
133150
})
151+
152+
t.Run("invalid_email_ignored", func(t *testing.T) {
153+
client := testvalidatev1.NewTestValidateServiceClient(
154+
startGrpcServer(t, testvalidate.BadStreamRequest.ProtoReflect().Type()),
155+
)
156+
157+
out, err := client.SendStream(context.Background(), testvalidate.BadStreamRequest)
158+
assert.Nil(t, err)
159+
160+
_, err = out.Recv()
161+
assert.Nil(t, err)
162+
})
134163
}

0 commit comments

Comments
 (0)