Skip to content

Commit

Permalink
fix: return better error messages for grpc-gateway errors (#1397)
Browse files Browse the repository at this point in the history
* fix: return better error messages for grpc-gateway errors

* feat: slip logger in to the special marhsaller, and also add

an integration test for the behavior.
  • Loading branch information
yquansah authored Mar 14, 2023
1 parent 1273bed commit 1628eca
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 20 deletions.
3 changes: 2 additions & 1 deletion internal/cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func registerFunc(ctx context.Context, conn *grpc.ClientConn, fn func(context.Co

func authenticationHTTPMount(
ctx context.Context,
logger *zap.Logger,
cfg config.AuthenticationConfig,
r chi.Router,
conn *grpc.ClientConn,
Expand Down Expand Up @@ -172,6 +173,6 @@ func authenticationHTTPMount(
r.Group(func(r chi.Router) {
r.Use(middleware...)

r.Mount("/auth/v1", gateway.NewGatewayServeMux(muxOpts...))
r.Mount("/auth/v1", gateway.NewGatewayServeMux(logger, muxOpts...))
})
}
4 changes: 2 additions & 2 deletions internal/cmd/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func NewHTTPServer(
isConsole = cfg.Log.Encoding == config.LogEncodingConsole

r = chi.NewRouter()
api = gateway.NewGatewayServeMux()
api = gateway.NewGatewayServeMux(logger)
httpPort = cfg.Server.HTTPPort
)

Expand Down Expand Up @@ -130,7 +130,7 @@ func NewHTTPServer(

// mount all authentication related HTTP components
// to the chi router.
authenticationHTTPMount(ctx, cfg.Authentication, r, conn)
authenticationHTTPMount(ctx, logger, cfg.Authentication, r, conn)

r.Group(func(r chi.Router) {
r.Use(func(handler http.Handler) http.Handler {
Expand Down
35 changes: 22 additions & 13 deletions internal/gateway/gateway.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package gateway

import (
"sync"

"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"go.flipt.io/flipt/rpc/flipt"
"go.uber.org/zap"
"google.golang.org/protobuf/encoding/protojson"
)

Expand All @@ -13,20 +16,26 @@ import (
// See: rpc/flipt/marshal.go
//
// See: https://github.com/flipt-io/flipt/issues/664
var commonMuxOptions = []runtime.ServeMuxOption{
runtime.WithMarshalerOption(runtime.MIMEWildcard, flipt.NewV1toV2MarshallerAdapter()),
runtime.WithMarshalerOption("application/json+pretty", &runtime.JSONPb{
MarshalOptions: protojson.MarshalOptions{
Indent: " ",
Multiline: true, // Optional, implied by presence of "Indent".
},
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
}),
}
var commonMuxOptions []runtime.ServeMuxOption
var once sync.Once

// NewGatewayServeMux builds a new gateway serve mux with common options.
func NewGatewayServeMux(opts ...runtime.ServeMuxOption) *runtime.ServeMux {
func NewGatewayServeMux(logger *zap.Logger, opts ...runtime.ServeMuxOption) *runtime.ServeMux {
once.Do(func() {
commonMuxOptions = []runtime.ServeMuxOption{
runtime.WithMarshalerOption(runtime.MIMEWildcard, flipt.NewV1toV2MarshallerAdapter(logger)),
runtime.WithMarshalerOption("application/json+pretty", &runtime.JSONPb{
MarshalOptions: protojson.MarshalOptions{
Indent: " ",
Multiline: true, // Optional, implied by presence of "Indent".
},
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
}),
}

})

return runtime.NewServeMux(append(commonMuxOptions, opts...)...)
}
2 changes: 1 addition & 1 deletion internal/server/auth/method/kubernetes/testing/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func StartHTTPServer(
t.Helper()

var (
mux = gateway.NewGatewayServeMux()
mux = gateway.NewGatewayServeMux(logger)
httpServer = &HTTPServer{
GRPCServer: StartGRPCServer(t, ctx, logger, conf),
}
Expand Down
1 change: 1 addition & 0 deletions internal/server/auth/method/oidc/testing/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func StartHTTPServer(

oidcmiddleware = oidc.NewHTTPMiddleware(conf.Session)
mux = gateway.NewGatewayServeMux(
logger,
runtime.WithMetadata(oidc.ForwardCookies),
runtime.WithForwardResponseOption(oidcmiddleware.ForwardResponseOption),
)
Expand Down
32 changes: 29 additions & 3 deletions rpc/flipt/marshaller.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package flipt

import (
"encoding/json"
"errors"
"io"

grpc_gateway_v1 "github.com/grpc-ecosystem/grpc-gateway/runtime"
grpc_gateway_v2 "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"go.uber.org/zap"
)

var _ grpc_gateway_v2.Marshaler = &V1toV2MarshallerAdapter{}
Expand All @@ -20,10 +23,11 @@ var _ grpc_gateway_v2.Marshaler = &V1toV2MarshallerAdapter{}
// TODO: remove this custom marshaller for Flipt API v2 as we want to use the default v2 marshaller directly.
type V1toV2MarshallerAdapter struct {
*grpc_gateway_v1.JSONPb
logger *zap.Logger
}

func NewV1toV2MarshallerAdapter() *V1toV2MarshallerAdapter {
return &V1toV2MarshallerAdapter{&grpc_gateway_v1.JSONPb{OrigName: false, EmitDefaults: true}}
func NewV1toV2MarshallerAdapter(logger *zap.Logger) *V1toV2MarshallerAdapter {
return &V1toV2MarshallerAdapter{&grpc_gateway_v1.JSONPb{OrigName: false, EmitDefaults: true}, logger}
}

func (m *V1toV2MarshallerAdapter) ContentType(_ interface{}) string {
Expand All @@ -34,8 +38,30 @@ func (m *V1toV2MarshallerAdapter) Marshal(v interface{}) ([]byte, error) {
return m.JSONPb.Marshal(v)
}

// decoderInterceptor intercepts and modifies the outbound error return value for
// inputs that fail to unmarshal against the protobuf.
type decoderInterceptor struct {
grpc_gateway_v1.Decoder
logger *zap.Logger
}

func (c *decoderInterceptor) Decode(v interface{}) error {
err := c.Decoder.Decode(v)
if err != nil {
c.logger.Debug("JSON decoding failed for inputs", zap.Error(err))

if _, ok := err.(*json.UnmarshalTypeError); ok {
return errors.New("invalid values for key(s) in json body")
}

return err
}

return nil
}

func (m *V1toV2MarshallerAdapter) NewDecoder(r io.Reader) grpc_gateway_v2.Decoder {
return m.JSONPb.NewDecoder(r)
return &decoderInterceptor{Decoder: m.JSONPb.NewDecoder(r), logger: m.logger}
}

func (m *V1toV2MarshallerAdapter) NewEncoder(w io.Writer) grpc_gateway_v2.Encoder {
Expand Down
6 changes: 6 additions & 0 deletions test/api.sh
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ step_5_test_evaluation()
status 200
matches "\"flagKey\":\"$flag_key\""
matches "\"match\":false"

# evaluate returns 400 plus user friendly error message
authedShakedown POST "/api/v1/evaluate" -H 'Content-Type:application/json' -d "{\"flag_key\":\"$flag_key\",\"entity_id\":\"$(uuid_str)\",\"context\":\"hello\"}"
status 400
matches "\"code\":3"
contains "\"message\":\"invalid values for key(s) in json body\""
}

step_6_test_batch_evaluation()
Expand Down

0 comments on commit 1628eca

Please sign in to comment.