Skip to content

Commit

Permalink
Add possibility enable emit unpopulated and default values.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michal Ližičiar committed Aug 12, 2024
1 parent 4a01b40 commit 01cae55
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 8 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Usage of ./grpc-rest-proxy:
--transport.http.server.gracefulTimeout duration graceful timeout (default 5s)
--transport.http.server.readHeaderTimeout duration read header timeout (default 5s)
--transport.http.server.readTimeout duration read timeout (default 10s)
--service.jsonencoder.emitUnpopulated emit unpopulated fields
--service.jsonencoder.emitDefaultValues emit default values
-v, --version print version
```

Expand Down
2 changes: 2 additions & 0 deletions cmd/service/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

grpcClient "github.com/eset/grpc-rest-proxy/pkg/gateway/grpc"
"github.com/eset/grpc-rest-proxy/pkg/repository/descriptors"
"github.com/eset/grpc-rest-proxy/pkg/service/jsonencoder"
"github.com/eset/grpc-rest-proxy/pkg/service/protoparser"
"github.com/eset/grpc-rest-proxy/pkg/transport"
"github.com/eset/grpc-rest-proxy/pkg/transport/http"
Expand Down Expand Up @@ -74,6 +75,7 @@ func (app *App) createHTTPServer() {
routerContext := &transport.Context{
Router: app.router,
GrcpClient: app.gateways.grpcClient,
Encoder: jsonencoder.NewOptions(app.conf.Service.JSONEncoder),
}
handler := transport.NewHandler(routerContext, logging.Default())
app.serverHTTP = http.NewServer(app.conf.Transport.HTTP.Server, handler)
Expand Down
6 changes: 6 additions & 0 deletions cmd/service/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/eset/grpc-rest-proxy/pkg/gateway/grpc"
"github.com/eset/grpc-rest-proxy/pkg/repository/descriptors"
"github.com/eset/grpc-rest-proxy/pkg/service/jsonencoder"
"github.com/eset/grpc-rest-proxy/pkg/transport"

"github.com/go-playground/validator/v10"
Expand All @@ -20,12 +21,17 @@ type Config struct {
Transport *transport.Config `mapstructure:"transport" validate:"required"`
Descriptors *descriptors.Config `mapstructure:"descriptors" validate:"required"`
Gateways *Gateway `mapstructure:"gateways" validate:"required"`
Service *Service `mapstructure:"service" validate:"required"`
}

type Gateway struct {
GrpcClientConfig *grpc.ClientConfig `mapstructure:"grpc"`
}

type Service struct {
JSONEncoder *jsonencoder.Config `mapstructure:"jsonencoder"`
}

func (c *Config) validate() error {
validate := validator.New()
if err := validate.Struct(c); err != nil {
Expand Down
6 changes: 6 additions & 0 deletions cmd/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (
grpcServerAddr = "0.0.0.0:50051"
tls = false
tlsSkipverify = false
defaultEmitUnpopulated = false
defaultEmitDefaultValues = false
)

var (
Expand Down Expand Up @@ -58,6 +60,10 @@ func main() {
pflag.Duration("gateways.grpc.client.requestTimeout", defaultRequestTimeout, "requests timeout")
pflag.Bool("gateways.grpc.client.tls", tls, "use TLS for gRPC connection")
pflag.Bool("gateways.grpc.client.tlsSkipverify", tlsSkipverify, "skip TLS verification")

pflag.Bool("service.jsonencoder.emitUnpopulated", defaultEmitUnpopulated, "emit unpopulated fields")
pflag.Bool("service.jsonencoder.emitDefaultValues", defaultEmitDefaultValues, "emit default values")

pflag.BoolP("version", "v", false, "print version")
configFile := pflag.StringP("config", "c", "", "path to config file")

Expand Down
32 changes: 32 additions & 0 deletions pkg/service/jsonencoder/encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package jsonencoder

import (
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"

jErrors "github.com/juju/errors"
)

type Config struct {
EmitUnpopulated bool `mapstructure:"emitUnpopulated"`
EmitDefaultValues bool `mapstructure:"emitDefaultValues"`
}

type Encoder struct {
opts protojson.MarshalOptions
}

func NewOptions(cfg *Config) Encoder {
return Encoder{
opts: protojson.MarshalOptions{EmitUnpopulated: cfg.EmitUnpopulated, EmitDefaultValues: cfg.EmitDefaultValues},
}
}

func (e Encoder) Format(m proto.Message) (string, error) {
response, err := e.opts.Marshal(m)
if err != nil {
return "", jErrors.Trace(err)
}

return string(response), nil
}
23 changes: 15 additions & 8 deletions pkg/transport/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

grpcClient "github.com/eset/grpc-rest-proxy/pkg/gateway/grpc"
"github.com/eset/grpc-rest-proxy/pkg/service/jsonencoder"
"github.com/eset/grpc-rest-proxy/pkg/service/transformer"
routerPkg "github.com/eset/grpc-rest-proxy/pkg/transport/router"

Expand All @@ -22,13 +23,13 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/types/dynamicpb"
)

type Context struct {
Router *routerPkg.ReloadableRouter
GrcpClient grpcClient.ClientInterface
Encoder jsonencoder.Encoder
}

type Logger interface {
Expand Down Expand Up @@ -57,10 +58,10 @@ func getQueryVariables(queryValues url.Values) []transformer.Variable {
return queryVariables
}

func convertRequestToGRPC(route *routerPkg.Match, r *http.Request) (req *dynamicpb.Message, resp *dynamicpb.Message, err error) {
func convertRequestToGRPC(route *routerPkg.Match, r *http.Request) (req *dynamicpb.Message, err error) {
reqBody, err := io.ReadAll(r.Body)
if err != nil {
return nil, nil, jErrors.Trace(err)
return nil, jErrors.Trace(err)
}
r.Body.Close()

Expand All @@ -69,11 +70,10 @@ func convertRequestToGRPC(route *routerPkg.Match, r *http.Request) (req *dynamic

req, err = transformer.GetRPCRequest(reqBody, route.GrpcSpec.RequestDesc, route.Params, route.BodyRule)
if err != nil {
return nil, nil, jErrors.Trace(err)
return nil, jErrors.Trace(err)
}
resp = transformer.GetRPCResponse(route.GrpcSpec.ResponseDesc)

return req, resp, nil
return req, nil
}

func createRoutingEndpoint(rc *Context, logger Logger) func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -90,12 +90,13 @@ func createRoutingEndpoint(rc *Context, logger Logger) func(w http.ResponseWrite
return
}

rpcRequest, rpcResponse, err := convertRequestToGRPC(routeMatch, r)
rpcRequest, err := convertRequestToGRPC(routeMatch, r)
if err != nil {
logger.ErrorContext(r.Context(), jErrors.Details(jErrors.Trace(err)))
w.WriteHeader(http.StatusBadRequest)
return
}
rpcResponse := transformer.GetRPCResponse(routeMatch.GrpcSpec.ResponseDesc)

var header, trailer metadata.MD
err = rc.GrcpClient.Invoke(
Expand All @@ -116,7 +117,13 @@ func createRoutingEndpoint(rc *Context, logger Logger) func(w http.ResponseWrite
}

transformer.SetRESTHeaders(w.Header(), header, trailer)
fmt.Fprint(w, protojson.Format(rpcResponse))
response, err := rc.Encoder.Format(rpcResponse)
if err != nil {
logger.ErrorContext(r.Context(), jErrors.Details(jErrors.Trace(err)))
w.WriteHeader(http.StatusInternalServerError)
}

fmt.Fprint(w, response)
w.WriteHeader(http.StatusOK)
}
}
Expand Down

0 comments on commit 01cae55

Please sign in to comment.