Skip to content

Commit

Permalink
refactor: simplify connect Resolve... funcs (#391)
Browse files Browse the repository at this point in the history
## This PR
<!-- add the description of the PR here -->

- Reduces duplicated code in Resolve... funcs

### Related Issues
<!-- add here the GitHub issue that this PR resolves if applicable -->

Fixes #385 

### Notes
<!-- any additional notes for this PR -->

### Follow-up Tasks
<!-- anything that is related to this PR but not done here should be
noted under this section -->
<!-- if there is a need for a new issue, please link it here -->

### How to test
<!-- if applicable, add testing instructions under this section -->
Tested against integration tests.

---------

Signed-off-by: Skye Gill <[email protected]>
  • Loading branch information
skyerus authored Feb 10, 2023
1 parent c2a1d53 commit feebbe2
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 136 deletions.
176 changes: 51 additions & 125 deletions pkg/service/connect_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,169 +245,95 @@ func (s *ConnectService) Notify(n Notification) {
}
}

func (s *ConnectService) ResolveBoolean(
ctx context.Context,
req *connect.Request[schemaV1.ResolveBooleanRequest],
) (*connect.Response[schemaV1.ResolveBooleanResponse], error) {
func resolve[T constraints](
logger *logger.Logger,
resolver func(reqID, flagKey string, ctx *structpb.Struct) (T, string, string, error),
flagKey string,
ctx *structpb.Struct,
resp response[T],
) error {
reqID := xid.New().String()
defer s.Logger.ClearFields(reqID)
s.Logger.WriteFields(
defer logger.ClearFields(reqID)

logger.WriteFields(
reqID,
zap.String("flag-key", req.Msg.GetFlagKey()),
zap.Strings("context-keys", formatContextKeys(req.Msg.GetContext())),
zap.String("flag-key", flagKey),
zap.Strings("context-keys", formatContextKeys(ctx)),
)
s.Logger.DebugWithID(reqID, "boolean flag value requested")

res := connect.NewResponse(&schemaV1.ResolveBooleanResponse{})
result, variant, reason, err := s.Eval.ResolveBooleanValue(reqID, req.Msg.GetFlagKey(), req.Msg.GetContext())
if err != nil {
s.Logger.WarnWithID(reqID, fmt.Sprintf("returning error response, reason: %s", err.Error()))
res.Msg.Reason = model.ErrorReason
return res, errFormat(err)
result, variant, reason, evalErr := resolver(reqID, flagKey, ctx)
if evalErr != nil {
logger.WarnWithID(reqID, fmt.Sprintf("returning error response, reason: %v", evalErr))
reason = model.ErrorReason
evalErr = errFormat(evalErr)
}

s.Logger.DebugWithID(reqID, fmt.Sprintf("flag evaluation response: result: %t, variant: %s, reason: %s",
result,
variant,
reason,
))
res.Msg.Reason = reason
res.Msg.Value = result
res.Msg.Variant = variant
return res, nil
if err := resp.SetResult(result, variant, reason); err != nil && evalErr == nil {
logger.ErrorWithID(reqID, err.Error())
return err
}

return evalErr
}

func (s *ConnectService) ResolveBoolean(
ctx context.Context,
req *connect.Request[schemaV1.ResolveBooleanRequest],
) (*connect.Response[schemaV1.ResolveBooleanResponse], error) {
res := connect.NewResponse(&schemaV1.ResolveBooleanResponse{})
err := resolve[bool](
s.Logger, s.Eval.ResolveBooleanValue, req.Msg.GetFlagKey(), req.Msg.GetContext(), &booleanResponse{res},
)

return res, err
}

func (s *ConnectService) ResolveString(
ctx context.Context,
req *connect.Request[schemaV1.ResolveStringRequest],
) (*connect.Response[schemaV1.ResolveStringResponse], error) {
reqID := xid.New().String()
defer s.Logger.ClearFields(reqID)
s.Logger.WriteFields(
reqID,
zap.String("flag-key", req.Msg.GetFlagKey()),
zap.Strings("context-keys", formatContextKeys(req.Msg.GetContext())),
)
s.Logger.DebugWithID(reqID, "string flag value requested")

res := connect.NewResponse(&schemaV1.ResolveStringResponse{})
result, variant, reason, err := s.Eval.ResolveStringValue(reqID, req.Msg.GetFlagKey(), req.Msg.GetContext())
if err != nil {
s.Logger.WarnWithID(reqID, fmt.Sprintf("returning error response, reason: %s", err.Error()))
res.Msg.Reason = model.ErrorReason
return res, errFormat(err)
}
err := resolve[string](
s.Logger, s.Eval.ResolveStringValue, req.Msg.GetFlagKey(), req.Msg.GetContext(), &stringResponse{res},
)

s.Logger.DebugWithID(reqID, fmt.Sprintf("flag evaluation response: result: %s, variant: %s, reason: %s",
result,
variant,
reason,
))
res.Msg.Reason = reason
res.Msg.Value = result
res.Msg.Variant = variant
return res, nil
return res, err
}

func (s *ConnectService) ResolveInt(
ctx context.Context,
req *connect.Request[schemaV1.ResolveIntRequest],
) (*connect.Response[schemaV1.ResolveIntResponse], error) {
reqID := xid.New().String()
defer s.Logger.ClearFields(reqID)
s.Logger.WriteFields(
reqID,
zap.String("flag-key", req.Msg.GetFlagKey()),
zap.Strings("context-keys", formatContextKeys(req.Msg.GetContext())),
)
s.Logger.DebugWithID(reqID, "int flag value requested")

res := connect.NewResponse(&schemaV1.ResolveIntResponse{})
result, variant, reason, err := s.Eval.ResolveIntValue(reqID, req.Msg.GetFlagKey(), req.Msg.GetContext())
if err != nil {
s.Logger.WarnWithID(reqID, fmt.Sprintf("returning error response, reason: %s", err.Error()))
res.Msg.Reason = model.ErrorReason
return res, errFormat(err)
}
err := resolve[int64](
s.Logger, s.Eval.ResolveIntValue, req.Msg.GetFlagKey(), req.Msg.GetContext(), &intResponse{res},
)

s.Logger.DebugWithID(reqID, fmt.Sprintf("flag evaluation response: result: %d, variant: %s, reason: %s",
result,
variant,
reason,
))
res.Msg.Reason = reason
res.Msg.Value = result
res.Msg.Variant = variant
return res, nil
return res, err
}

func (s *ConnectService) ResolveFloat(
ctx context.Context,
req *connect.Request[schemaV1.ResolveFloatRequest],
) (*connect.Response[schemaV1.ResolveFloatResponse], error) {
reqID := xid.New().String()
defer s.Logger.ClearFields(reqID)
s.Logger.WriteFields(
reqID,
zap.String("flag-key", req.Msg.GetFlagKey()),
zap.Strings("context-keys", formatContextKeys(req.Msg.GetContext())),
)
s.Logger.DebugWithID(reqID, "float flag value requested")

res := connect.NewResponse(&schemaV1.ResolveFloatResponse{})
result, variant, reason, err := s.Eval.ResolveFloatValue(reqID, req.Msg.GetFlagKey(), req.Msg.GetContext())
if err != nil {
s.Logger.WarnWithID(reqID, fmt.Sprintf("returning error response, reason: %s", err.Error()))
res.Msg.Reason = model.ErrorReason
return res, errFormat(err)
}
err := resolve[float64](
s.Logger, s.Eval.ResolveFloatValue, req.Msg.GetFlagKey(), req.Msg.GetContext(), &floatResponse{res},
)

s.Logger.DebugWithID(reqID, fmt.Sprintf("flag evaluation response: result: %64f, variant: %s, reason: %s",
result,
variant,
reason,
))
res.Msg.Reason = reason
res.Msg.Value = result
res.Msg.Variant = variant
return res, nil
return res, err
}

func (s *ConnectService) ResolveObject(
ctx context.Context,
req *connect.Request[schemaV1.ResolveObjectRequest],
) (*connect.Response[schemaV1.ResolveObjectResponse], error) {
reqID := xid.New().String()
defer s.Logger.ClearFields(reqID)
s.Logger.WriteFields(
reqID,
zap.String("flag-key", req.Msg.GetFlagKey()),
zap.Strings("context-keys", formatContextKeys(req.Msg.GetContext())),
)
s.Logger.DebugWithID(reqID, "object flag value requested")

res := connect.NewResponse(&schemaV1.ResolveObjectResponse{})
result, variant, reason, err := s.Eval.ResolveObjectValue(reqID, req.Msg.GetFlagKey(), req.Msg.GetContext())
if err != nil {
s.Logger.WarnWithID(reqID, fmt.Sprintf("returning error response, reason: %s", err.Error()))
res.Msg.Reason = model.ErrorReason
return res, errFormat(err)
}
val, err := structpb.NewStruct(result)
if err != nil {
s.Logger.ErrorWithID(reqID, fmt.Sprintf("struct response construction: %v", err))
return res, err
}
err := resolve[map[string]any](
s.Logger, s.Eval.ResolveObjectValue, req.Msg.GetFlagKey(), req.Msg.GetContext(), &objectResponse{res},
)

s.Logger.DebugWithID(reqID, fmt.Sprintf("flag evaluation response: result: %v, variant: %s, reason: %s",
result,
variant,
reason,
))
res.Msg.Reason = reason
res.Msg.Value = val
res.Msg.Variant = variant
return res, nil
return res, err
}

func formatContextKeys(context *structpb.Struct) []string {
Expand Down
29 changes: 18 additions & 11 deletions pkg/service/connect_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,9 @@ func TestConnectService_ResolveBoolean(t *testing.T) {
},
},
want: &schemaV1.ResolveBooleanResponse{
Reason: model.ErrorReason,
Value: true,
Variant: ":(",
Reason: model.ErrorReason,
},
wantErr: errors.New("eval interface error"),
},
Expand Down Expand Up @@ -394,7 +396,9 @@ func TestConnectService_ResolveString(t *testing.T) {
},
},
want: &schemaV1.ResolveStringResponse{
Reason: model.ErrorReason,
Value: "true",
Variant: ":(",
Reason: model.ErrorReason,
},
wantErr: errors.New("eval interface error"),
},
Expand Down Expand Up @@ -529,7 +533,9 @@ func TestConnectService_ResolveFloat(t *testing.T) {
},
},
want: &schemaV1.ResolveFloatResponse{
Reason: model.ErrorReason,
Value: 12,
Variant: ":(",
Reason: model.ErrorReason,
},
wantErr: errors.New("eval interface error"),
},
Expand Down Expand Up @@ -664,7 +670,9 @@ func TestConnectService_ResolveInt(t *testing.T) {
},
},
want: &schemaV1.ResolveIntResponse{
Reason: model.ErrorReason,
Value: 12,
Variant: ":(",
Reason: model.ErrorReason,
},
wantErr: errors.New("eval interface error"),
},
Expand Down Expand Up @@ -803,7 +811,8 @@ func TestConnectService_ResolveObject(t *testing.T) {
},
},
want: &schemaV1.ResolveObjectResponse{
Reason: model.ErrorReason,
Variant: ":(",
Reason: model.ErrorReason,
},
wantErr: errors.New("eval interface error"),
},
Expand All @@ -822,13 +831,11 @@ func TestConnectService_ResolveObject(t *testing.T) {
Logger: logger.NewLogger(nil, false),
}

if name != "eval returns error" {
outParsed, err := structpb.NewStruct(tt.evalFields.result)
if err != nil {
t.Error(err)
}
tt.want.Value = outParsed
outParsed, err := structpb.NewStruct(tt.evalFields.result)
if err != nil {
t.Error(err)
}
tt.want.Value = outParsed
got, err := s.ResolveObject(tt.functionArgs.ctx, connect.NewRequest(tt.functionArgs.req))
if (err != nil) && !errors.Is(err, tt.wantErr) {
t.Errorf("ConnectService.ResolveObject() error = %v, wantErr %v", err, tt.wantErr)
Expand Down
77 changes: 77 additions & 0 deletions pkg/service/connect_service_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package service

import (
"fmt"

schemaV1 "buf.build/gen/go/open-feature/flagd/protocolbuffers/go/schema/v1"
"github.com/bufbuild/connect-go"
"google.golang.org/protobuf/types/known/structpb"
)

type response[T constraints] interface {
SetResult(value T, variant, reason string) error
}

type constraints interface {
bool | string | map[string]any | float64 | int64
}

type booleanResponse struct {
*connect.Response[schemaV1.ResolveBooleanResponse]
}

func (r *booleanResponse) SetResult(value bool, variant, reason string) error {
r.Msg.Value = value
r.Msg.Variant = variant
r.Msg.Reason = reason
return nil
}

type stringResponse struct {
*connect.Response[schemaV1.ResolveStringResponse]
}

func (r *stringResponse) SetResult(value, variant, reason string) error {
r.Msg.Value = value
r.Msg.Variant = variant
r.Msg.Reason = reason
return nil
}

type floatResponse struct {
*connect.Response[schemaV1.ResolveFloatResponse]
}

func (r *floatResponse) SetResult(value float64, variant, reason string) error {
r.Msg.Value = value
r.Msg.Variant = variant
r.Msg.Reason = reason
return nil
}

type intResponse struct {
*connect.Response[schemaV1.ResolveIntResponse]
}

func (r *intResponse) SetResult(value int64, variant, reason string) error {
r.Msg.Value = value
r.Msg.Variant = variant
r.Msg.Reason = reason
return nil
}

type objectResponse struct {
*connect.Response[schemaV1.ResolveObjectResponse]
}

func (r *objectResponse) SetResult(value map[string]any, variant, reason string) error {
r.Msg.Reason = reason
val, err := structpb.NewStruct(value)
if err != nil {
return fmt.Errorf("struct response construction: %w", err)
}

r.Msg.Value = val
r.Msg.Variant = variant
return nil
}

0 comments on commit feebbe2

Please sign in to comment.