Skip to content

Commit dd7e12a

Browse files
committed
Remove atomics + more improvements
1 parent 1e23a8c commit dd7e12a

File tree

19 files changed

+186
-359
lines changed

19 files changed

+186
-359
lines changed

client.go

Lines changed: 64 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"fmt"
66
"log/slog"
77
"strings"
8-
"sync/atomic"
8+
"sync"
99
"time"
1010

1111
"github.com/Flagsmith/flagsmith-go-client/v4/flagengine"
@@ -17,10 +17,6 @@ import (
1717
enginetraits "github.com/Flagsmith/flagsmith-go-client/v4/flagengine/identities/traits"
1818
)
1919

20-
type contextKey string
21-
22-
var contextKeyEvaluationContext = contextKey("evaluationContext")
23-
2420
// Client is a Flagsmith client, used to evaluate feature flags. Use [NewClient] to instantiate a client.
2521
//
2622
// Use [Client.GetFlags] to evaluate feature flags.
@@ -33,11 +29,12 @@ type Client struct {
3329
ctxLocalEval context.Context
3430
ctxAnalytics context.Context
3531

36-
environment atomic.Value
37-
identitiesWithOverrides atomic.Value
32+
environment environmentState
33+
identityOverrides sync.Map
3834

39-
log *slog.Logger
40-
client *resty.Client
35+
log *slog.Logger
36+
logHandlerOptions *slog.HandlerOptions
37+
client *resty.Client
4138
}
4239

4340
// NewClient creates a Flagsmith [Client] using the environment determined by apiKey.
@@ -96,7 +93,7 @@ func NewClient(apiKey string, options ...Option) *Client {
9693
panic("local evaluation and offline handler cannot be used together.")
9794
}
9895
if c.offlineHandler != nil {
99-
c.environment.Store(c.offlineHandler.GetEnvironment())
96+
c.environment.Set(c.offlineHandler.GetEnvironment())
10097
}
10198

10299
if c.config.localEvaluation {
@@ -118,8 +115,8 @@ func NewClient(apiKey string, options ...Option) *Client {
118115

119116
// GetFlags evaluates the feature flags within an [EvaluationContext].
120117
//
121-
// When flag evaluation fails, the return value is determined by the default flag handler from
122-
// [WithDefaultHandler], if one was provided.
118+
// When flag evaluation fails, the value of each Flag is determined by the default flag handler
119+
// from [WithDefaultHandler], if one was provided.
123120
//
124121
// Flags are evaluated remotely by the Flagsmith API by default.
125122
// To evaluate flags locally, instantiate a client using [WithLocalEvaluation].
@@ -137,9 +134,16 @@ func NewClient(apiKey string, options ...Option) *Client {
137134
// // Flag is enabled for this evaluation context
138135
// }
139136
func (c *Client) GetFlags(ctx context.Context, ec EvaluationContext) (f Flags, err error) {
140-
ctx = withEvaluationContext(ctx, ec)
141-
if ec.Identity.Identifier != "" || ec.Identity.Transient {
142-
return c.GetIdentityFlags(ctx, ec.Identity.Identifier, mapIdentityEvaluationContextToTraits(ec.Identity))
137+
if ec.identifier != "" {
138+
if c.config.offlineMode && c.offlineHandler != nil || c.config.localEvaluation {
139+
f, err = c.getIdentityFlagsFromEnvironment(ec.identifier, ec.traits)
140+
} else {
141+
f, err = c.getIdentityFlagsFromAPI(ctx, ec.identifier, ec.traits)
142+
}
143+
if err != nil && c.defaultFlagHandler != nil {
144+
f = Flags{defaultFlagHandler: c.defaultFlagHandler}
145+
}
146+
return f, err
143147
}
144148
return c.GetEnvironmentFlags(ctx)
145149
}
@@ -166,12 +170,10 @@ func (c *Client) UpdateEnvironment(ctx context.Context) error {
166170
e := &FlagsmithAPIError{Msg: msg, Err: nil, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()}
167171
return c.handleError(e)
168172
}
169-
c.environment.Store(&env)
170-
identitiesWithOverrides := make(map[string]identities.IdentityModel)
173+
c.environment.Set(&env)
171174
for _, id := range env.IdentityOverrides {
172-
identitiesWithOverrides[id.Identifier] = *id
175+
c.identityOverrides.Store(id.Identifier, *id)
173176
}
174-
c.identitiesWithOverrides.Store(identitiesWithOverrides)
175177

176178
c.log.Info("environment updated", "environment", env.APIKey)
177179
return nil
@@ -180,8 +182,8 @@ func (c *Client) UpdateEnvironment(ctx context.Context) error {
180182
// GetIdentitySegments returns the segments that this evaluation context is a part of. It requires a local environment
181183
// provided by [WithLocalEvaluation] and/or [WithOfflineHandler].
182184
func (c *Client) GetIdentitySegments(ec EvaluationContext) ([]*segments.SegmentModel, error) {
183-
if env, ok := c.environment.Load().(*environments.EnvironmentModel); ok {
184-
identity := c.getIdentityModel(ec.Identity.Identifier, env.APIKey, mapIdentityEvaluationContextToTraits(ec.Identity))
185+
if env := c.environment.Get(); env != nil {
186+
identity := c.getIdentityModel(ec.identifier, env.APIKey, ec.traits)
185187
return flagengine.GetIdentitySegments(env, &identity), nil
186188
}
187189
return nil, &FlagsmithClientError{msg: "no local environment available to calculate identity segments"}
@@ -234,44 +236,20 @@ func (c *Client) GetEnvironmentFlags(ctx context.Context) (f Flags, err error) {
234236
return Flags{}, &FlagsmithClientError{msg: fmt.Sprintf("Failed to fetch flags with error: %s", err)}
235237
}
236238

237-
// GetIdentityFlags evaluates and returns feature flags, using the given identity as the flag evaluation context.
239+
// GetIdentityFlags evaluates and returns the flags for an identity.
238240
//
239-
// Deprecated: Use [Client.GetFlags] instead.
240-
func (c *Client) GetIdentityFlags(ctx context.Context, identifier string, traits []Trait) (f Flags, err error) {
241-
if c.config.localEvaluation || c.config.offlineMode {
242-
if f, err = c.getIdentityFlagsFromEnvironment(identifier, traits); err == nil {
243-
return f, nil
244-
}
245-
} else {
246-
if f, err = c.getIdentityFlagsFromAPI(ctx, identifier, traits); err == nil {
247-
return f, nil
248-
}
249-
}
250-
if c.offlineHandler != nil {
251-
return c.getIdentityFlagsFromEnvironment(identifier, traits)
252-
} else if c.defaultFlagHandler != nil {
253-
return Flags{defaultFlagHandler: c.defaultFlagHandler}, nil
254-
}
255-
return Flags{}, &FlagsmithClientError{msg: fmt.Sprintf("Failed to fetch flags with error: %s", err)}
241+
// Deprecated. Use GetFlags instead.
242+
func (c *Client) GetIdentityFlags(ctx context.Context, identifier string, traits map[string]interface{}) (f Flags, err error) {
243+
return c.GetFlags(ctx, NewEvaluationContext(identifier, traits))
256244
}
257245

258246
// getEnvironmentFlagsFromAPI tries to contact the Flagsmith API to get the latest environment data.
259247
func (c *Client) getEnvironmentFlagsFromAPI(ctx context.Context) (Flags, error) {
260-
client := c.client.
261-
OnBeforeRequest(newRestyLogRequestMiddleware(c.log)).
262-
OnAfterResponse(newRestyLogResponseMiddleware(c.log)).
263-
OnAfterResponse(newRestyLogResponseMiddleware(c.log))
264-
req := client.NewRequest()
265-
ec, ok := getEvaluationContext(ctx)
266-
if ok && ec.Environment.APIKey != "" {
267-
req.SetHeader(EnvironmentKeyHeader, ec.Environment.APIKey)
268-
}
269-
248+
req := c.client.NewRequest()
270249
resp, err := req.
271250
SetContext(ctx).
272251
ForceContentType("application/json").
273252
Get(c.config.baseURL + "flags/")
274-
275253
if err != nil {
276254
msg := fmt.Sprintf("GET /flags failed: %s", err)
277255
return Flags{}, &FlagsmithAPIError{Msg: msg, Err: err, ResponseStatusCode: resp.StatusCode(), ResponseStatus: resp.Status()}
@@ -283,22 +261,19 @@ func (c *Client) getEnvironmentFlagsFromAPI(ctx context.Context) (Flags, error)
283261
return makeFlagsFromAPIFlags(resp.Body(), c.analyticsProcessor, c.defaultFlagHandler)
284262
}
285263

286-
// getIdentityFlagsFromAPI tries to contact the Flagsmith API to get the latest identity flags.
264+
// getIdentityFlagsFromAPI tries to contact the Flagsmith API to Get the latest identity flags.
287265
// Will return an error in case of failure or unexpected response.
288-
func (c *Client) getIdentityFlagsFromAPI(ctx context.Context, identifier string, traits []Trait) (Flags, error) {
266+
func (c *Client) getIdentityFlagsFromAPI(ctx context.Context, identifier string, traits map[string]interface{}) (Flags, error) {
267+
tt := make([]Trait, 0, len(traits))
268+
for k, v := range traits {
269+
tt = append(tt, Trait{Key: k, Value: v})
270+
}
271+
289272
body := struct {
290273
Identifier string `json:"identifier"`
291274
Traits []Trait `json:"traits,omitempty"`
292-
Transient bool `json:"transient,omitempty"`
293-
}{Identifier: identifier, Traits: traits}
275+
}{Identifier: identifier, Traits: tt}
294276
req := c.client.NewRequest()
295-
ec, ok := getEvaluationContext(ctx)
296-
if ok {
297-
if ec.Environment.APIKey != "" {
298-
req.SetHeader(EnvironmentKeyHeader, ec.Environment.APIKey)
299-
}
300-
body.Transient = ec.Identity.Transient
301-
}
302277
resp, err := req.
303278
SetBody(&body).
304279
SetContext(ctx).
@@ -316,8 +291,8 @@ func (c *Client) getIdentityFlagsFromAPI(ctx context.Context, identifier string,
316291
}
317292

318293
func (c *Client) getEnvironmentFlagsFromEnvironment() (Flags, error) {
319-
env, ok := c.environment.Load().(*environments.EnvironmentModel)
320-
if !ok {
294+
env := c.environment.Get()
295+
if env == nil {
321296
return Flags{}, fmt.Errorf("getEnvironmentFlagsFromEnvironment: no local environment is available")
322297
}
323298
return makeFlagsFromFeatureStates(
@@ -328,9 +303,9 @@ func (c *Client) getEnvironmentFlagsFromEnvironment() (Flags, error) {
328303
), nil
329304
}
330305

331-
func (c *Client) getIdentityFlagsFromEnvironment(identifier string, traits []Trait) (Flags, error) {
332-
env, ok := c.environment.Load().(*environments.EnvironmentModel)
333-
if !ok {
306+
func (c *Client) getIdentityFlagsFromEnvironment(identifier string, traits map[string]interface{}) (Flags, error) {
307+
env := c.environment.Get()
308+
if env := c.environment.Get(); env == nil {
334309
return Flags{}, fmt.Errorf("getIdentityFlagsFromDocument: no local environment is available")
335310
}
336311
identity := c.getIdentityModel(identifier, env.APIKey, traits)
@@ -366,15 +341,15 @@ func (c *Client) pollEnvironment(ctx context.Context) {
366341
}
367342
}
368343

369-
func (c *Client) getIdentityModel(identifier string, apiKey string, traits []Trait) identities.IdentityModel {
370-
identityTraits := make([]*enginetraits.TraitModel, len(traits))
371-
for i, trait := range traits {
372-
identityTraits[i] = trait.ToTraitModel()
344+
func (c *Client) getIdentityModel(identifier string, apiKey string, traits map[string]interface{}) identities.IdentityModel {
345+
identityTraits := make([]*enginetraits.Trait, 0, len(traits))
346+
for k, v := range traits {
347+
identityTraits = append(identityTraits, enginetraits.NewTrait(k, v))
373348
}
374349

375-
identitiesWithOverrides, _ := c.identitiesWithOverrides.Load().(map[string]identities.IdentityModel)
376-
identity, ok := identitiesWithOverrides[identifier]
350+
i, ok := c.identityOverrides.Load(identifier)
377351
if ok {
352+
identity := i.(identities.IdentityModel)
378353
identity.IdentityTraits = identityTraits
379354
return identity
380355
}
@@ -386,18 +361,26 @@ func (c *Client) getIdentityModel(identifier string, apiKey string, traits []Tra
386361
}
387362
}
388363

389-
func withEvaluationContext(ctx context.Context, ec EvaluationContext) context.Context {
390-
return context.WithValue(ctx, contextKeyEvaluationContext, ec)
391-
}
392-
393-
func getEvaluationContext(ctx context.Context) (ec EvaluationContext, ok bool) {
394-
ec, ok = ctx.Value(contextKeyEvaluationContext).(EvaluationContext)
395-
return ec, ok
396-
}
397-
398364
func (c *Client) handleError(err *FlagsmithAPIError) *FlagsmithAPIError {
399365
if c.errorHandler != nil {
400366
c.errorHandler(err)
401367
}
402368
return err
403369
}
370+
371+
type environmentState struct {
372+
mu sync.RWMutex
373+
env *environments.EnvironmentModel
374+
}
375+
376+
func (es *environmentState) Get() *environments.EnvironmentModel {
377+
es.mu.RLock()
378+
defer es.mu.RUnlock()
379+
return es.env
380+
}
381+
382+
func (es *environmentState) Set(env *environments.EnvironmentModel) {
383+
es.mu.Lock()
384+
defer es.mu.Unlock()
385+
es.env = env
386+
}

0 commit comments

Comments
 (0)