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// }
139136func (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].
182184func (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.
259247func (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
318293func (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-
398364func (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