Skip to content

Commit

Permalink
Add evaluation context support
Browse files Browse the repository at this point in the history
  • Loading branch information
khvn26 committed Aug 2, 2024
1 parent 7b73f08 commit ec66647
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 79 deletions.
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.EXPORT_ALL_VARIABLES:

EVALUATION_CONTEXT_SCHEMA_URL ?= https://raw.githubusercontent.com/Flagsmith/flagsmith/feat/evaluation-context-schema/sdk/evaluation-context.json


.PHONY: generate-evaluation-context
generate-evaluation-context:
npx quicktype ${EVALUATION_CONTEXT_SCHEMA_URL} --src-lang schema --lang go --package flagsmith --omit-empty --just-types-and-package > evaluationcontext.go
78 changes: 62 additions & 16 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ import (
"github.com/Flagsmith/flagsmith-go-client/v3/flagengine/segments"
"github.com/Flagsmith/flagsmith-go-client/v3/internal/flaghttp"

. "github.com/Flagsmith/flagsmith-go-client/v3/flagengine/identities/traits"
enginetraits "github.com/Flagsmith/flagsmith-go-client/v3/flagengine/identities/traits"
)

type contextKey string

var contextKeyEvaluationContext = contextKey("evaluationContext")

// Client provides various methods to query Flagsmith API.
type Client struct {
apiKey string
Expand Down Expand Up @@ -44,8 +48,8 @@ func NewClient(apiKey string, options ...Option) *Client {
}

c.client.SetHeaders(map[string]string{
"Accept": "application/json",
"X-Environment-Key": c.apiKey,
"Accept": "application/json",
flaghttp.EnvironmentKeyHeader: c.apiKey,
})
c.client.SetTimeout(c.config.timeout)
c.log = createLogger()
Expand Down Expand Up @@ -87,9 +91,34 @@ func NewClient(apiKey string, options ...Option) *Client {

// Returns `Flags` struct holding all the flags for the current environment.
//
// Provide `EvaluationContext` to evaluate flags for a specific environment or identity.
//
// If local evaluation is enabled this function will not call the Flagsmith API
// directly, but instead read the asynchronously updated local environment or
// use the default flag handler in case it has not yet been updated.
//
// Notes:
//
// * `EvaluationContext.Environment` is ignored in local evaluation mode.
//
// * `EvaluationContext.Feature` is not yet supported.
func (c *Client) GetFlags(ctx context.Context, ec *EvaluationContext) (f Flags, err error) {
if ec != nil {
ctx = context.WithValue(ctx, contextKeyEvaluationContext, ec)
if ec.Identity != nil {
return c.GetIdentityFlags(ctx, ec.Identity.Identifier, mapIdentityEvaluationContextToTraits(*ec.Identity))
}
}
return c.GetEnvironmentFlags(ctx)
}

// Returns `Flags` struct holding all the flags for the current environment.
//
// If local evaluation is enabled this function will not call the Flagsmith API
// directly, but instead read the asynchronously updated local environment or
// use the default flag handler in case it has not yet been updated.
//
// Deprecated: Use `GetFlags` instead.
func (c *Client) GetEnvironmentFlags(ctx context.Context) (f Flags, err error) {
if c.config.localEvaluation || c.config.offlineMode {
if f, err = c.getEnvironmentFlagsFromEnvironment(); err == nil {
Expand All @@ -108,10 +137,6 @@ func (c *Client) GetEnvironmentFlags(ctx context.Context) (f Flags, err error) {
return Flags{}, &FlagsmithClientError{msg: fmt.Sprintf("Failed to fetch flags with error: %s", err)}
}

type GetIdentityFlagsOpts struct {
Transient bool `json:"transient,omitempty"`
}

// Returns `Flags` struct holding all the flags for the current environment for
// a given identity.
//
Expand All @@ -122,13 +147,15 @@ type GetIdentityFlagsOpts struct {
// If local evaluation is enabled this function will not call the Flagsmith API
// directly, but instead read the asynchronously updated local environment or
// use the default flag handler in case it has not yet been updated.
func (c *Client) GetIdentityFlags(ctx context.Context, identifier string, traits []*Trait, opts *GetIdentityFlagsOpts) (f Flags, err error) {
//
// Deprecated: Use `GetFlags` providing `EvaluationContext.Identity` instead.
func (c *Client) GetIdentityFlags(ctx context.Context, identifier string, traits []*Trait) (f Flags, err error) {
if c.config.localEvaluation || c.config.offlineMode {
if f, err = c.getIdentityFlagsFromEnvironment(identifier, traits); err == nil {
return f, nil
}
} else {
if f, err = c.GetIdentityFlagsFromAPI(ctx, identifier, traits, opts); err == nil {
if f, err = c.GetIdentityFlagsFromAPI(ctx, identifier, traits); err == nil {
return f, nil
}
}
Expand Down Expand Up @@ -180,7 +207,15 @@ func (c *Client) BulkIdentify(ctx context.Context, batch []*IdentityTraits) erro
// GetEnvironmentFlagsFromAPI tries to contact the Flagsmith API to get the latest environment data.
// Will return an error in case of failure or unexpected response.
func (c *Client) GetEnvironmentFlagsFromAPI(ctx context.Context) (Flags, error) {
resp, err := c.client.NewRequest().
req := c.client.NewRequest()
maybeEc := ctx.Value(contextKeyEvaluationContext)
if maybeEc != nil {
envCtx := maybeEc.(*EvaluationContext).Environment
if envCtx != nil {
req.SetHeader(flaghttp.EnvironmentKeyHeader, envCtx.APIKey)
}
}
resp, err := req.
SetContext(ctx).
ForceContentType("application/json").
Get(c.config.baseURL + "flags/")
Expand All @@ -195,16 +230,27 @@ func (c *Client) GetEnvironmentFlagsFromAPI(ctx context.Context) (Flags, error)

// GetIdentityFlagsFromAPI tries to contact the Flagsmith API to get the latest identity flags.
// Will return an error in case of failure or unexpected response.
func (c *Client) GetIdentityFlagsFromAPI(ctx context.Context, identifier string, traits []*Trait, opts *GetIdentityFlagsOpts) (Flags, error) {
func (c *Client) GetIdentityFlagsFromAPI(ctx context.Context, identifier string, traits []*Trait) (Flags, error) {
body := struct {
Identifier string `json:"identifier"`
Traits []*Trait `json:"traits,omitempty"`
GetIdentityFlagsOpts
Transient *bool `json:"transient,omitempty"`
}{Identifier: identifier, Traits: traits}
if opts != nil {
body.Transient = opts.Transient
req := c.client.NewRequest()
maybeEc := ctx.Value(contextKeyEvaluationContext)
if maybeEc != nil {
ec := maybeEc.(*EvaluationContext)
envCtx := ec.Environment
if envCtx != nil {
req.SetHeader(flaghttp.EnvironmentKeyHeader, envCtx.APIKey)
}
idCtx := ec.Identity
if idCtx != nil {
// `Identifier` and `Traits` had been set by `GetFlags` earlier.
body.Transient = &idCtx.Transient
}
}
resp, err := c.client.NewRequest().
resp, err := req.
SetBody(&body).
SetContext(ctx).
ForceContentType("application/json").
Expand Down Expand Up @@ -295,7 +341,7 @@ func (c *Client) UpdateEnvironment(ctx context.Context) error {
}

func (c *Client) getIdentityModel(identifier string, apiKey string, traits []*Trait) identities.IdentityModel {
identityTraits := make([]*TraitModel, len(traits))
identityTraits := make([]*enginetraits.TraitModel, len(traits))
for i, trait := range traits {
identityTraits[i] = trait.ToTraitModel()
}
Expand Down
Loading

0 comments on commit ec66647

Please sign in to comment.