Skip to content

Commit

Permalink
feat(errorsgrpc): add unmarshal error unary client interceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
bradub committed Dec 6, 2023
1 parent 3b32668 commit 5ae541d
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 24 deletions.
102 changes: 102 additions & 0 deletions errors/errorsgrpc/errorgrpc_client_interceptor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package errorsgrpc_test

import (
"context"
"net"
"testing"

// nolint: staticcheck
"github.com/golang/protobuf/proto"
"github.com/purposeinplay/go-commons/errors"
"github.com/purposeinplay/go-commons/errors/errorsgrpc"
commonserr "github.com/purposeinplay/go-commons/errors/proto/commons/error/v1"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
"google.golang.org/grpc/status"
"google.golang.org/grpc/test/bufconn"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
t *testing.T
}

// SayHello implements helloworld.GreeterServer.
func (s *server) SayHello(context.Context, *pb.HelloRequest) (*pb.HelloReply, error) {
sts := status.New(codes.NotFound, errors.ErrorTypeNotFound.String())

sts, err := sts.WithDetails(proto.MessageV1(&commonserr.ErrorResponse{
ErrorCode: 1,
Message: "not found",
}))
if err != nil {
s.t.Fatalf("failed to add details to error: %s", err)
}

return nil, sts.Err()
}

func TestErrors(t *testing.T) {
t.Parallel()

req := require.New(t)

const bufSize = 1024 * 1024

var (
lis = bufconn.Listen(bufSize)
bufDialer = func(context.Context, string) (net.Conn, error) { return lis.Dial() }
)

grpcServer := grpc.NewServer()

pb.RegisterGreeterServer(grpcServer, &server{t: t})

t.Cleanup(grpcServer.Stop)

done := make(chan struct{}, 1)

go func() {
defer close(done)

serveErr := grpcServer.Serve(lis)
req.NoError(serveErr)
}()

clientConn, err := grpc.Dial(
"bufnet",
grpc.WithContextDialer(bufDialer),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(errorsgrpc.UnmarshalErrorUnaryClientInterceptor()),
)
req.NoError(err)

t.Cleanup(func() { req.NoError(clientConn.Close()) })

greeterClient := pb.NewGreeterClient(clientConn)

ctx := context.Background()

_, err = greeterClient.SayHello(ctx, &pb.HelloRequest{})

var appErr *errors.Error

req.ErrorAs(err, &appErr)

req.Equal(
&errors.Error{
Type: errors.ErrorTypeNotFound,
Code: 1,
Details: "not found",
},
appErr,
)

grpcServer.Stop()

<-done
}
48 changes: 48 additions & 0 deletions errors/errorsgrpc/errorsgrpc_client_interceptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package errorsgrpc

import (
"context"

"github.com/purposeinplay/go-commons/errors"
commonserr "github.com/purposeinplay/go-commons/errors/proto/commons/error/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)

// UnmarshalErrorUnaryClientInterceptor returns a UnaryClientInterceptor that
// translates grpc error responses to errors.Error.
func UnmarshalErrorUnaryClientInterceptor() grpc.UnaryClientInterceptor {
return func(
ctx context.Context,
method string,
req, reply any,
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
err := invoker(ctx, method, req, reply, cc, opts...)
if err == nil {
return nil
}

sts, ok := status.FromError(err)
if !ok {
return err
}

if len(sts.Details()) != 1 {
return err
}

errResp, ok := sts.Details()[0].(*commonserr.ErrorResponse)
if !ok {
return err
}

return &errors.Error{
Type: errors.ErrorType(sts.Message()),
Code: errors.ErrorCode(errResp.ErrorCode),
Details: errResp.Message,
}
}
}
File renamed without changes.
File renamed without changes.
11 changes: 7 additions & 4 deletions errors/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ require (
github.com/golang/protobuf v1.5.3
github.com/stretchr/testify v1.8.1
go.uber.org/zap v1.26.0
google.golang.org/grpc v1.58.3
google.golang.org/grpc v1.59.0
google.golang.org/grpc/examples v0.0.0-20231205201002-0866ce06badc
google.golang.org/protobuf v1.31.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/net v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
24 changes: 14 additions & 10 deletions errors/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 10 additions & 10 deletions grpc/gateway_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ func (s *gatewayServer) close() error {

// nolint: revive // false-positive, it reports tracing as a control flag.
func newGatewayServer(
muxOptions []runtime.ServeMuxOption,
tracing bool,
registerGateway registerGatewayFunc,
address string,
httpRoutes []httpRoute,
middlewares chi.Middlewares,
debugStandardLibraryEndpoints bool,
corsOptions cors.Options,
muxOptions []runtime.ServeMuxOption,
tracing bool,
registerGateway registerGatewayFunc,
address string,
httpRoutes []httpRoute,
middlewares chi.Middlewares,
debugStandardLibraryEndpoints bool,
corsOptions cors.Options,
) (
*gatewayServer,
error,
*gatewayServer,
error,
) {
grpcGatewayMux := runtime.NewServeMux(
muxOptions...,
Expand Down

0 comments on commit 5ae541d

Please sign in to comment.